diff --git a/.eslintrc.yaml b/.eslintrc.yaml index b8b666983..88673e93f 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -29,10 +29,10 @@ rules: # Not possible while strictNullChecks is disabled in tsconfig. "@typescript-eslint/no-non-null-assertion": "error" no-unused-vars: off - "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "destructuredArrayIgnorePattern": "^_" }] + "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "destructuredArrayIgnorePattern": "^_", "caughtErrorsIgnorePattern": "^_"}] no-use-before-define: off "@typescript-eslint/no-use-before-define": ["error", { "functions": false }] - "@typescript-eslint/no-var-requires": off # Remove this override once all files use ES6 style imports. + "@typescript-eslint/no-require-imports": off # Remove this override once all files use ES6 style imports. prefer-const: ["error", {"destructuring": "all"}] react/no-array-index-key: error react/prop-types: off # Remove this override once all props have been typed using PropTypes or TypeScript. diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3eca90d18..c257f4e67 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [20, 22, 24] + node: [24, 26] steps: - uses: actions/checkout@v7 - uses: actions/setup-node@v6 @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [20, 22, 24] + node: [24, 26] steps: - uses: actions/checkout@v7 - uses: actions/setup-node@v6 @@ -38,7 +38,7 @@ jobs: node-version: ${{ matrix.node }} # Build is implicit with the "prepare" life cycle script - run: npm ci --loglevel verbose - - run: npm run get-data + - run: npm run fetch-test-data - run: npx playwright install chromium - run: npm run smoke-test diff --git a/DEV_DOCS.md b/DEV_DOCS.md index 6f462d251..2e7928245 100644 --- a/DEV_DOCS.md +++ b/DEV_DOCS.md @@ -93,7 +93,7 @@ General advice for writing tests: > Smoke tests are currently very limited but we may expand these in the future -1. Fetch datasets with `npm run get-data`. +1. Fetch testing datasets with `npm run fetch-test-data`. 2. Install the testing browser with `npx playwright install chromium`. 3. Run `npm run smoke-test`. @@ -110,15 +110,9 @@ If embarking on this journey, consider using Playwright since it is already used Linting via eslint: `npm run lint`, typechecking via `npm type-check` -## Heroku review apps - - -A Heroku pipeline for this repository is connected to GitHub under the nextstrain-bot user account. The Review Apps feature facilitates manual review of changes by automatically creating a test instance from the PR source branch and adding a link to it on the GitHub PR page. These apps are based on configuration in [app.json](./app.json). - - #### Test on downstream repositories -Additionally, a GitHub Actions workflow has been set up to generate PRs in downstream repositories that reflect the new changes in Auspice. To use it, add the label [preview on auspice.us](https://github.com/nextstrain/auspice/labels/preview%20on%20auspice.us) and/or [preview on nextstrain.org](https://github.com/nextstrain/auspice/labels/preview%20on%20nextstrain.org). +A GitHub Actions workflow has been set up to generate PRs in downstream repositories that reflect the new changes in Auspice. To use it, add the label [preview on auspice.us](https://github.com/nextstrain/auspice/labels/preview%20on%20auspice.us) and/or [preview on nextstrain.org](https://github.com/nextstrain/auspice/labels/preview%20on%20nextstrain.org). ## git-lfs diff --git a/README.md b/README.md index 4f35c93a4..92990bf98 100644 --- a/README.md +++ b/README.md @@ -39,20 +39,11 @@ See [the relevant page on Auspice docs](https://docs.nextstrain.org/projects/aus To get up & running, you'll need datasets to visualise. (Please see the [nextstrain docs](https://nextstrain.org/docs/) for tutorials on how to run your own analyses.) -If you've installed auspice from `npm` you may get datasets to display via: +If you've installed auspice from `npm` you may get an example dataset to display via: ```bash mkdir data curl http://data.nextstrain.org/zika.json --compressed -o data/zika.json -curl http://data.nextstrain.org/ncov.json --compressed -o data/ncov.json -... -``` - -If you've installed auspice from source, we have a helper script to download a number of datasets for testing: - -```bash -# from the auspice src directory -npm run get-data ``` ### Obtain narratives to view locally diff --git a/app.json b/app.json deleted file mode 100644 index ac12f1bd8..000000000 --- a/app.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "auspice", - "scripts": {}, - "env": { - "HOST": { - "description": "For Auspice server; see https://docs.nextstrain.org/projects/auspice/en/stable/server/index.html#deploying-via-heroku", - "value": "0.0.0.0" - }, - "NPM_CONFIG_PRODUCTION": { - "description": "Runs with NODE_ENV=production; see https://devcenter.heroku.com/articles/nodejs-support#configuring-npm", - "value": "true" - } - }, - "formation": { - "web": { - "quantity": 1 - } - }, - "addons": [], - "buildpacks": [ - { - "url": "heroku/nodejs" - } - ], - "stack": "heroku-24" -} diff --git a/auspice.js b/auspice.js index 6b352c44b..292d4058d 100755 --- a/auspice.js +++ b/auspice.js @@ -1,11 +1,11 @@ #!/usr/bin/env node -const argparse = require('argparse'); -const version = require('./src/version').version; -const view = require("./cli/view"); -const build = require("./cli/build"); -const develop = require("./cli/develop"); -const convert = require("./cli/convert"); +import argparse from 'argparse'; +import { version } from './src/version.js'; +import * as view from "./cli/view.ts"; +import * as build from "./cli/build.ts"; +import * as develop from "./cli/develop.ts"; +import * as convert from "./cli/convert.js"; const parser = new argparse.ArgumentParser({ version: version, @@ -38,5 +38,3 @@ if (args.subcommand === "build") { } else if (args.subcommand === "convert") { convert.run(args); } - -// console.dir(args); diff --git a/babel.config.js b/babel.config.cjs similarity index 90% rename from babel.config.js rename to babel.config.cjs index 25c781165..6f1bd17b0 100644 --- a/babel.config.js +++ b/babel.config.cjs @@ -1,4 +1,4 @@ -const utils = require('./cli/utils'); +const utils = require('./cli/utils.ts'); /* What variables does this config depend on? * process.env.BABEL_EXTENSION_PATH -- a resolved path @@ -31,6 +31,9 @@ module.exports = function babelConfig(api) { if (api.env("development")) { plugins.push(["react-hot-loader/babel", { safetyNet: false }]); } + if (api.env('test')) { + plugins.push("babel-plugin-transform-import-meta"); + } if (process.env.BABEL_INCLUDE_TIMING_FUNCTIONS === "false") { plugins.push(["strip-function-call", {strip: ["timerStart", "timerEnd"]}]); } diff --git a/cli/build.js b/cli/build.ts similarity index 86% rename from cli/build.js rename to cli/build.ts index ad4cb34e0..c86e0267f 100644 --- a/cli/build.js +++ b/cli/build.ts @@ -1,14 +1,20 @@ -const webpack = require("webpack"); -const path = require("path"); -const generateWebpackConfig = require("../webpack.config.js").default; -const utils = require("./utils"); +import webpack from "webpack"; +import path from "path"; +import { fileURLToPath } from 'url'; +import { createRequire } from 'module'; +import * as utils from "./utils.ts"; + +const require = createRequire(import.meta.url); +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const generateWebpackConfig = require("../webpack.config.cjs").default; const SUPPRESS = require('argparse').Const.SUPPRESS; -const addParser = (parser) => { +const addParser = (parser): void => { const description = `Build the client source code bundle. For development, you may want to use "auspice develop" which recompiles code on the fly as changes are made. You may provide customisations (e.g. code, options) to this step to modify the functionality and appearance of auspice. - To serve the bundle you will need a server such as "auspice view". + To serve the bundle you will need a server such as "auspice view". `; const subparser = parser.addParser('build', {addHelp: true, description}); @@ -24,7 +30,7 @@ const addParser = (parser) => { subparser.addArgument('--serverless', {action: "storeTrue", help: SUPPRESS}); }; -const run = (args) => { +const run = (args): void => { /* webpack set up */ const extensionPath = args.extend ? path.resolve(args.extend) : undefined; @@ -64,14 +70,14 @@ const run = (args) => { /* Where should the built files be saved? (or sourced??) * This may grow more complex over time */ -function getCustomOutputPath(extensions) { +function getCustomOutputPath(extensions): string | false { if (extensions && path.resolve(__dirname, "..") !== process.cwd()) { return process.cwd(); } return false; } -module.exports = { +export { addParser, run }; diff --git a/cli/convert.js b/cli/convert.js index fa0ef3938..b9335f00d 100644 --- a/cli/convert.js +++ b/cli/convert.js @@ -1,7 +1,7 @@ /* eslint no-console: off */ -const fs = require("fs"); -const convertFromV1 = require("./server/convertJsonSchemas").convertFromV1; -const utils = require("./utils"); +import fs from "fs"; +import { convertFromV1 } from "./server/convertJsonSchemas.js"; +import * as utils from "./utils.ts"; const addParser = (parser) => { @@ -35,7 +35,7 @@ const run = (args) => { fs.writeFileSync(args.output, JSON.stringify(v2, null, args.minify_json ? 0 : 2)); }; -module.exports = { +export { addParser, run }; diff --git a/cli/dev/reverse-proxy.js b/cli/dev/reverse-proxy.js index 3de2062a1..8c349b82c 100644 --- a/cli/dev/reverse-proxy.js +++ b/cli/dev/reverse-proxy.js @@ -1,7 +1,6 @@ - -const utils = require("../utils"); -const { URL } = require("url"); -const fetch = require("node-fetch"); +import * as utils from "../utils.ts"; +import { URL } from "url"; +import fetch from "node-fetch"; const PROXY = process.env.PROXY || `http://localhost:5000` @@ -59,8 +58,6 @@ async function proxy(req, res) { } } -module.exports = { - getAvailable: proxy, - getDataset: proxy, - getNarrative: proxy, -}; \ No newline at end of file +export const getAvailable = proxy; +export const getDataset = proxy; +export const getNarrative = proxy; diff --git a/cli/develop.js b/cli/develop.ts similarity index 69% rename from cli/develop.js rename to cli/develop.ts index e6d6831fc..71249f839 100644 --- a/cli/develop.js +++ b/cli/develop.ts @@ -1,17 +1,26 @@ /* eslint no-console: off */ -const path = require("path"); -const express = require("express"); -const webpack = require("webpack"); -const webpackDevMiddleware = require("webpack-dev-middleware"); -const webpackHotMiddleware = require("webpack-hot-middleware"); -const utils = require("./utils"); -const { loadAndAddHandlers, serveRelativeFilepaths } = require("./view"); -const version = require('../src/version').version; -const chalk = require('chalk'); -const generateWebpackConfig = require("../webpack.config.js").default; +import path from "path"; +import { fileURLToPath } from 'url'; +import { createRequire } from 'module'; +import express from "express"; +import webpack from "webpack"; +import webpackDevMiddleware from "webpack-dev-middleware"; +import webpackHotMiddleware from "webpack-hot-middleware"; +import * as utils from "./utils.ts"; +import * as view from "./view.ts"; +import { version } from '../src/version.js'; +import _chalk from 'chalk'; +/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */ +const chalk = _chalk as any as import('chalk').Chalk; +import { processPathArguments } from "./server/processPaths.ts"; + +const require = createRequire(import.meta.url); +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const generateWebpackConfig = require("../webpack.config.cjs").default; const SUPPRESS = require('argparse').Const.SUPPRESS; -const addParser = (parser) => { +const addParser = (parser): void => { const description = `Launch auspice in development mode. This runs a local server and uses hot-reloading to allow automatic updating as you edit the code. NOTE: there is a speed penalty for this ability and this should never be used for production. @@ -21,8 +30,7 @@ const addParser = (parser) => { subparser.addArgument('--verbose', {action: "storeTrue", help: "Print more verbose progress messages in the terminal."}); subparser.addArgument('--extend', {action: "store", metavar: "JSON", help: "Client customisations to be applied. See documentation for more details. Note that hot reloading does not currently work for these customisations."}); subparser.addArgument('--handlers', {action: "store", metavar: "JS", help: "Overwrite the provided server handlers for client requests. See documentation for more details."}); - subparser.addArgument('--datasetDir', {metavar: "PATH", help: "Directory where datasets (JSONs) are sourced. This is ignored if you define custom handlers."}); - subparser.addArgument('--narrativeDir', {metavar: "PATH", help: "Directory where narratives (Markdown files) are sourced. This is ignored if you define custom handlers."}); + view.addDatasetNarrativePathArgs(subparser) subparser.addArgument('--includeTiming', {action: "storeTrue", help: "Do not strip timing functions. With these included the browser console will print timing measurements for a number of functions."}); /* there are some options which we deliberately do not document via `--help`. See build.js for explanations. */ @@ -30,7 +38,9 @@ const addParser = (parser) => { }; -const run = (args) => { +const run = async (args): Promise => { + const dataPaths = processPathArguments(args) + /* Basic server set up */ const app = express(); app.set('port', process.env.PORT || 4000); @@ -38,7 +48,7 @@ const run = (args) => { const baseDir = path.resolve(__dirname, ".."); utils.verbose(`Serving index / favicon etc from "${baseDir}"`); - app.get("/favicon.png", (req, res) => {res.sendFile(path.join(baseDir, "favicon.png"));}); + app.get("/favicon.png", (_req, res) => {res.sendFile(path.join(baseDir, "favicon.png"));}); /* webpack set up */ const extensionPath = args.extend ? path.resolve(args.extend) : undefined; @@ -51,7 +61,7 @@ const run = (args) => { process.env.BABEL_EXTENSION_PATH = extensionPath; /* Redirects / to webpack-generated index */ - app.use((req, res, next) => { + app.use((req, _res, next) => { if (!/^\/__webpack_hmr|^\/charon|\.[A-Za-z0-9]{1,5}$/.test(req.path)) { req.url = webpackConfig.output.publicPath; } @@ -69,12 +79,20 @@ const run = (args) => { let handlerMsg = ""; if (args.gh_pages) { - handlerMsg = serveRelativeFilepaths({app, dir: path.resolve(args.gh_pages)}); + handlerMsg = view.serveRelativeFilepaths({app, dir: path.resolve(args.gh_pages)}); + } else if (args.handlers) { + handlerMsg = await view.customRouteHandlers(app, path.resolve(args.handlers)); } else { - handlerMsg = loadAndAddHandlers({app, handlersArg: args.handlers, datasetDir: args.datasetDir, narrativeDir: args.narrativeDir}); + handlerMsg = view.defaultRouteHandlers({app, dataPaths}); } + /* Handle unhandled API (charon) routes */ + app.get("/charon*", (req, res) => { + const errorMessage = "Query unhandled -- " + req.originalUrl; + utils.warn(errorMessage); + return res.status(500).type("text/plain").send(errorMessage); + }); - app.get("*", (req, res) => res.redirect("/")); + app.get("*", (_req, res) => res.redirect("/")); const server = app.listen(app.get('port'), app.get('host'), () => { utils.log("\n\n---------------------------------------------------"); @@ -95,8 +113,8 @@ const run = (args) => { utils.error(`Host ${app.get('host')} is not a valid address. The server could not be started. If you did not provide a HOST environment variable when starting the app you may have HOST already set in your system. You can either change that variable, or override HOST when starting the app. Example commands to fix: - ${chalk.yellow('HOST="localhost" auspice develop')} - ${chalk.yellow('HOST="localhost" npm run develop')}`); + ${chalk.yellow('HOST="localhost" auspice develop ')} + ${chalk.yellow('HOST="localhost" npm run develop -- ')}`); } utils.error(`Uncaught error in app.listen(). Code: ${error.code}`); @@ -105,7 +123,7 @@ const run = (args) => { }; -module.exports = { +export { addParser, run }; diff --git a/cli/server/convertJsonSchemas.js b/cli/server/convertJsonSchemas.js index f95e16629..d36bb102f 100644 --- a/cli/server/convertJsonSchemas.js +++ b/cli/server/convertJsonSchemas.js @@ -1,4 +1,4 @@ -const utils = require("../utils"); +import * as utils from "../utils.ts"; /** In auspice v1, the `prettyString` function was used extensively to transform values * for "nicer" display. v2 JSONs intentially avoid this -- the strings are intended to @@ -397,7 +397,7 @@ const setNodeBranchAttrs = (v2) => { }; -const convertFromV1 = ({tree, meta}) => { +export const convertFromV1 = ({tree, meta}) => { const v2 = {version: "v2", meta: { extensions: { original_version: "v1" }}}; // set metadata setColorings(v2["meta"], meta); @@ -411,8 +411,3 @@ const convertFromV1 = ({tree, meta}) => { removeNonV2TreeProps(v2); return v2; }; - - -module.exports = { - convertFromV1 -}; diff --git a/cli/server/getAvailable.js b/cli/server/getAvailable.js deleted file mode 100644 index c7e66418b..000000000 --- a/cli/server/getAvailable.js +++ /dev/null @@ -1,101 +0,0 @@ -const utils = require("../utils"); -const fs = require('fs'); -const { promisify } = require('util'); -const { findAvailableSecondTreeOptions } = require('./getDatasetHelpers'); - -const readdir = promisify(fs.readdir); - -const getAvailableDatasets = async (localDataPath) => { - const datasets = []; - /* NOTE: if there are v1 & v2 files with the same name the v2 JSON is - * preferentially fetched. E.g. if `zika.json`, `zika_meta.json` and - * `zika_tree.json` exist then only `zika.json` is viewable in auspice. - */ - try { - const files = await readdir(localDataPath); - /* v2 files -- match JSONs not ending with `_tree.json`, `_meta.json`, - `_tip-frequencies.json`, `_seq.json` */ - const v2Files = files.filter((file) => ( - file.endsWith(".json") && - !file.includes("manifest") && - !file.endsWith("_tree.json") && - !file.endsWith("_meta.json") && - !file.endsWith("_tip-frequencies.json") && - !file.endsWith("_root-sequence.json") && - !file.endsWith("_seq.json") && - !file.endsWith("_measurements.json") - )) - .map((file) => file - .replace(".json", "") - .split("_") - .join("/") - ); - - v2Files.forEach((filepath) => { - datasets.push({ - request: filepath, - v2: true, - secondTreeOptions: findAvailableSecondTreeOptions(filepath, v2Files) - }); - }); - - /* v1 files -- match files ending with `_tree.json` */ - const v1Files = files - .filter((file) => file.endsWith("_tree.json")) - .map((file) => file - .replace("_tree.json", "") - .split("_") - .join("/") - ); - - v1Files.forEach((filepath) => { - datasets.push({ - request: filepath, - v2: false, - secondTreeOptions: findAvailableSecondTreeOptions(filepath, v1Files) - }); - }); - } catch (err) { - utils.warn(`Couldn't collect available dataset files (path searched: ${localDataPath})`); - utils.verbose(err); - } - return datasets; -}; - -const getAvailableNarratives = async (localNarrativesPath) => { - let narratives = []; - try { - narratives = await readdir(localNarrativesPath); - narratives = narratives - .filter((file) => file.endsWith(".md") && file!=="README.md") - .map((file) => file.replace(".md", "")) - .map((file) => file.split("_").join("/")) - .map((filepath) => `narratives/${filepath}`) - .map((filepath) => ({request: filepath})); - } catch (err) { - utils.warn(`Couldn't collect available narratives (path searched: ${localNarrativesPath})`); - utils.verbose(err); - } - return narratives; -}; - -const setUpGetAvailableHandler = ({datasetsPath, narrativesPath}) => { - /* return Auspice's default handler for "/charon/getAvailable" requests. - * Remember that auspice itself only serves local files. - * Servers often use their own handler instead of this. - */ - return async (req, res) => { - utils.log("GET AVAILABLE returning locally available datasets & narratives"); - const datasets = await getAvailableDatasets(datasetsPath); - const narratives = await getAvailableNarratives(narrativesPath); - res.json({datasets, narratives}); - }; - -}; - - -module.exports = { - setUpGetAvailableHandler, - getAvailableDatasets, - getAvailableNarratives -}; diff --git a/cli/server/getAvailable.ts b/cli/server/getAvailable.ts new file mode 100644 index 000000000..888a2d689 --- /dev/null +++ b/cli/server/getAvailable.ts @@ -0,0 +1,143 @@ +import * as utils from "../utils.ts"; +import fs from 'fs'; +import path from "path"; +import { promisify } from 'util'; +import { findAvailableSecondTreeOptions } from './getDatasetHelpers.ts'; + +const readdir = promisify(fs.readdir); + +export interface DatasetInfo { + request: string; + v2: boolean; + secondTreeOptions: string[]; + fileType: string; + dir: string; + buildUrl?: string; +} + +export interface NarrativeInfo { + fullPath: string; + request: string; + fileType: string; +} + +export const getAvailableDatasets = (dir, files): DatasetInfo[] => { + const datasets = []; + /* NOTE: if there are v1 & v2 files with the same name the v2 JSON is + * preferentially fetched. E.g. if `zika.json`, `zika_meta.json` and + * `zika_tree.json` exist then only `zika.json` is viewable in auspice. + */ + + /* v2 files -- match JSONs not ending with `_tree.json`, `_meta.json`, + `_tip-frequencies.json`, `_seq.json` */ + const v2Files = files.filter((file) => ( + file.endsWith(".json") && + !file.includes("manifest") && + !file.endsWith("_tree.json") && + !file.endsWith("_meta.json") && + !file.endsWith("_tip-frequencies.json") && + !file.endsWith("_root-sequence.json") && + !file.endsWith("_seq.json") && + !file.endsWith("_measurements.json") + )) + .map((file) => file + .replace(".json", "") + .split("_") + .join("/") + ); + + v2Files.forEach((filepath) => { + datasets.push({ + request: filepath, + v2: true, + secondTreeOptions: findAvailableSecondTreeOptions(filepath, v2Files), + fileType: 'dataset', + dir, + }); + }); + + /* v1 files -- match files ending with `_tree.json` */ + const v1Files = files + .filter((file) => file.endsWith("_tree.json")) + .map((file) => file + .replace("_tree.json", "") + .split("_") + .join("/") + ); + + v1Files.forEach((filepath) => { + datasets.push({ + request: filepath, + v2: false, + secondTreeOptions: findAvailableSecondTreeOptions(filepath, v1Files), + fileType: 'dataset', + dir, + }); + }); + return datasets; +}; + +export const getAvailableNarratives = (dir, files): NarrativeInfo[] => { + return files + .filter((file) => file.endsWith(".md") && file!=="README.md") + .map((file) => ({ + fullPath: path.join(dir, file), + request: 'narratives/' + file.replace(".md", "").split("_").join("/"), + fileType: 'narrative', + })); +}; + +export const setUpGetAvailableHandler = (dataPaths) => { + /* return Auspice's default handler for "/charon/getAvailable" requests. + * Remember that auspice itself only serves local files. + * Servers often use their own handler instead of this. + */ + return async (_req, res): Promise => { + utils.log("GET AVAILABLE returning locally available datasets & narratives"); + let resources: (DatasetInfo | NarrativeInfo)[] = [] + /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */ + for (const [p, dataTypes] of Object.entries(dataPaths) as [string, Set][]) { + try { + // FUTURE TODO - recurse into subfolders (readdir can do this natively) + const files = await readdir(p); + if (dataTypes.has('datasets')) { + resources.push(...getAvailableDatasets(p, files)) + } + if (dataTypes.has('narratives')) { + resources.push(...getAvailableNarratives(p, files)) + } + } catch (err) { + utils.warn(`Couldn't collect files (path searched: ${p})`); + utils.verbose(err); + } + } + resources = unique(resources) + + /* convert to the expected API response structure */ + res.json(availableResponseStructure(resources)); + }; + +}; + +function availableResponseStructure(resources: (DatasetInfo | NarrativeInfo)[]): {datasets: {request: string, buildUrl?: string, secondTreeOptions?: string[]}[], narratives: {request: string}[]} { + const datasets = resources.filter((r): r is DatasetInfo => r.fileType==='dataset') + .map((r) => ({request: r.request, buildUrl: r.buildUrl, secondTreeOptions: r.secondTreeOptions})); + const narratives = resources.filter((r): r is NarrativeInfo => r.fileType==='narrative') + .map((r) => ({request: r.request})); + return {datasets, narratives}; +} + +/** + * First match wins + */ +function unique(resources: (DatasetInfo | NarrativeInfo)[]): (DatasetInfo | NarrativeInfo)[] { + const seen = {dataset: new Set(), narrative: new Set()}; + return resources.filter((r) => { + if (seen[r.fileType].has(r.request)) { + // utils.verbose(`Dropping duplicate ${r.request} from available`); // Too verbose! + return false; + } + seen[r.fileType].add(r.request); + return true; + }) +} diff --git a/cli/server/getDataset.js b/cli/server/getDataset.js deleted file mode 100644 index a7965bee3..000000000 --- a/cli/server/getDataset.js +++ /dev/null @@ -1,25 +0,0 @@ -const getAvailable = require("./getAvailable"); -const helpers = require("./getDatasetHelpers"); - -const setUpGetDatasetHandler = ({datasetsPath}) => { - return async (req, res) => { - try { - const availableDatasets = await getAvailable.getAvailableDatasets(datasetsPath); - const info = helpers.interpretRequest(req, datasetsPath); - const redirected = helpers.redirectIfDatapathMatchFound(res, info, availableDatasets); - if (redirected) return; - helpers.makeFetchAddresses(info, datasetsPath, availableDatasets); - await helpers.sendJson(res, info); - } catch (err) { - console.trace(err); - // Throw 404 when not available - const errorCode = err.message.endsWith("not in available datasets") ? 404 : 500; - return helpers.handleError(res, `couldn't fetch JSONs`, err.message, errorCode); - } - }; -}; - - -module.exports = { - setUpGetDatasetHandler -}; diff --git a/cli/server/getDataset.ts b/cli/server/getDataset.ts new file mode 100644 index 000000000..e9a2b5331 --- /dev/null +++ b/cli/server/getDataset.ts @@ -0,0 +1,104 @@ +import { promisify } from 'util'; +import path from "path"; +import fs from 'fs'; +import * as getAvailable from "./getAvailable.ts"; +import type { DatasetInfo } from "./getAvailable.ts"; +import * as helpers from "./getDatasetHelpers.ts"; +import * as utils from "../utils.ts"; + +const readdir = promisify(fs.readdir); + +/** + * Returns a route handler which responds to requests by serving the relevant JSON file + * from disk. + */ +export const setUpGetDatasetHandler = (dataPaths) => { + return async (req, res): Promise => { + + let requestInfo; + try { + requestInfo = helpers.interpretRequest(req); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + return helpers.handleError(res, msg, msg, 400); + } + + const matchResult = await matchDatasetFile(dataPaths, requestInfo); + + if (!Array.isArray(matchResult)) { + return await helpers.sendJson(res, { ...requestInfo, address: matchResult }); + } + + /* No perfect match - attempt to find a close one iff type=dataset */ + if (requestInfo.dataType==='dataset') { + const closeMatchRequest = helpers.closestMatch(requestInfo.parts, matchResult); + if (closeMatchRequest) { + utils.verbose(`Redirecting request to ${closeMatchRequest}`); + return res.redirect(`./getDataset?prefix=/${closeMatchRequest}`); + } + } + + /* fallthrough to 404 error */ + const msg = `no matching file for ${requestInfo.dataType || 'unknown data type'}`; + return helpers.handleError(res, msg, msg, 404); + }; +}; + + +/** Charon sidecar type query -> filename suffix */ +const SIDECARS = { + 'root-sequence': '_root-sequence.json', + 'tip-frequencies': '_tip-frequencies.json', + measurements: '_measurements.json', +} + +/** Walk through folders (dataPaths) looking for a file on disk which + * exactly matches (in terms of dataset name & type). We return the + * first exact match ("first match wins"), which mirrors `getAvailable` + * + * Return either: + * - string: the path to a v2 main dataset / sidecar JSON + * - object: the paths for (v1) meta/tree dataset JSONs + * - array: no matching file found. The array represents all available datasets + */ +async function matchDatasetFile(dataPaths, requestInfo): Promise { + /** + * Iterate through the dataPaths and return the first match we find + * (this "first match wins" approach mirrors that of `getAvailable`) + */ + const allAvailableDatasets = [] + /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */ + for (const [dir, dataTypes] of Object.entries(dataPaths) as [string, Set][]) { + if (!dataTypes.has('datasets')) continue; + let files: string[] = []; + try { + files = await readdir(dir); + } catch (err) { + utils.warn(`Error reading datasets from ${dir}: ${err instanceof Error ? err.message : err}`) + } + + if (requestInfo.dataType==='dataset') { + // list all available datasets -- includes v1 (meta+tree) & v2 ("main") dataset + const availableDatasets = getAvailable.getAvailableDatasets(dir, files); + allAvailableDatasets.push(...availableDatasets); + for (const resource of availableDatasets) { + if (resource.request === requestInfo.parts.join("/")) { + const address = resource.v2 ? + path.join(resource.dir, `${requestInfo.parts.join("_")}.json`) : + ({ + meta: path.join(resource.dir, `${requestInfo.parts.join("_")}_meta.json`), + tree: path.join(resource.dir, `${requestInfo.parts.join("_")}_tree.json`) + }); + return address; + } + } + } else if (Object.hasOwn(SIDECARS, requestInfo.dataType)) { + const expectedFileName = requestInfo.parts.join("_") + SIDECARS[requestInfo.dataType]; + const matches = files.filter((f) => f === expectedFileName); + if (matches.length) { + return path.join(dir, matches[0]); + } + } + } + return allAvailableDatasets; +} diff --git a/cli/server/getDatasetHelpers.js b/cli/server/getDatasetHelpers.ts similarity index 61% rename from cli/server/getDatasetHelpers.js rename to cli/server/getDatasetHelpers.ts index 82a5b537d..74fcd3da3 100644 --- a/cli/server/getDatasetHelpers.js +++ b/cli/server/getDatasetHelpers.ts @@ -8,34 +8,35 @@ * */ +import * as utils from "../utils.ts"; +import queryString from "query-string"; +import { convertFromV1 } from "./convertJsonSchemas.js"; +import fs from "fs"; -const utils = require("../utils"); -const queryString = require("query-string"); -const path = require("path"); -const convertFromV1 = require("./convertJsonSchemas").convertFromV1; -const fs = require("fs"); - -const handleError = (res, clientMsg, serverMsg="", code=500) => { +export const handleError = (res, clientMsg, serverMsg="", code=500): void => { utils.warn(`${clientMsg} -- ${serverMsg}`); return res.status(code).type("text/plain").send(clientMsg); }; -const splitPrefixIntoParts = (url) => url +const splitPrefixIntoParts = (url): string[] => url .replace(/^\//, '') .replace(/\/$/, '') .split("/"); /** - * Basic interpretation of an auspice request - * @returns {Object} keys: `dataType`, `parts` + * Parses the dataset URL query into an object with two keys 'parts' and 'dataType' + * + * @returns {Object} ret + * @returns {string[]} ret.parts is the dataset name spit on '/' character + * @returns {string} ret.dataType is the dataset type ('dataset', 'root-sequence' etc) */ -const interpretRequest = (req) => { - utils.log(`GET DATASET query received: ${req.url.split('?')[1]}`); +export const interpretRequest = (req): {parts: string[], dataType?: string} => { const query = queryString.parse(req.url.split('?')[1]); + utils.log(`[GET DATASET] ${Object.entries(query).map(([k,v]) => `${k}: '${v}'`).join(', ')}`); if (!query.prefix) throw new Error("'prefix' not defined in request"); const datanameParts = splitPrefixIntoParts(query.prefix); - const info = {parts: datanameParts}; + const info: {parts: string[], dataType?: string} = {parts: datanameParts}; /* query.type is used to indicate which sidecar file should be fetched, without it we fetch the "main" dataset JSON. See the `Dataset` constructor in `./src/actions/loadData.js` for which sidecars auspice may try to fetch */ @@ -46,66 +47,36 @@ const interpretRequest = (req) => { } else if (sidecars.includes(query.type)) { info.dataType = query.type; } else { - throw new Error(`Unknown file type '${query.type}' requested.`); + throw new Error(`Unknown dataset type '${query.type}' requested.`); } return info; }; /** - * Given a request, if the dataset does not exist then either - * (a) redirect to an appropriate dataset if possible & return `true` - * (b) throw. - * If the dataset existed then return `false` as no redirect necessary. + * Given a request for which there is no perfect match, return a close match + * if one exists else return false */ -const redirectIfDatapathMatchFound = (res, info, availableDatasets) => { +export const closestMatch = (requestedDatasetParts, availableDatasets): string | false => { let matchingDatasets = availableDatasets; let i; - const matchDatasetRequest = (d) => d.request.split("/")[i] === info.parts[i]; + const matchDatasetRequest = (d): boolean => d.request.split("/")[i] === requestedDatasetParts[i]; // Filter gradually by path fragment, starting from the root - for (i = 0; i < info.parts.length; i++) { + for (i = 0; i < requestedDatasetParts.length; i++) { const newMatchingDatasets = matchingDatasets.filter(matchDatasetRequest); if (!newMatchingDatasets.length) break; matchingDatasets = newMatchingDatasets; } - // If root fragment does cannot be matched, throw - if (!i) throw new Error(`${info.parts.join("/")} not in available datasets`); + // If root fragment cannot be matched return false (no closest match found) + if (!i) return false; // If best match is not equal to path requested, redirect - if (matchingDatasets[0].request !== info.parts.join("/")) { - res.redirect(`./getDataset?prefix=/${matchingDatasets[0].request}`); - return true; + if (matchingDatasets[0].request !== requestedDatasetParts.join("/")) { + return matchingDatasets[0].request; } return false; }; -/** - * sets info.address. - * if we need v1 datasets then `info.address` will be an object with `meta` - * and `tree` keys. Otherwise `info.address` will be a string of the fetch - * path. - * @sideEffect sets `info.address` {Object | string} - * @throws - */ -const makeFetchAddresses = (info, datasetsPath, availableDatasets) => { - if (info.dataType !== "dataset") { - info.address = path.join( - datasetsPath, - `${info.parts.join("_")}_${info.dataType}.json` - ); - } else { - const requestStr = info.parts.join("/"); // TO DO - const availableInfo = availableDatasets.filter((d) => d.request === requestStr)[0]; - if (availableInfo.v2) { - info.address = path.join(datasetsPath, `${info.parts.join("_")}.json`); - } else { - info.address = { - meta: path.join(datasetsPath, `${info.parts.join("_")}_meta.json`), - tree: path.join(datasetsPath, `${info.parts.join("_")}_tree.json`) - }; - } - } -}; -const sendJson = async (res, info) => { +export const sendJson = async (res, info): Promise => { if (typeof info.address === "string") { /* In general, JSONs are designed such that no server modifications are needed by the server. This allows us to read as a stream and @@ -140,7 +111,7 @@ const sendJson = async (res, info) => { * (c) differ from the `currentDatasetUrl` by only 1 part * Note: the "parts" of the URL are formed by splitting it on `"/"` */ -const findAvailableSecondTreeOptions = (currentDatasetUrl, availableDatasetUrls) => { +export const findAvailableSecondTreeOptions = (currentDatasetUrl, availableDatasetUrls): string[] => { const currentDatasetUrlArr = currentDatasetUrl.split('/'); const availableTangleTreeOptions = availableDatasetUrls.filter((datasetUrl) => { @@ -170,12 +141,3 @@ const findAvailableSecondTreeOptions = (currentDatasetUrl, availableDatasetUrls) return availableTangleTreeOptions; }; - -module.exports = { - interpretRequest, - redirectIfDatapathMatchFound, - makeFetchAddresses, - handleError, - sendJson, - findAvailableSecondTreeOptions -}; diff --git a/cli/server/getNarrative.js b/cli/server/getNarrative.js deleted file mode 100644 index e3112bdc6..000000000 --- a/cli/server/getNarrative.js +++ /dev/null @@ -1,40 +0,0 @@ -const queryString = require("query-string"); -const path = require("path"); -const fs = require("fs"); -const utils = require("../utils"); - -const setUpGetNarrativeHandler = ({narrativesPath}) => { - return async (req, res) => { - const query = queryString.parse(req.url.split('?')[1]); - const prefix = query.prefix || ""; - const filename = prefix - .replace(/.+narratives\//, "") // remove the URL up to (& including) "narratives/" - .replace(/\/$/, "") // remove ending slash - .replace(/\//g, "_") // change slashes to underscores - .concat(".md"); // add .md suffix - - const type = query.type ? query.type.toLowerCase() : null; - - const pathName = path.join(narrativesPath, filename); - utils.log("trying to access & parse local narrative file: " + pathName); - try { - const fileContents = fs.readFileSync(pathName, 'utf8'); - if (type === "md" || type === "markdown") { - // we could stream the response (as we sometimes do for getDataset) but narrative files are small - // so the expected time savings / server overhead is small. - res.send(fileContents); - } else { - throw new Error(`Unknown format requested: ${type}`); - } - utils.verbose("SUCCESS"); - } catch (err) { - const errorMessage = `Narratives couldn't be served -- ${err.message}`; - utils.warn(errorMessage); - res.status(500).type("text/plain").send(errorMessage); - } - }; -}; - -module.exports = { - setUpGetNarrativeHandler, -}; diff --git a/cli/server/getNarrative.ts b/cli/server/getNarrative.ts new file mode 100644 index 000000000..6fe7fd39c --- /dev/null +++ b/cli/server/getNarrative.ts @@ -0,0 +1,49 @@ +import queryString from "query-string"; +import { promisify } from 'util'; +import fs from "fs"; +import * as utils from "../utils.ts"; +import * as getAvailable from "./getAvailable.ts"; + +const readdir = promisify(fs.readdir); + + +export const setUpGetNarrativeHandler = (dataPaths) => { + return async (req, res): Promise => { + utils.log(`GET NARRATIVE request received: ${req.url}`); + const query = queryString.parse(req.url.split('?')[1]); + const type = query.type ? query.type.toLowerCase() : null; + if (!(type === "md" || type === "markdown")) { + return res.status(400).type("text/plain").send(`Unknown format requested: ${type}`); + } + if (!query.prefix || !(String(query.prefix).startsWith('narratives/') || String(query.prefix).startsWith('/narratives/'))) { + return res.status(400).type("text/plain").send(`Query must include a prefix starting with [/]narratives/`); + } + + const requestStr = query.prefix // we know this starts with [/]narratives/ + .replace(/^\//, "") // remove leading slash + .replace(/\/$/, "") // remove ending slash + + /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */ + for (const [p, dataTypes] of Object.entries(dataPaths) as [string, Set][]) { + if (!dataTypes.has('narratives')) continue; + try { + const files = await readdir(p); + const match = getAvailable.getAvailableNarratives(p, files) + .filter((d) => d.request===requestStr)[0] + if (match) { + // we could stream the response (as we sometimes do for getDataset) but narrative files are small + // so the expected time savings / server overhead is small. + return res.send(fs.readFileSync(match.fullPath, 'utf8')); + } + // else go scan the next dataPaths (directory) + } catch (err) { + const errorMessage = `Narratives couldn't be served -- ${err instanceof Error ? err.message : err}`; + utils.warn(errorMessage); + return res.status(500).type("text/plain").send(errorMessage); + } + } + const errorMessage = `No matching narrative found`; + utils.warn(errorMessage); + res.status(404).type("text/plain").send(errorMessage); + } +}; diff --git a/cli/server/processPaths.ts b/cli/server/processPaths.ts new file mode 100644 index 000000000..298c5c8a6 --- /dev/null +++ b/cli/server/processPaths.ts @@ -0,0 +1,78 @@ +import * as utils from "../utils.ts"; + +type AvailableResourceTypes = Set<'datasets' | 'narratives'>; + +/** + * Returns an object linking (absolute) paths to a set whose members indicate whether + * the path should be searched for dataset JSONs, narrative markdown files, or both. + * + * Historically the CLI took a single dataset path and a single narrative path, and + * that behaviour is still allowed here in addition to the newer approach of supplying + * any number of paths which can contain datasets and/or narratives. The two approaches + * are mutually exclusive and enforcement of this happens here. + */ +export function processPathArguments(args): Record { + + if (args.handlers) { + if (args.datasetDir || args.narrativeDir || args.path.length>0) { + utils.error("Can't provide data paths if you use custom handlers") + } + return {} + } + + if (args.gh_pages) { + if (args.handlers || args.datasetDir || args.narrativeDir || args.path.length > 0) { + utils.error("Can't provide data paths or handlers if you use --gh-pages") + } + return {} + } + + if (args.path.length === 0) { + if (!args.datasetDir && !args.narrativeDir) { + utils.error("Please provide paths to search for datasets and narratives: 'auspice ... PATH [PATH ...]'"); + } else if (!args.datasetDir) { // i.e. we do have --narrativeDir + utils.error("Please provide a '--datasetDir ' with datasets, or alternatively use the new positional-args invocation: 'auspice ... PATH [PATH ...]'"); + } + // loud deprecation warnings! + if (args.datasetDir) { + utils.warn(`[DEPRECATED] Instead of 'auspice ... --datasetDir ${args.datasetDir}' please use 'auspice ... ${args.datasetDir}' `) + } + if (args.narrativeDir) { + utils.warn(`[DEPRECATED] Instead of 'auspice ... --narrativeDir ${args.narrativeDir}' please use 'auspice ... ${args.narrativeDir}' `) + } + } else { // else: we do have positional path arguments + if (args.datasetDir || args.narrativeDir) { + utils.error("Incompatible arguments defining paths for datasets and/or narratives: " + + "You must either specify the paths as named arguments ('--datasetDir' and optionally '--narrativeDir') " + + "or specify one or more positional arguments."); + } + } + + + const dataPaths: Record = Object.fromEntries( + (args.path.length ? + args.path.map(utils.cleanUpPathname) : + [utils.cleanUpPathname(args.datasetDir)] + ).map((p: string) => [p, new Set(['datasets'])]) + ); + + const narrativePaths = args.path.length ? + args.path.map(utils.cleanUpPathname) : + args.narrativeDir ? + [utils.cleanUpPathname(args.narrativeDir)] : + []; // if no '--narrativeDir' then use auspice without narratives + + for (const narrativePath of narrativePaths) { + if (narrativePath in dataPaths) { + dataPaths[narrativePath].add('narratives'); + } else { + dataPaths[narrativePath] = new Set(['narratives']); + } + } + + if ("undefined" in dataPaths) { + utils.error("One or more provided paths does not exist") + } + + return dataPaths; +} diff --git a/cli/tsconfig.json b/cli/tsconfig.json new file mode 100644 index 000000000..08e517a38 --- /dev/null +++ b/cli/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "module": "nodenext", + "moduleResolution": "nodenext", + "allowImportingTsExtensions": true, + "types": ["node"], + "lib": ["ES2022"] + }, + "include": ["./**/*"], + "exclude": [] +} diff --git a/cli/utils.js b/cli/utils.ts similarity index 55% rename from cli/utils.js rename to cli/utils.ts index 4a82d0aa5..f686242a0 100644 --- a/cli/utils.js +++ b/cli/utils.ts @@ -1,29 +1,31 @@ /* eslint no-console: off */ -const fs = require('fs'); -const chalk = require('chalk'); -const path = require("path"); +import fs from 'fs'; +import _chalk from 'chalk'; +// chalk 2.x types aren't compatible with nodenext module resolution +/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */ +const chalk = _chalk as any as import('chalk').Chalk; +import path from 'path'; +import { fileURLToPath } from 'url'; -const verbose = (msg) => { +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +export const verbose = (msg): void => { if (global.AUSPICE_VERBOSE) { console.log(chalk.greenBright(`[verbose]\t${msg}`)); } }; -const log = (msg) => { +export const log = (msg): void => { console.log(chalk.blueBright(msg)); }; -const warn = (msg) => { +export const warn = (msg): void => { console.warn(chalk.yellowBright(`[warning]\t${msg}`)); }; -const error = (msg) => { +export const error = (msg): never => { console.error(chalk.redBright(`[error]\t${msg}`)); process.exit(2); }; -const isNpmGlobalInstall = () => { - return __dirname.indexOf("lib/node_modules/auspice") !== -1; -}; - -const cleanUpPathname = (pathIn) => { +export const cleanUpPathname = (pathIn): string | undefined => { let pathOut = pathIn; if (!pathOut.endsWith("/")) pathOut += "/"; if (pathOut.startsWith("~")) { @@ -31,34 +33,13 @@ const cleanUpPathname = (pathIn) => { } pathOut = path.resolve(pathOut); if (!fs.existsSync(pathOut)) { + warn(`path ${pathOut} doesn't exist`) return undefined; } return pathOut; }; -const getCurrentDirectoriesFor = (type) => { - const cwd = process.cwd(); - const folderName = type === "data" ? "auspice" : "narratives"; - if (fs.existsSync(path.join(cwd, folderName))) { - return cleanUpPathname(path.join(cwd, folderName)); - } - return cleanUpPathname(cwd); -}; - -const resolveLocalDirectory = (providedPath, isNarratives) => { - let localPath; - if (providedPath) { - localPath = cleanUpPathname(providedPath); - } else if (isNpmGlobalInstall()) { - localPath = getCurrentDirectoriesFor(isNarratives ? "narratives" : "data"); - } else { - // fallback to the auspice source directory - localPath = path.join(path.resolve(__dirname), "..", isNarratives ? "narratives" : "data"); - } - return localPath; -}; - -const readFilePromise = (fileName) => { +export const readFilePromise = (fileName): Promise => { return new Promise((resolve, reject) => { fs.readFile(fileName, 'utf8', (err, data) => { if (err) { @@ -76,7 +57,7 @@ const readFilePromise = (fileName) => { /* write an index.html file to the current working directory * Optionally set the hrefs for local files to relative links (needed for github pages) */ -const exportIndexDotHtml = ({relative=false}) => { +export const exportIndexDotHtml = ({relative=false}): void => { if (path.resolve(__dirname, "..") === process.cwd()) { warn("Cannot export index.html to the auspice source directory."); return; @@ -91,13 +72,3 @@ const exportIndexDotHtml = ({relative=false}) => { } fs.writeFileSync(outputFilePath, data); }; - -module.exports = { - verbose, - log, - warn, - error, - resolveLocalDirectory, - readFilePromise, - exportIndexDotHtml -}; diff --git a/cli/view.js b/cli/view.ts similarity index 61% rename from cli/view.js rename to cli/view.ts index 3fa582572..062d22d0b 100644 --- a/cli/view.js +++ b/cli/view.ts @@ -1,18 +1,27 @@ /* eslint no-console: off */ -/* eslint global-require: off */ - -const path = require("path"); -const fs = require("fs"); -const express = require("express"); -const expressStaticGzip = require("express-static-gzip"); -const compression = require('compression'); -const nakedRedirect = require('express-naked-redirect'); -const utils = require("./utils"); -const version = require('../src/version').version; -const chalk = require('chalk'); +/* eslint-disable @typescript-eslint/explicit-function-return-type, @typescript-eslint/consistent-type-assertions */ + +import path from "path"; +import fs from "fs"; +import { fileURLToPath } from 'url'; +import { createRequire } from 'module'; +import express from "express"; +import expressStaticGzip from "express-static-gzip"; +import compression from 'compression'; +import nakedRedirect from 'express-naked-redirect'; +import * as utils from "./utils.ts"; +import { version } from '../src/version.js'; +import _chalk from 'chalk'; +const chalk = _chalk as any as import('chalk').Chalk; +import { processPathArguments } from "./server/processPaths.ts"; +import { setUpGetAvailableHandler } from "./server/getAvailable.ts"; +import { setUpGetDatasetHandler } from "./server/getDataset.ts"; +import { setUpGetNarrativeHandler } from "./server/getNarrative.ts"; + +const require = createRequire(import.meta.url); +const __dirname = path.dirname(fileURLToPath(import.meta.url)); const SUPPRESS = require('argparse').Const.SUPPRESS; - const addParser = (parser) => { const description = `Launch a local server to view locally available datasets & narratives. The handlers for (auspice) client requests can be overridden here (see documentation for more details). @@ -22,13 +31,24 @@ const addParser = (parser) => { const subparser = parser.addParser('view', {addHelp: true, description}); subparser.addArgument('--verbose', {action: "storeTrue", help: "Print more verbose logging messages."}); subparser.addArgument('--handlers', {action: "store", metavar: "JS", help: "Overwrite the provided server handlers for client requests. See documentation for more details."}); - subparser.addArgument('--datasetDir', {metavar: "PATH", help: "Directory where datasets (JSONs) are sourced. This is ignored if you define custom handlers."}); - subparser.addArgument('--narrativeDir', {metavar: "PATH", help: "Directory where narratives (Markdown files) are sourced. This is ignored if you define custom handlers."}); + addDatasetNarrativePathArgs(subparser) subparser.addArgument('--customBuildOnly', {action: "storeTrue", help: "Error if a custom build is not found."}); /* there are some options which we deliberately do not document via `--help`. */ subparser.addArgument('--gh-pages', {action: "store", help: SUPPRESS}); /* related to the "static-site-generation" or "github-pages" */ }; +function addDatasetNarrativePathArgs(parser) { + parser.addArgument('--datasetDir', { + metavar: "PATH", + help: "[DEPRECATED] Directory where datasets (JSONs) are sourced. This cannot be used with positional arg(s) or custom handlers." + }); + parser.addArgument('--narrativeDir', { + metavar: "PATH", + help: "[DEPRECATED] Directory where narratives (Markdown files) are sourced. This cannot be used with positional arg(s) or custom handlers." + }); + parser.addArgument('path', {action: "store", nargs: '*', help: "Directories where datasets and/or narratives are stored"}); +} + const serveRelativeFilepaths = ({app, dir}) => { app.get("*.json", (req, res) => { const filePath = path.join(dir, req.originalUrl); @@ -38,42 +58,29 @@ const serveRelativeFilepaths = ({app, dir}) => { return `JSON requests will be served relative to ${dir}.`; }; -const loadAndAddHandlers = ({app, handlersArg, datasetDir, narrativeDir}) => { - /* load server handlers, either from provided path or the defaults */ - const handlers = {}; - let datasetsPath, narrativesPath; - if (handlersArg) { - const handlersPath = path.resolve(handlersArg); - utils.verbose(`Loading handlers from ${handlersPath}`); - const inject = require(handlersPath); - handlers.getAvailable = inject.getAvailable; - handlers.getDataset = inject.getDataset; - handlers.getNarrative = inject.getNarrative; - } else { - datasetsPath = utils.resolveLocalDirectory(datasetDir, false); - narrativesPath = utils.resolveLocalDirectory(narrativeDir, true); - handlers.getAvailable = require("./server/getAvailable") - .setUpGetAvailableHandler({datasetsPath, narrativesPath}); - handlers.getDataset = require("./server/getDataset") - .setUpGetDatasetHandler({datasetsPath}); - handlers.getNarrative = require("./server/getNarrative") - .setUpGetNarrativeHandler({narrativesPath}); - } - - /* apply handlers */ - app.get("/charon/getAvailable", handlers.getAvailable); - app.get("/charon/getDataset", handlers.getDataset); - app.get("/charon/getNarrative", handlers.getNarrative); - app.get("/charon*", (req, res) => { - const errorMessage = "Query unhandled -- " + req.originalUrl; - utils.warn(errorMessage); - return res.status(500).type("text/plain").send(errorMessage); - }); +async function customRouteHandlers(app, handlersPath) { + utils.verbose(`Loading handlers from ${handlersPath}`); + const customCode = await import(handlersPath); + app.get("/charon/getAvailable", customCode.getAvailable); + app.get("/charon/getDataset", customCode.getDataset); + app.get("/charon/getNarrative", customCode.getNarrative); + return `Custom server handlers provided.`; +} - return handlersArg ? - `Custom server handlers provided.` : - `Looking for datasets in ${datasetsPath}\nLooking for narratives in ${narrativesPath}`; -}; +/** + * Adds route handlers for the three canonical charon routes to the *app* + */ +function defaultRouteHandlers({app, dataPaths}) { + app.get("/charon/getAvailable", setUpGetAvailableHandler(dataPaths)); + app.get("/charon/getDataset", setUpGetDatasetHandler(dataPaths)); + app.get("/charon/getNarrative", setUpGetNarrativeHandler(dataPaths)); + const sep = "\n - " + return 'Looking for datasets & narratives in the following paths,\n' + + '(if there are multiple matches then the first one will be used)' + sep + + (Object.entries(dataPaths) as [string, Set][]) + .map(([p, dataTypes]) => `${p} (${Array.from(dataTypes).join(', ')})`) + .join(sep); +} const getAuspiceBuild = (customBuildOnly) => { const cwd = path.resolve(process.cwd()); @@ -108,7 +115,9 @@ function hasAuspiceBuild(directory) { ) } -const run = (args) => { +const run = async (args) => { + const dataPaths = processPathArguments(args) + /* Basic server set up */ const app = express(); app.set('port', process.env.PORT || 4000); @@ -119,7 +128,7 @@ const run = (args) => { const auspiceBuild = getAuspiceBuild(args.customBuildOnly); utils.verbose(`Serving favicon from "${auspiceBuild.baseDir}"`); utils.verbose(`Serving index and built javascript from "${auspiceBuild.distDir}"`); - app.get("/favicon.png", (req, res) => {res.sendFile(path.join(auspiceBuild.baseDir, "favicon.png"));}); + app.get("/favicon.png", (_req, res) => {res.sendFile(path.join(auspiceBuild.baseDir, "favicon.png"));}); app.use("/dist", expressStaticGzip(auspiceBuild.distDir, { maxAge: '30d', enableBrotli: true, @@ -128,12 +137,20 @@ const run = (args) => { let handlerMsg = ""; if (args.gh_pages) { handlerMsg = serveRelativeFilepaths({app, dir: path.resolve(args.gh_pages)}); + } else if (args.handlers) { + handlerMsg = await customRouteHandlers(app, path.resolve(args.handlers)); } else { - handlerMsg = loadAndAddHandlers({app, handlersArg: args.handlers, datasetDir: args.datasetDir, narrativeDir: args.narrativeDir}); + handlerMsg = defaultRouteHandlers({app, dataPaths}); } + /* Handle unhandled API (charon) routes */ + app.get("/charon*", (req, res) => { + const errorMessage = "Query unhandled -- " + req.originalUrl; + utils.warn(errorMessage); + return res.status(500).type("text/plain").send(errorMessage); + }); /* this must be the last "get" handler, else the "*" swallows all other requests */ - app.get("*", (req, res) => { + app.get("*", (_req, res) => { res.sendFile(path.join(auspiceBuild.baseDir, "dist/index.html"), {headers: {"Cache-Control": "no-cache, no-store, must-revalidate"}}); }); @@ -155,8 +172,8 @@ const run = (args) => { utils.error(`Host ${app.get('host')} is not a valid address. The server could not be started. If you did not provide a HOST environment variable when starting the app you may have HOST already set in your system. You can either change that variable, or override HOST when starting the app. Example commands to fix: - ${chalk.yellow('HOST="localhost" auspice view')} - ${chalk.yellow('HOST="localhost" npm run view')}`); + ${chalk.yellow('HOST="localhost" auspice view ')} + ${chalk.yellow('HOST="localhost" npm run view -- ')}`); } utils.error(`Uncaught error in app.listen(). Code: ${error.code}`); @@ -164,9 +181,12 @@ const run = (args) => { }; -module.exports = { +export { addParser, run, - loadAndAddHandlers, + addDatasetNarrativePathArgs, + processPathArguments, + defaultRouteHandlers, + customRouteHandlers, serveRelativeFilepaths }; diff --git a/docs/introduction/how-to-run.rst b/docs/introduction/how-to-run.rst index 808acd005..c96cd189e 100644 --- a/docs/introduction/how-to-run.rst +++ b/docs/introduction/how-to-run.rst @@ -8,6 +8,10 @@ Auspice is run as a command line program -- ``auspice`` -- with various subcomma - ``auspice develop --help`` - ``auspice convert --help`` +.. note:: + + If you just want to visualise your JSON datasets (or a newick tree) in Auspice, you can use the stand-alone web-app `auspice.us `__ which allows drag-and-drop of files onto the browser. + How to Get an Example Dataset Up and Running -------------------------------------------- @@ -22,7 +26,7 @@ And then run ``auspice`` via: .. code:: bash - auspice view --datasetDir datasets + auspice view datasets This will allow you to run Auspice locally (i.e. from your computer) and view the dataset which is behind `nextstrain.org/zika `__. :ref:`See below ` for how to download all of the data available on `nextstrain.org `__. @@ -31,15 +35,9 @@ To analyse your own data, please see the tutorials on the `nextstrain docs `. @@ -95,9 +93,6 @@ Datasets JSONs include: * Frequency JSON (optional) - `example here `__ * Generates the frequencies panel, e.g. on `nextstrain.org/flu `__. -.. note:: - - We are working on ways to make datasets in Newick / Nexus formats available. You can see an early prototype of this at `auspice-us.herokuapp.com `__ where you can drop on Newick (and CSV) files. Using BEAST trees is possible, but you have to use Augur to convert them first. .. note:: @@ -123,12 +118,3 @@ Narratives For narratives, please see `Writing a Narrative `__ for a description of the file format. .. _introduction-obtaining-a-set-of-input-files: - -Obtaining a Set of Input Files ------------------------------- - -If you'd like to download the dataset JSONs which are behind the core-datasets shown on `nextstrain.org `__, then you can run `this script `__ which will create a ``./data`` directory for you. - -The nextstrain-maintained narratives are stored in the `nextstrain/narratives github repo `__. You can obtain these by cloning that repo. - -You can then run ``auspice view --datasetDir data --narrativeDir `` to visualise all of the `nextstrain.org `__ datasets locally. diff --git a/docs/server/api.rst b/docs/server/api.rst index 9201a7fb8..36adf4afe 100644 --- a/docs/server/api.rst +++ b/docs/server/api.rst @@ -71,27 +71,12 @@ A ``204`` reponse will cause Auspice to show its splash page listing the availab **URL query arguments:** - ``prefix`` (required) - the pathname of the requesting page in Auspice. Use this to determine which narrative to return. -- ``type`` (optional) - the format of the data returned (see below for more information). Current valid values are "json" and "md". If no type is specified the server will use "json" as a default (for backwards compatibility reasons). Requests to this API from the Auspice client are made with ``type=md``. +- ``type`` (required) - this must be ``type=md`` for historical reasons **Response (on success):** -The response depends on the ``type`` specified in the query. +The narrative file is sent to the client (unmodified, to be parsed client-side). -If a markdown format is requested, then the narrative file is sent to the client unmodified to be parsed on the client. - -If a JSON is requested then the narrative file is parsed into JSON format by the server. For Auspice versions prior to v2.18 this was the only expected behavior. The transformation from markdown (i.e. the narrative file itself) to JSON is via the ``parseNarrativeFile()`` function (see below for how this is exported from Auspice for use in other servers). Here, roughly, is the code we use in the auspice server for this transformation: - -.. code:: js - - const fileContents = fs.readFileSync(pathName, 'utf8'); - if (type === "json") { - const blocks = parseNarrative(fileContents); - res.send(JSON.stringify(blocks).replace(/`__. - -``parseNarrativeFile`` -~~~~~~~~~~~~~~~~~~~~~~ - -.. warning:: - - This function is deprecated as of vXXX. You can now send the untransformed contents of the narrative file (markdown) for client-side parsing. See :ref:`above ` for more details. - -**Signature:** - -.. code:: js - - const blocks = parseNarrativeFile(fileContents); - -where ``fileContents`` is a string representation of the narrative Markdown file. - -**Returns:** - -An array of objects, each entry representing a different narrative "block" or "page". Each object has properties - -* ``__html`` -- the HTML to render in the sidebar to form the narrative -* ``dataset`` -- the dataset associated with this block -* ``query`` -- the query associated with this block diff --git a/docs/server/overview.rst b/docs/server/overview.rst index 401262945..464fa4083 100644 --- a/docs/server/overview.rst +++ b/docs/server/overview.rst @@ -73,6 +73,6 @@ It should be simple to deploy a custom version of auspice to any server, but we Deploying to Heroku is straightforward, but there are a few points to note: 1. You must set the config var ``HOST=0.0.0.0`` for the app. -2. You will need to either create a ``Procfile`` or a ``npm run start`` script which calls ``auspice view`` (or ``npx auspice view`` depending on how you implement auspice). -3. Make sure the datasets to be served are either (a) included in your git repo or (b) downloaded by the heroku build pipeline. `We use option (b) `_ by specifying a npm script called ``heroku-postbuild``. - +2. You will need to either create a ``Procfile`` or a ``npm run start`` script which calls ``auspice view`` (or some equivalent). +3. Datasets & narratives can be sourced by providing positional argument(s) to ``auspice view`` or by writing your own request handlers for the server. + If you need to download datasets at heroku-buildtime you can use the ``heroku-postbuild`` npm script. diff --git a/index.js b/index.js index fe9962920..be52de20b 100644 --- a/index.js +++ b/index.js @@ -1,16 +1,11 @@ /** This file exports the functions / objects available to node packages - * which import auspice (e.g. via `const auspice = require("auspice")`) + * which import auspice (e.g. via `import { convertFromV1 } from "auspice"`) * * These are intended to be "helper functions" for those wishing to write * their own auspice server, i.e. a server which handles the GET * requests originating from a auspice client. */ +import { convertFromV1 } from "./cli/server/convertJsonSchemas.js"; -const convertFromV1 = require("./cli/server/convertJsonSchemas").convertFromV1; -const parseNarrativeFile = require("./cli/server/getNarrative").parseNarrative; - -module.exports = { - convertFromV1, - parseNarrativeFile -}; +export { convertFromV1 }; diff --git a/package-lock.json b/package-lock.json index 119203516..1d3eadea3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -115,11 +115,10 @@ "@types/lodash": "^4.17.13", "@types/node": "^18.15.11", "@types/webpack-env": "^1.18.2", - "@typescript-eslint/eslint-plugin": "^5.57.0", - "@typescript-eslint/parser": "^5.57.0", - "chai": "^4.1.2", - "chai-http": "^4.0.0", - "eslint": "^8.39.0", + "@typescript-eslint/eslint-plugin": "^8.60.1", + "@typescript-eslint/parser": "^8.60.1", + "babel-plugin-transform-import-meta": "^2.3.3", + "eslint": "^8.57.1", "eslint-plugin-react": "^7.2.1", "eslint-plugin-react-hooks": "^4.0.1", "jest": "^29.5.0", @@ -130,8 +129,8 @@ "typescript": "~5.9" }, "engines": { - "node": "^20 || ^22 || ^24", - "npm": "^9 || ^10 || ^11" + "node": "^24 || ^26", + "npm": "^11" } }, "node_modules/@asamuzakjp/css-color": { @@ -2101,50 +2100,44 @@ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, "funding": { "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/regexpp": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz", - "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", - "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "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", - "espree": "^9.5.1", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -2163,13 +2156,15 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "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" }, @@ -2180,11 +2175,31 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -2198,6 +2213,7 @@ "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" }, @@ -2206,10 +2222,11 @@ } }, "node_modules/@eslint/js": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", - "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -2229,13 +2246,15 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -2256,10 +2275,12 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", @@ -6533,18 +6554,6 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/chai": { - "version": "4.2.22", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.22.tgz", - "integrity": "sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ==", - "dev": true - }, - "node_modules/@types/cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", - "dev": true - }, "node_modules/@types/d3-array": { "version": "2.12.7", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-2.12.7.tgz", @@ -7166,12 +7175,6 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, - "node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", - "dev": true - }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7184,16 +7187,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/superagent": { - "version": "3.8.7", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-3.8.7.tgz", - "integrity": "sha512-9KhCkyXv268A2nZ1Wvu7rQWM+BmdYUVkycFeNnYrUL5Zwu7o8wPQ3wBfW59dDP+wuoxw0ww8YKgTNv8j/cgscA==", - "dev": true, - "dependencies": { - "@types/cookiejar": "*", - "@types/node": "*" - } - }, "node_modules/@types/tapable": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", @@ -7283,130 +7276,149 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.2.tgz", - "integrity": "sha512-yVrXupeHjRxLDcPKL10sGQ/QlVrA8J5IYOEWVqk0lJaSZP7X5DfnP7Ns3cc74/blmbipQ1htFNVGsHX6wsYm0A==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz", + "integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.2", - "@typescript-eslint/type-utils": "5.59.2", - "@typescript-eslint/utils": "5.59.2", - "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/type-utils": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.60.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "node_modules/@typescript-eslint/parser": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz", + "integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "debug": "^4.4.3" }, "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/parser": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.2.tgz", - "integrity": "sha512-uq0sKyw6ao1iFOZZGk9F8Nro/8+gfB5ezl1cA06SrqbgJAt0SRoFhb9pXaHvkrxUpZaoLxt8KlovHNk8Gp6/HQ==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz", + "integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "5.59.2", - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/typescript-estree": "5.59.2", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.60.1", + "@typescript-eslint/types": "^8.60.1", + "debug": "^4.4.3" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.2.tgz", - "integrity": "sha512-dB1v7ROySwQWKqQ8rEWcdbTsFjh2G0vn8KUyvTXdPoyzSL6lLGkiXEV5CvpJsEe9xIdKV+8Zqb7wif2issoOFA==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz", + "integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/visitor-keys": "5.59.2" + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz", + "integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.2.tgz", - "integrity": "sha512-b1LS2phBOsEy/T381bxkkywfQXkV1dWda/z0PhnIy3bC5+rQWQDS7fk9CSpcXBccPY27Z6vBEuaPBCKCgYezyQ==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz", + "integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.2", - "@typescript-eslint/utils": "5.59.2", - "debug": "^4.3.4", - "tsutils": "^3.21.0" + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.2.tgz", - "integrity": "sha512-LbJ/HqoVs2XTGq5shkiKaNTuVv5tTejdHgfdjqRUGdYhjW1crm/M7og2jhVskMt8/4wS3T1+PfFvL1K3wqYj4w==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz", + "integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -7414,65 +7426,76 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.2.tgz", - "integrity": "sha512-+j4SmbwVmZsQ9jEyBMgpuBD0rKwi9RxRpjX71Brr73RsYnEr3Lt5QZ624Bxphp8HUkSKfqGnPJp1kA5nl0Sh7Q==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz", + "integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/visitor-keys": "5.59.2", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "@typescript-eslint/project-service": "8.60.1", + "@typescript-eslint/tsconfig-utils": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree/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==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": "18 || 20 || >=22" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, + "license": "MIT", "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=10" + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", "dev": true, "license": "ISC", "bin": { @@ -7483,55 +7506,41 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.2.tgz", - "integrity": "sha512-kSuF6/77TZzyGPhGO4uVp+f0SBoYxCDf+lW3GKhtKru/L8k/Hd7NFQxyWUeY7Z/KGB2C6Fe3yf2vVi4V9TsCSQ==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz", + "integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.2", - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/typescript-estree": "5.59.2", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.2.tgz", - "integrity": "sha512-EEpsO8m3RASrKAHI9jpavNv9NlEUebV4qmF1OWxSTtKSFBpC1NCmWazDQHFivRf0O1DV11BA645yrLEVQ0/Lig==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz", + "integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.59.2", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "8.60.1", + "eslint-visitor-keys": "^5.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -7539,17 +7548,25 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "dev": true, + "license": "ISC" + }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -7749,6 +7766,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -8036,26 +8054,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -8312,6 +8315,20 @@ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" }, + "node_modules/babel-plugin-transform-import-meta": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-import-meta/-/babel-plugin-transform-import-meta-2.3.3.tgz", + "integrity": "sha512-bbh30qz1m6ZU1ybJoNOhA2zaDvmeXMnGNBMVMDOJ1Fni4+wMBoy/j7MTRVmqAUCIcy54/rEnr9VEBsfcgbpm3Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/template": "^7.25.9", + "tslib": "^2.8.1" + }, + "peerDependencies": { + "@babel/core": "^7.10.0" + } + }, "node_modules/babel-preset-current-node-syntax": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", @@ -8740,62 +8757,6 @@ "node": ">=0.8" } }, - "node_modules/chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chai-http": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/chai-http/-/chai-http-4.3.0.tgz", - "integrity": "sha512-zFTxlN7HLMv+7+SPXZdkd5wUlK+KxH6Q7bIEMiEx0FK3zuuMqL7cwICAQ0V1+yYRozBburYuxN1qZstgHpFZQg==", - "dev": true, - "dependencies": { - "@types/chai": "4", - "@types/superagent": "^3.8.3", - "cookiejar": "^2.1.1", - "is-ip": "^2.0.0", - "methods": "^1.1.2", - "qs": "^6.5.1", - "superagent": "^3.7.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chai-http/node_modules/ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/chai-http/node_modules/is-ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-2.0.0.tgz", - "integrity": "sha1-aO6gfooKCpTC0IDdZ0xzGrKkYas=", - "dev": true, - "dependencies": { - "ip-regex": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/chain-function": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.1.tgz", @@ -8823,15 +8784,6 @@ "node": ">=10" } }, - "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/check-types": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz", @@ -8992,18 +8944,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -9017,12 +8957,6 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -9183,12 +9117,6 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, - "node_modules/cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "dev": true - }, "node_modules/copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", @@ -9600,9 +9528,10 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -9627,23 +9556,12 @@ "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", "dev": true }, - "node_modules/deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=0.12" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", @@ -9686,15 +9604,6 @@ "node": ">=6" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -9742,18 +9651,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -10138,27 +10035,30 @@ } }, "node_modules/eslint": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", - "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.39.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -10166,22 +10066,19 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -10293,6 +10190,19 @@ "node": ">=8.0.0" } }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -10370,10 +10280,11 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -10385,23 +10296,12 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/eslint/node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -10443,7 +10343,17 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" } }, "node_modules/eslint/node_modules/is-path-inside": { @@ -10468,19 +10378,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/eslint/node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/eslint/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -10496,23 +10393,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/eslint/node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -10543,15 +10423,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/eslint/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -10576,18 +10447,6 @@ "node": ">=8" } }, - "node_modules/eslint/node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/eslint/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -10601,27 +10460,16 @@ } }, "node_modules/espree": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "eslint-visitor-keys": "^3.4.1" }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -10883,45 +10731,11 @@ } ] }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, - "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "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, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -11112,55 +10926,6 @@ "is-callable": "^1.1.3" } }, - "node_modules/form-data": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", - "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.35", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/form-data/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/formidable": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", - "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", - "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", - "dev": true, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -11278,16 +11043,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -11469,11 +11224,12 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" }, "node_modules/gzip-size": { "version": "5.1.1", @@ -11801,10 +11557,11 @@ "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" }, "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -14234,16 +13991,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -14423,6 +14170,20 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -14644,15 +14405,6 @@ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, - "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, - "engines": { - "node": ">= 8" - } - }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -14837,12 +14589,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -15157,6 +14903,24 @@ "opener": "bin/opener-bin.js" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/outer-product": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/outer-product/-/outer-product-0.0.4.tgz", @@ -15349,15 +15113,6 @@ "node": ">=8" } }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -15551,6 +15306,16 @@ "node": ">=0.10.0" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/pretty-error": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", @@ -16908,37 +16673,6 @@ "stylis": "^3.5.0" } }, - "node_modules/superagent": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", - "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", - "deprecated": "Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at .", - "dev": true, - "dependencies": { - "component-emitter": "^1.2.0", - "cookiejar": "^2.1.0", - "debug": "^3.1.0", - "extend": "^3.0.0", - "form-data": "^2.3.1", - "formidable": "^1.2.0", - "methods": "^1.1.1", - "mime": "^1.4.1", - "qs": "^6.5.1", - "readable-stream": "^2.3.5" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/superagent/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -17172,6 +16906,54 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tldts": { "version": "6.1.86", "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", @@ -17239,6 +17021,19 @@ "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -17297,31 +17092,24 @@ } }, "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^1.8.1" + "prelude-ls": "^1.2.1" }, "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + "node": ">= 0.8.0" } }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -19530,37 +19318,29 @@ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, "@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "requires": { - "eslint-visitor-keys": "^3.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", - "dev": true - } + "eslint-visitor-keys": "^3.4.3" } }, "@eslint-community/regexpp": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz", - "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true }, "@eslint/eslintrc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", - "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.1", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -19576,18 +19356,24 @@ "dev": true }, "globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "requires": { "type-fest": "^0.20.2" } }, - "js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true + }, + "js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", "dev": true, "requires": { "argparse": "^2.0.1" @@ -19602,9 +19388,9 @@ } }, "@eslint/js": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", - "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true }, "@hot-loader/react-dom": { @@ -19619,13 +19405,13 @@ } }, "@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", "minimatch": "^3.0.5" } }, @@ -19636,9 +19422,9 @@ "dev": true }, "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, "@istanbuljs/load-nyc-config": { @@ -21565,18 +21351,6 @@ "@babel/types": "^7.20.7" } }, - "@types/chai": { - "version": "4.2.22", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.22.tgz", - "integrity": "sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ==", - "dev": true - }, - "@types/cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", - "dev": true - }, "@types/d3-array": { "version": "2.12.7", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-2.12.7.tgz", @@ -22098,12 +21872,6 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, - "@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", - "dev": true - }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -22115,16 +21883,6 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, - "@types/superagent": { - "version": "3.8.7", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-3.8.7.tgz", - "integrity": "sha512-9KhCkyXv268A2nZ1Wvu7rQWM+BmdYUVkycFeNnYrUL5Zwu7o8wPQ3wBfW59dDP+wuoxw0ww8YKgTNv8j/cgscA==", - "dev": true, - "requires": { - "@types/cookiejar": "*", - "@types/node": "*" - } - }, "@types/tapable": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", @@ -22210,156 +21968,166 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.2.tgz", - "integrity": "sha512-yVrXupeHjRxLDcPKL10sGQ/QlVrA8J5IYOEWVqk0lJaSZP7X5DfnP7Ns3cc74/blmbipQ1htFNVGsHX6wsYm0A==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz", + "integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==", "dev": true, "requires": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.2", - "@typescript-eslint/type-utils": "5.59.2", - "@typescript-eslint/utils": "5.59.2", - "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true - } + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/type-utils": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" } }, "@typescript-eslint/parser": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.2.tgz", - "integrity": "sha512-uq0sKyw6ao1iFOZZGk9F8Nro/8+gfB5ezl1cA06SrqbgJAt0SRoFhb9pXaHvkrxUpZaoLxt8KlovHNk8Gp6/HQ==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz", + "integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.59.2", - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/typescript-estree": "5.59.2", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "debug": "^4.4.3" + } + }, + "@typescript-eslint/project-service": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz", + "integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==", + "dev": true, + "requires": { + "@typescript-eslint/tsconfig-utils": "^8.60.1", + "@typescript-eslint/types": "^8.60.1", + "debug": "^4.4.3" } }, "@typescript-eslint/scope-manager": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.2.tgz", - "integrity": "sha512-dB1v7ROySwQWKqQ8rEWcdbTsFjh2G0vn8KUyvTXdPoyzSL6lLGkiXEV5CvpJsEe9xIdKV+8Zqb7wif2issoOFA==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz", + "integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==", "dev": true, "requires": { - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/visitor-keys": "5.59.2" + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1" } }, + "@typescript-eslint/tsconfig-utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz", + "integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==", + "dev": true, + "requires": {} + }, "@typescript-eslint/type-utils": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.2.tgz", - "integrity": "sha512-b1LS2phBOsEy/T381bxkkywfQXkV1dWda/z0PhnIy3bC5+rQWQDS7fk9CSpcXBccPY27Z6vBEuaPBCKCgYezyQ==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz", + "integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.59.2", - "@typescript-eslint/utils": "5.59.2", - "debug": "^4.3.4", - "tsutils": "^3.21.0" + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" } }, "@typescript-eslint/types": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.2.tgz", - "integrity": "sha512-LbJ/HqoVs2XTGq5shkiKaNTuVv5tTejdHgfdjqRUGdYhjW1crm/M7og2jhVskMt8/4wS3T1+PfFvL1K3wqYj4w==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz", + "integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.2.tgz", - "integrity": "sha512-+j4SmbwVmZsQ9jEyBMgpuBD0rKwi9RxRpjX71Brr73RsYnEr3Lt5QZ624Bxphp8HUkSKfqGnPJp1kA5nl0Sh7Q==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz", + "integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==", "dev": true, "requires": { - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/visitor-keys": "5.59.2", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "@typescript-eslint/project-service": "8.60.1", + "@typescript-eslint/tsconfig-utils": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "dependencies": { - "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==", + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "requires": { + "balanced-match": "^4.0.2" + } + }, + "minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "brace-expansion": "^5.0.5" } }, "semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", "dev": true } } }, "@typescript-eslint/utils": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.2.tgz", - "integrity": "sha512-kSuF6/77TZzyGPhGO4uVp+f0SBoYxCDf+lW3GKhtKru/L8k/Hd7NFQxyWUeY7Z/KGB2C6Fe3yf2vVi4V9TsCSQ==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz", + "integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==", "dev": true, "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.2", - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/typescript-estree": "5.59.2", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "dependencies": { - "semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true - } + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1" } }, "@typescript-eslint/visitor-keys": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.2.tgz", - "integrity": "sha512-EEpsO8m3RASrKAHI9jpavNv9NlEUebV4qmF1OWxSTtKSFBpC1NCmWazDQHFivRf0O1DV11BA645yrLEVQ0/Lig==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz", + "integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==", "dev": true, "requires": { - "@typescript-eslint/types": "5.59.2", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "8.60.1", + "eslint-visitor-keys": "^5.0.0" }, "dependencies": { "eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true } } }, + "@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "dev": true + }, "@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -22724,23 +22492,11 @@ "is-string": "^1.0.7" } }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, "async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, "available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -22940,6 +22696,16 @@ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" }, + "babel-plugin-transform-import-meta": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-import-meta/-/babel-plugin-transform-import-meta-2.3.3.tgz", + "integrity": "sha512-bbh30qz1m6ZU1ybJoNOhA2zaDvmeXMnGNBMVMDOJ1Fni4+wMBoy/j7MTRVmqAUCIcy54/rEnr9VEBsfcgbpm3Q==", + "dev": true, + "requires": { + "@babel/template": "^7.25.9", + "tslib": "^2.8.1" + } + }, "babel-preset-current-node-syntax": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", @@ -23231,52 +22997,6 @@ } } }, - "chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, - "chai-http": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/chai-http/-/chai-http-4.3.0.tgz", - "integrity": "sha512-zFTxlN7HLMv+7+SPXZdkd5wUlK+KxH6Q7bIEMiEx0FK3zuuMqL7cwICAQ0V1+yYRozBburYuxN1qZstgHpFZQg==", - "dev": true, - "requires": { - "@types/chai": "4", - "@types/superagent": "^3.8.3", - "cookiejar": "^2.1.1", - "is-ip": "^2.0.0", - "methods": "^1.1.2", - "qs": "^6.5.1", - "superagent": "^3.7.0" - }, - "dependencies": { - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true - }, - "is-ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-2.0.0.tgz", - "integrity": "sha1-aO6gfooKCpTC0IDdZ0xzGrKkYas=", - "dev": true, - "requires": { - "ip-regex": "^2.0.0" - } - } - } - }, "chain-function": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.1.tgz", @@ -23298,12 +23018,6 @@ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, "check-types": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz", @@ -23422,15 +23136,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, "commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -23441,12 +23146,6 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, "compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -23550,12 +23249,6 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, - "cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "dev": true - }, "copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", @@ -23904,9 +23597,9 @@ } }, "debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "requires": { "ms": "^2.1.3" } @@ -23923,15 +23616,6 @@ "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", "dev": true }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -23967,12 +23651,6 @@ "rimraf": "^2.6.3" } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -24001,15 +23679,6 @@ "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", "dev": true }, - "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, - "requires": { - "path-type": "^4.0.0" - } - }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -24297,27 +23966,28 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", - "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.39.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -24325,22 +23995,19 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "dependencies": { @@ -24397,21 +24064,15 @@ "dev": true }, "eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, - "eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", - "dev": true - }, "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", @@ -24443,6 +24104,12 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true + }, "is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -24458,16 +24125,6 @@ "argparse": "^2.0.1" } }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -24477,20 +24134,6 @@ "p-locate": "^5.0.0" } }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -24509,12 +24152,6 @@ "p-limit": "^3.0.2" } }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -24533,15 +24170,6 @@ "has-flag": "^4.0.0" } }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -24625,23 +24253,21 @@ "estraverse": "^4.1.1" } }, + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + }, "espree": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "requires": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", - "dev": true - } + "eslint-visitor-keys": "^3.4.1" } }, "esprima": { @@ -24825,41 +24451,11 @@ "mime": "^1.3.4" } }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "dependencies": { - "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, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -25009,34 +24605,6 @@ "is-callable": "^1.1.3" } }, - "form-data": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", - "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.35", - "safe-buffer": "^5.2.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, - "formidable": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", - "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", - "dev": true - }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -25115,12 +24683,6 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, - "get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true - }, "get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -25246,10 +24808,10 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, "gzip-size": { @@ -25473,9 +25035,9 @@ "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" }, "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true }, "immer": { @@ -27247,12 +26809,6 @@ } } }, - "js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", - "dev": true - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -27377,6 +26933,16 @@ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -27553,12 +27119,6 @@ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -27695,12 +27255,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -27913,6 +27467,20 @@ "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==" }, + "optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + } + }, "outer-product": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/outer-product/-/outer-product-0.0.4.tgz", @@ -28054,12 +27622,6 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true - }, "picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -28197,6 +27759,12 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, "pretty-error": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", @@ -29205,35 +28773,6 @@ "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==", "requires": {} }, - "superagent": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", - "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", - "dev": true, - "requires": { - "component-emitter": "^1.2.0", - "cookiejar": "^2.1.0", - "debug": "^3.1.0", - "extend": "^3.0.0", - "form-data": "^2.3.1", - "formidable": "^1.2.0", - "methods": "^1.1.1", - "mime": "^1.4.1", - "qs": "^6.5.1", - "readable-stream": "^2.3.5" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -29384,6 +28923,31 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "requires": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "dependencies": { + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "requires": {} + }, + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true + } + } + }, "tldts": { "version": "6.1.86", "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", @@ -29438,6 +29002,13 @@ "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" }, + "ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "requires": {} + }, "ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -29471,25 +29042,17 @@ } }, "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { - "tslib": "^1.8.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } + "prelude-ls": "^1.2.1" } }, "type-detect": { diff --git a/package.json b/package.json index 82a5df6d4..f3c05dd34 100644 --- a/package.json +++ b/package.json @@ -5,15 +5,16 @@ "author": "James Hadfield, Trevor Bedford and Richard Neher", "license": "AGPL-3.0-only", "repository": "github:nextstrain/auspice", + "type": "module", "homepage": "https://www.npmjs.com/package/auspice", "engines": { - "node": "^20 || ^22 || ^24", - "npm": "^9 || ^10 || ^11" + "node": "^24 || ^26", + "npm": "^11" }, "bin": { "auspice": "./auspice.js" }, - "main": "index.js", + "exports": "./index.js", "scripts": { "view": "node auspice.js view --verbose", "dev": "node auspice.js develop --verbose", @@ -23,12 +24,9 @@ "prepare": "npm run build", "lint": "eslint --max-warnings=0 .", "lint:fix": "eslint --fix .", - "type-check": "tsc", - "type-check:watch": "npm run type-check -- --watch", - "get-data": "env bash ./scripts/get-data.sh", + "type-check": "tsc && tsc --project cli/tsconfig.json", + "type-check:watch": "tsc --watch", "fetch-test-data": "./scripts/fetch-test-data", - "heroku-postbuild": "npm run build && npm run get-data", - "gzip-and-upload": "env bash ./scripts/gzip-and-upload.sh", "test": "jest test/*.js test/*.ts", "smoke-test": "NODE_ENV=test ENV=dev npx playwright test", "diff-lang": "./scripts/diff-lang.js" @@ -137,11 +135,10 @@ "@types/lodash": "^4.17.13", "@types/node": "^18.15.11", "@types/webpack-env": "^1.18.2", - "@typescript-eslint/eslint-plugin": "^5.57.0", - "@typescript-eslint/parser": "^5.57.0", - "chai": "^4.1.2", - "chai-http": "^4.0.0", - "eslint": "^8.39.0", + "@typescript-eslint/eslint-plugin": "^8.60.1", + "@typescript-eslint/parser": "^8.60.1", + "babel-plugin-transform-import-meta": "^2.3.3", + "eslint": "^8.57.1", "eslint-plugin-react": "^7.2.1", "eslint-plugin-react-hooks": "^4.0.1", "jest": "^29.5.0", diff --git a/playwright.config.ts b/playwright.config.ts index 60a705cd5..7d2905d8b 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -14,7 +14,7 @@ export default defineConfig({ }, ], webServer: { - command: 'npm run server', + command: 'npm run view -- test/data test/fetched-jsons', url: 'http://localhost:4000', reuseExistingServer: true, }, diff --git a/scripts/diff-lang.js b/scripts/diff-lang.js index a926d6965..4f7a1e3e1 100755 --- a/scripts/diff-lang.js +++ b/scripts/diff-lang.js @@ -1,7 +1,10 @@ #!/usr/bin/env node /* eslint-disable no-console */ -const fs = require('fs'); -const path = require('path'); +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); main(); diff --git a/scripts/extract-release-notes.js b/scripts/extract-release-notes.js index 52d00fe69..05d989229 100644 --- a/scripts/extract-release-notes.js +++ b/scripts/extract-release-notes.js @@ -3,7 +3,7 @@ * It is intended to be run as part of releaseNewVersion.sh */ -const fs = require('fs'); +import fs from 'fs'; function main() { const releaseNotes = []; diff --git a/scripts/fetch-test-data b/scripts/fetch-test-data index 2a74ac750..60bdba662 100755 --- a/scripts/fetch-test-data +++ b/scripts/fetch-test-data @@ -1,12 +1,54 @@ -#!/usr/bin/env bash +#!/usr/bin/env node -# Provision specific JSONs for tests +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; -set -x -e -o pipefail +const __dirname = path.dirname(fileURLToPath(import.meta.url)); -DEST="test/fetched-jsons" -CHARON="https://nextstrain.org/charon/getDataset?prefix=" -FETCH="curl --fail --compressed" +const DEST = path.join(__dirname, '..', 'test', 'fetched-jsons'); +const CHARON = 'https://nextstrain.org/charon/getDataset?prefix='; -${FETCH} ${CHARON}/ebola/all-outbreaks@2026-05-18 --output ${DEST}/ebola-all-outbreaks.json -${FETCH} ${CHARON}/ebola/ebov-2013@2025-10-14 --output ${DEST}/ebola-ebov-2013.json +const datasets = [ + { src: 'ebola/all-outbreaks@2026-05-18', dst: 'ebola-all-outbreaks' }, + { src: 'ebola/ebov-2013@2025-10-14', dst: 'ebola-ebov-2013' }, + { src: 'mumps/global@2026-05-30', dst: 'mumps', tipFrequencies: true }, + { src: 'zika@2026-06-05', dst: 'zika'}, +]; + +async function main() { + if (!fs.existsSync(DEST)) { + console.error(`Expected directory ${DEST} to exist`); + process.exit(1); + } + + const fetches = datasets.flatMap(({src, dst, tipFrequencies}) => { + const items = [ + {url: `${CHARON}/${src}`, output: `${dst}.json`}, + ]; + if (tipFrequencies) { + items.push({url: `${CHARON}/${src}&type=tip-frequencies`, output: `${dst}_tip-frequencies.json`}); + } + return items; + }); + + await Promise.all( + fetches.map(async ({url, output}) => { + console.log(`Fetching ${url}`); + const res = await fetch(url); + if (!res.ok) { + throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`); + } + const outputPath = path.join(DEST, output); + fs.writeFileSync(outputPath, Buffer.from(await res.arrayBuffer())); + console.log(` -> ${outputPath}`); + }) + ); + + console.log(`Fetched ${fetches.length} files`); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/scripts/get-data.sh b/scripts/get-data.sh deleted file mode 100755 index 7861efdc6..000000000 --- a/scripts/get-data.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/bin/bash - -data_files=( - "dengue_all.json" "dengue_denv1.json" "dengue_denv2.json" "dengue_denv3.json" "dengue_denv4.json"\ - "ebola.json" "ebola_root-sequence.json" \ - "ebola_2019-09-14-no-epi-id_meta.json" "ebola_2019-09-14-no-epi-id_tree.json" \ - "lassa_s_tree.json" "lassa_s_meta.json" \ - "lassa_l_tree.json" "lassa_l_meta.json" \ - "measles.json" \ - "mers_tree.json" "mers_meta.json" \ - "mumps_global.json" "mumps_na.json" \ - "WNV_NA_tree.json" "WNV_NA_meta.json" \ - "zika.json" \ - "tb_global_meta.json" "tb_global_tree.json" \ - "enterovirus_d68_genome_meta.json" "enterovirus_d68_genome_tree.json" \ - "enterovirus_d68_vp1_meta.json" "enterovirus_d68_vp1_tree.json" \ - ############## AVIAN FLU ############## - "flu_avian_h7n9_ha.json" \ - "flu_avian_h7n9_mp.json" \ - "flu_avian_h7n9_na.json" \ - "flu_avian_h7n9_np.json" \ - "flu_avian_h7n9_ns.json" \ - "flu_avian_h7n9_pa.json" \ - "flu_avian_h7n9_pb1.json" \ - "flu_avian_h7n9_pb2.json" \ - ############## SEASONAL FLU ############## - "flu_seasonal_h3n2_ha_2y.json" "flu_seasonal_h3n2_ha_2y_tip-frequencies.json" \ - "flu_seasonal_h3n2_ha_3y.json" "flu_seasonal_h3n2_ha_3y_tip-frequencies.json" \ - "flu_seasonal_h3n2_ha_6y.json" "flu_seasonal_h3n2_ha_6y_tip-frequencies.json" \ - "flu_seasonal_h3n2_ha_12y.json" "flu_seasonal_h3n2_ha_12y_tip-frequencies.json" \ - "flu_seasonal_h3n2_na_2y.json" "flu_seasonal_h3n2_na_2y_tip-frequencies.json" \ - "flu_seasonal_h3n2_na_3y.json" "flu_seasonal_h3n2_na_3y_tip-frequencies.json" \ - "flu_seasonal_h3n2_na_6y.json" "flu_seasonal_h3n2_na_6y_tip-frequencies.json" \ - "flu_seasonal_h3n2_na_12y.json" "flu_seasonal_h3n2_na_12y_tip-frequencies.json" \ - "flu_seasonal_h1n1pdm_ha_2y.json" "flu_seasonal_h1n1pdm_ha_2y_tip-frequencies.json" \ - "flu_seasonal_h1n1pdm_ha_3y.json" "flu_seasonal_h1n1pdm_ha_3y_tip-frequencies.json" \ - "flu_seasonal_h1n1pdm_ha_6y.json" "flu_seasonal_h1n1pdm_ha_6y_tip-frequencies.json" \ - "flu_seasonal_h1n1pdm_ha_12y.json" "flu_seasonal_h1n1pdm_ha_12y_tip-frequencies.json" \ - "flu_seasonal_h1n1pdm_ha_pandemic_meta.json" "flu_seasonal_h1n1pdm_ha_pandemic_tree.json" "flu_seasonal_h1n1pdm_ha_pandemic_tip-frequencies.json" \ - "flu_seasonal_h1n1pdm_na_2y.json" "flu_seasonal_h1n1pdm_na_2y_tip-frequencies.json" \ - "flu_seasonal_h1n1pdm_na_3y.json" "flu_seasonal_h1n1pdm_na_3y_tip-frequencies.json" \ - "flu_seasonal_h1n1pdm_na_6y.json" "flu_seasonal_h1n1pdm_na_6y_tip-frequencies.json" \ - "flu_seasonal_h1n1pdm_na_12y.json" "flu_seasonal_h1n1pdm_na_12y_tip-frequencies.json" \ - "flu_seasonal_h1n1pdm_na_pandemic_tree.json" "flu_seasonal_h1n1pdm_na_pandemic_meta.json" "flu_seasonal_h1n1pdm_na_pandemic_tip-frequencies.json" \ - "flu_seasonal_vic_ha_2y.json" "flu_seasonal_vic_ha_2y_tip-frequencies.json" "flu_seasonal_vic_ha_2y_root-sequence.json" \ - "flu_seasonal_vic_ha_3y.json" "flu_seasonal_vic_ha_3y_tip-frequencies.json" "flu_seasonal_vic_ha_3y_root-sequence.json" \ - "flu_seasonal_vic_ha_6y.json" "flu_seasonal_vic_ha_6y_tip-frequencies.json" "flu_seasonal_vic_ha_6y_root-sequence.json" \ - "flu_seasonal_vic_ha_12y.json" "flu_seasonal_vic_ha_12y_tip-frequencies.json" "flu_seasonal_vic_ha_12y_root-sequence.json" \ - "flu_seasonal_vic_na_2y.json" "flu_seasonal_vic_na_2y_tip-frequencies.json" "flu_seasonal_vic_na_2y_root-sequence.json" \ - "flu_seasonal_vic_na_3y.json" "flu_seasonal_vic_na_3y_tip-frequencies.json" "flu_seasonal_vic_na_3y_root-sequence.json" \ - "flu_seasonal_vic_na_6y.json" "flu_seasonal_vic_na_6y_tip-frequencies.json" "flu_seasonal_vic_na_6y_root-sequence.json" \ - "flu_seasonal_vic_na_12y.json" "flu_seasonal_vic_na_12y_tip-frequencies.json" "flu_seasonal_vic_na_12y_root-sequence.json" \ - "flu_seasonal_yam_ha_2y.json" "flu_seasonal_yam_ha_2y_tip-frequencies.json" "flu_seasonal_yam_ha_2y_root-sequence.json" \ - "flu_seasonal_yam_ha_3y.json" "flu_seasonal_yam_ha_3y_tip-frequencies.json" "flu_seasonal_yam_ha_3y_root-sequence.json" \ - "flu_seasonal_yam_ha_6y.json" "flu_seasonal_yam_ha_6y_tip-frequencies.json" "flu_seasonal_yam_ha_6y_root-sequence.json" \ - "flu_seasonal_yam_ha_12y.json" "flu_seasonal_yam_ha_12y_tip-frequencies.json" "flu_seasonal_yam_ha_12y_root-sequence.json" \ - "flu_seasonal_yam_na_2y.json" "flu_seasonal_yam_na_2y_tip-frequencies.json" "flu_seasonal_yam_na_2y_root-sequence.json" \ - "flu_seasonal_yam_na_3y.json" "flu_seasonal_yam_na_3y_tip-frequencies.json" "flu_seasonal_yam_na_3y_root-sequence.json" \ - "flu_seasonal_yam_na_6y.json" "flu_seasonal_yam_na_6y_tip-frequencies.json" "flu_seasonal_yam_na_6y_root-sequence.json" \ - "flu_seasonal_yam_na_12y.json" "flu_seasonal_yam_na_12y_tip-frequencies.json" "flu_seasonal_yam_na_12y_root-sequence.json" \ - ############## LATEST CORE SARS-CoV-2 (COVID-19) BUILDS ############## - "ncov_gisaid_global.json" "ncov_gisaid_global_tip-frequencies.json" \ - "ncov_gisaid_africa.json" "ncov_gisaid_africa_tip-frequencies.json" \ - "ncov_gisaid_asia.json" "ncov_gisaid_asia_tip-frequencies.json" \ - "ncov_gisaid_europe.json" "ncov_gisaid_europe_tip-frequencies.json" \ - "ncov_gisaid_north-america.json" "ncov_gisaid_north-america_tip-frequencies.json" \ - "ncov_gisaid_oceania.json" "ncov_gisaid_oceania_tip-frequencies.json" \ - "ncov_gisaid_south-america.json" "ncov_gisaid_south-america_tip-frequencies.json" \ - ############## TIMESTAMPED SARS-CoV-2 BUILDS USED IN NARRATIVES ############# - "ncov_2020-01-23.json" "ncov_2020-01-25.json" "ncov_2020-01-26.json" "ncov_2020-01-30.json" \ - "ncov_2020-03-04.json" "ncov_2020-03-05.json" "ncov_2020-03-11.json" "ncov_2020-03-13.json" \ - "ncov_2020-03-20.json" "ncov_2020-03-27.json" "ncov_2020-04-03.json" \ - "ncov_global_2020-04-09.json" "ncov_north-america_2020-04-17.json" \ -) - -rm -rf data/ -mkdir -p data/ -for i in "${data_files[@]}" -do - curl http://data.nextstrain.org/"${i}" --compressed -o data/"${i}" -done - -echo "Copying the test datasets from test/data to data" -cp -r test/data/*.json data/ - -echo "The local data directory ./data now contains a selection of up-to-date datasets from http://data.nextstrain.org" diff --git a/src/actions/updateMetadata/updateMetadata.ts b/src/actions/updateMetadata/updateMetadata.ts index f9bb84a3b..f00942a4f 100644 --- a/src/actions/updateMetadata/updateMetadata.ts +++ b/src/actions/updateMetadata/updateMetadata.ts @@ -128,7 +128,7 @@ function _reduxControls( /* geographic resolutions */ if (newMetadata.geographic?.length && !state.panelsAvailable.includes("map")) { - updates.panelsAvailable = [...state.panelsAvailable, "map"], + updates.panelsAvailable = [...state.panelsAvailable, "map"]; updates.panelsToDisplay = [...updates.panelsAvailable]; updates.canTogglePanelLayout = hasMultipleGridPanels(updates.panelsAvailable); updates.geoResolution = newMetadata.geographic[0].key; diff --git a/src/components/controls/animation-controls.js b/src/components/controls/animation-controls.js index 203aabc74..0f782f42c 100644 --- a/src/components/controls/animation-controls.js +++ b/src/components/controls/animation-controls.js @@ -21,9 +21,8 @@ class AnimationControls extends React.Component { return ( { - this.props.animationPlayPauseButton === "Play" ? - this.props.dispatch({type: MAP_ANIMATION_PLAY_PAUSE_BUTTON, data: "Pause"}) : - this.props.dispatch({type: MAP_ANIMATION_PLAY_PAUSE_BUTTON, data: "Play"}); + const newState = this.props.animationPlayPauseButton === "Play" ? "Pause" : "Play"; + this.props.dispatch({ type: MAP_ANIMATION_PLAY_PAUSE_BUTTON, data: newState }); }} > diff --git a/src/components/controls/filter.js b/src/components/controls/filter.js index f27e949b3..3c3f9069a 100644 --- a/src/components/controls/filter.js +++ b/src/components/controls/filter.js @@ -82,7 +82,6 @@ class FilterData extends React.Component { ...(this.props.totalStateCountsSecondTree?.[traitName]?.keys() || []), ]); - this.props.totalStateCounts[traitName]; const traitTitle = this.getFilterTitle(traitName); const filterValuesCurrentlyActive = new Set((this.props.activeFilters[traitName] || []).filter((x) => x.active).map((x) => x.value)); for (const traitValue of Array.from(traitData).sort()) { diff --git a/src/components/controls/language.js b/src/components/controls/language.js index 452031ac8..dea408410 100644 --- a/src/components/controls/language.js +++ b/src/components/controls/language.js @@ -20,7 +20,7 @@ class Language extends React.Component { try { const res = await import(/* webpackMode: "lazy-once" */ `../../locales/${lang}/${ns}.json`); i18n.addResourceBundle(lang, ns, res.default); - } catch (err) { + } catch (_err) { console.error(`Language ${lang} not found!`); } } diff --git a/src/components/framework/fine-print.js b/src/components/framework/fine-print.js index 5943a1eec..98ecf5225 100644 --- a/src/components/framework/fine-print.js +++ b/src/components/framework/fine-print.js @@ -11,7 +11,7 @@ import { publications } from "../download/downloadModal"; import { hasExtension, getExtension } from "../../util/extensions"; import { canShowLinkOuts } from "../modal/LinkOutModalContents.jsx"; -const logoPNG = require("../../images/favicon.png"); +import logoPNG from "../../images/favicon.png"; const MarkdownDisplay = lazy(() => import("../markdownDisplay")); diff --git a/src/components/info/byline.js b/src/components/info/byline.js index 8da817e44..33da38f80 100644 --- a/src/components/info/byline.js +++ b/src/components/info/byline.js @@ -3,6 +3,7 @@ import { connect } from "react-redux"; import { withTranslation, Trans } from 'react-i18next'; import styled from 'styled-components'; import { headerFont } from "../../globalStyles"; +import gisaidLogo from "../../images/gisaid-logo.png"; /** * React component for the byline of the current dataset. @@ -138,7 +139,7 @@ function renderDataProvenance(t, metadata) { .map((source) => { if (source.name.toUpperCase() === "GISAID") { // SPECIAL CASE return ( - gisaid-logo + gisaid-logo ); } const url = parseUrl(source.url); @@ -171,7 +172,7 @@ function parseUrl(potentialUrl) { try { const urlObj = new URL(potentialUrl); return urlObj.href; - } catch (err) { + } catch (_err) { return false; } } diff --git a/src/components/map/mapHelpersLatLong.js b/src/components/map/mapHelpersLatLong.js index 09abe4b51..b51162674 100644 --- a/src/components/map/mapHelpersLatLong.js +++ b/src/components/map/mapHelpersLatLong.js @@ -221,13 +221,13 @@ const maybeConstructTransmissionEvent = ( try { latOrig = demeToLatLongs[nodeLocation].latitude; longOrig = demeToLatLongs[nodeLocation].longitude; - } catch (e) { + } catch (_err) { demesMissingLatLongs.add(nodeLocation); } try { latDest = demeToLatLongs[childLocation].latitude; longDest = demeToLatLongs[childLocation].longitude; - } catch (e) { + } catch (_err) { demesMissingLatLongs.add(childLocation); } @@ -503,7 +503,7 @@ const updateTransmissionDataColAndVis = (transmissionData, transmissionIndices, transmissionDataCopy[index].color = col; transmissionDataCopy[index].visible = vis; }); - } catch (err) { + } catch (_err) { console.warn(`Error trying to access ${id} in transmissionIndices. Map transmissions may be wrong.`); } }); diff --git a/src/components/navBar/content.js b/src/components/navBar/content.js index dfc1b8136..b96ce4d98 100644 --- a/src/components/navBar/content.js +++ b/src/components/navBar/content.js @@ -1,7 +1,6 @@ import React from "react"; import styled from 'styled-components'; - -const logoPNG = require("../../images/logo-light.svg"); +import logoPNG from "../../images/logo-light.svg"; const AuspiceNavBarContainer = styled.div` display: flex; diff --git a/src/components/splash/splash.js b/src/components/splash/splash.js index deb35e9c4..5c89de5f3 100644 --- a/src/components/splash/splash.js +++ b/src/components/splash/splash.js @@ -4,6 +4,7 @@ import NavBar from "../navBar"; import Flex from "../../components/framework/flex"; import { CenterContent } from "./centerContent"; import { FinePrintStyles, getCitation, getCustomFinePrint } from "../../components/framework/fine-print"; +import logoLightSvg from "../../images/logo-light.svg"; const getNumColumns = (width) => width > 1000 ? 3 : width > 750 ? 2 : 1; @@ -32,9 +33,7 @@ const SplashContent = ({available, browserDimensions, errorMessage}) => { logo diff --git a/src/components/tree/phyloTree/change.ts b/src/components/tree/phyloTree/change.ts index c478e5f92..f3550f5f4 100644 --- a/src/components/tree/phyloTree/change.ts +++ b/src/components/tree/phyloTree/change.ts @@ -75,7 +75,10 @@ const svgSetters = { }; -type UpdateCall = (selection: Transition) => void; +type ModifiableSelection = + | Transition + | Selection; +type UpdateCall = (selection: ModifiableSelection) => void; /** createUpdateCall @@ -118,20 +121,25 @@ const genericSelectAndModify = ( ): void => { // console.log("general svg update for", treeElem); - svg.selectAll(treeElem) - .filter((d) => d.update) - .transition().duration(transitionTime) - .call(updateCall); - if (!transitionTime) { - timerFlush(); + const selection = svg.selectAll(treeElem) + .filter((d) => d.update); + if (transitionTime) { + selection.transition().duration(transitionTime).call(updateCall); + } else { + /* transitionTime===0: apply the update directly rather than via a + * (zero-duration) transition. Starting a transition here would interrupt any + * in-flight transition on the same elements — e.g. a quick legend-hover + * tip-radius change (transitionTime 0) landing mid-zoom would cancel the + * zoom's position transition and freeze those tips. A direct write updates + * the attrs/styles immediately without touching other running transitions. */ + selection.call(updateCall); } - }; /* use D3 to select and modify elements, such that a given element is only ever modified _once_ * @elemsToUpdate {set} - the class names to select, e.g. ".tip" or ".branch" * @svgPropsToUpdate {set} - the props (styles & attrs) to update. The respective functions are defined above - * @transitionTime {INT} - in ms. if 0 then no transition (timerFlush is used) + * @transitionTime {INT} - in ms. if 0 the update is applied directly (no transition) * @extras {dict} - extra keywords to tell this function to call certain phyloTree update methods. In flux. */ export const modifySVG = function modifySVG( diff --git a/src/components/tree/tree.tsx b/src/components/tree/tree.tsx index c615c2111..bd6fb68d6 100644 --- a/src/components/tree/tree.tsx +++ b/src/components/tree/tree.tsx @@ -138,7 +138,10 @@ export class TreeComponent extends React.Component {mainTree ? this.domRefs.mainTree = c : this.domRefs.secondTree = c;}} + ref={(c): void => { + if (mainTree) { this.domRefs.mainTree = c; } + else { this.domRefs.secondTree = c; } + }} /> diff --git a/src/root.js b/src/root.js index d774ef4e8..28bd662ed 100644 --- a/src/root.js +++ b/src/root.js @@ -16,7 +16,7 @@ const DebugNarrative = lazy(() => import("./components/narrativeEditor/narrative * This triggers a window resize which in turn triggers a general * rerender. A bit ham-fisted but gets the job done for the time being * */ -if (module.hot) { +if (import.meta.webpackHot) { setTimeout(() => window.dispatchEvent(new Event('resize')), 500); } diff --git a/src/store.ts b/src/store.ts index 69c87306b..5a6ff4247 100644 --- a/src/store.ts +++ b/src/store.ts @@ -41,10 +41,9 @@ const store = configureStore({ devTools: process.env.NODE_ENV !== 'production', }) -if (process.env.NODE_ENV !== 'production' && module.hot) { - // console.log("hot reducer reload"); - module.hot.accept('./reducers', () => { - const nextRootReducer = require('./reducers/index'); +if (process.env.NODE_ENV !== 'production' && import.meta.webpackHot) { + import.meta.webpackHot.accept('./reducers', async () => { + const nextRootReducer = (await import('./reducers/index')).default; store.replaceReducer(nextRootReducer); }); } diff --git a/src/util/entropy.js b/src/util/entropy.js index 076771e6d..b9aa76edc 100644 --- a/src/util/entropy.js +++ b/src/util/entropy.js @@ -29,6 +29,7 @@ const calcMutationCounts = (nodes, visibility, selectedCds) => { mutations?.[cdsName]?.forEach((m) => { const {from, to, pos} = parseMutation(m); if (valid(from, to, isAA)) { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions sparse[pos] ? sparse[pos]++ : sparse[pos] = 1; } }); @@ -39,7 +40,7 @@ const calcMutationCounts = (nodes, visibility, selectedCds) => { for (let i = 0; i < sparse.length; i++) { if (!sparse[i]) {continue;} if (sparse[i] > m) {m = sparse[i];} - counts[j] = isAA ? + counts[j] = isAA ? {codon: parseInt(i, 10), y: sparse[i]} : {x: i, y: sparse[i]}; /* TODO reset y scale in D3 or compute entropy */ j++; @@ -118,7 +119,7 @@ const calcEntropy = (nodes, visibility, selectedCds) => { counts[position][anc_state[position]] = nUnobserved; } } - + let s = 0; /* shannon entropy */ for (const count of Object.values(counts[position])) { const a = count / visibleTips; @@ -177,10 +178,10 @@ export function getCdsByName(genomeMap, name) { * codon). * If _either_ of the rangeGenome positions are _beyond_ the CDS then we return * the entire rangeLocal of the cds + set the flag `valid` to false. - * + * * For wrapping genes, the UI forces the rangeGenome (i.e. the zoomCoordinates) * to be on either side of the origin, and we maintain thus assumption here. - * + * * Returns [rangeLocalInView:rangeLocal, valid:bool] */ export function getCdsRangeLocalFromRangeGenome(cds, rangeGenome) { @@ -203,12 +204,12 @@ export function getCdsRangeLocalFromRangeGenome(cds, rangeGenome) { * The general approach is to visit the segments in the order they appear, * i.e. in the context of the strand it's always 5' -> 3' but for -ve strand * CDSs this appears 3' -> 5' in the context of the +ve strand. Once we find - * an intersection we can work out the appropriate local coordinate. + * an intersection we can work out the appropriate local coordinate. * Remember that zoomStart/End are reversed if the CDS is wrapping! */ let prevSeg; for (const seg of segments) { - /* If the zoom start (5') is inside the segment, then we know one of the local bounds */ + /* If the zoom start (5') is inside the segment, then we know one of the local bounds */ if (seg.rangeGenome[0]<=zoomStart && seg.rangeGenome[1]>=zoomStart) { if (positive) { const delta = zoomStart - seg.rangeGenome[0]; @@ -217,8 +218,8 @@ export function getCdsRangeLocalFromRangeGenome(cds, rangeGenome) { const delta = zoomStart - seg.rangeGenome[0]; cdsLocalEnd = seg.rangeLocal[1] - delta; } - } - /* If the zoom end (3') is inside the segment, then we know one of the local bounds */ + } + /* If the zoom end (3') is inside the segment, then we know one of the local bounds */ if (seg.rangeGenome[0]<=zoomEnd && seg.rangeGenome[1]>=zoomEnd) { if (positive) { const delta = zoomEnd - seg.rangeGenome[0]; @@ -349,4 +350,4 @@ export function nucleotideToAaPosition(genomeMap, nucPos) { } }) return matches; -} \ No newline at end of file +} diff --git a/src/util/extensions.ts b/src/util/extensions.ts index 1f7c5caa1..6e4e7de19 100644 --- a/src/util/extensions.ts +++ b/src/util/extensions.ts @@ -6,7 +6,6 @@ type Extensions = { const registry: Extensions = ((): Extensions => { if (!process.env.EXTENSION_DATA) { - // console.log("no EXTENSION_DATA found"); return {}; } @@ -15,12 +14,10 @@ const registry: Extensions = ((): Extensions => { Object.keys(extensions).forEach((key: string) => { if (key.endsWith("Component")) { - // console.log("loading component", key); /* "@extensions" is a webpack alias */ extensions[key] = require(`@extensions/${extensions[key]}`).default; } }); - // console.log("extensions", extensions); return extensions; })(); diff --git a/src/util/parseNarrative.js b/src/util/parseNarrative.js index 5ab213539..378ee9185 100644 --- a/src/util/parseNarrative.js +++ b/src/util/parseNarrative.js @@ -1,10 +1,10 @@ /* eslint no-param-reassign: off */ -const { safeLoadFront } = require('yaml-front-matter'); -const queryString = require("query-string"); +import { safeLoadFront } from 'yaml-front-matter'; +import queryString from "query-string"; -const parseMarkdownNarrativeFile = (fileContents, markdownParser) => { +export const parseMarkdownNarrativeFile = (fileContents, markdownParser) => { const frontMatter = safeLoadFront(fileContents); if (Object.keys(frontMatter).length === 1) { @@ -22,7 +22,7 @@ const parseMarkdownNarrativeFile = (fileContents, markdownParser) => { }; -function createTitleSlideFromFrontmatter(frontMatter, markdownParser) { +export function createTitleSlideFromFrontmatter(frontMatter, markdownParser) { let markdown = ""; // A markdown interpretation of the YAML for display markdown += parseTitleSlideTitle(frontMatter); @@ -45,7 +45,7 @@ function createTitleSlideFromFrontmatter(frontMatter, markdownParser) { }; } -function parseTitleSlideTitle(frontMatter) { +export function parseTitleSlideTitle(frontMatter) { if (!frontMatter.title) throw new Error("Narrative YAML frontmatter must define a title!"); return `## ${frontMatter.title}\n`; } @@ -54,7 +54,7 @@ function parseUrl(urlString, fallbackDataset=false) { let url, dataset; try { url = new URL(urlString); - } catch (err) { + } catch (_err) { if (fallbackDataset) { console.error(`Couldn't parse the URL "${urlString}". Falling back to "${fallbackDataset}".`); return {dataset: fallbackDataset, query: ""}; @@ -139,7 +139,7 @@ function* parseNarrativeBody(markdown, fallbackDataset, markdownParser) { } } -function parseNarrativeAuthors(frontMatter) { +export function parseNarrativeAuthors(frontMatter) { let authorMd = ""; const authors = parseAttributions(frontMatter, "authors", "authorLinks"); if (authors) { @@ -152,7 +152,7 @@ function parseNarrativeAuthors(frontMatter) { return authorMd+"\n"; } -function parseNarrativeTranslators(frontMatter) { +export function parseNarrativeTranslators(frontMatter) { const translators = parseAttributions(frontMatter, "translators", "translatorLinks"); if (translators) return `### Translators: ${translators}\n`; return ""; @@ -212,13 +212,3 @@ function attributionLink(attribution, attributionLinkValue) { } return attribution; } - - -module.exports = { - parseMarkdownNarrativeFile, - /* following functions exported for unit testing */ - createTitleSlideFromFrontmatter, - parseTitleSlideTitle, - parseNarrativeAuthors, - parseNarrativeTranslators -}; diff --git a/src/util/treeJsonProcessing.ts b/src/util/treeJsonProcessing.ts index 6ff2de6b1..9bb1b4984 100644 --- a/src/util/treeJsonProcessing.ts +++ b/src/util/treeJsonProcessing.ts @@ -164,6 +164,7 @@ const collectObservedMutations = (nodesArray: ReduxNode[]): Mutations => { if (!n.branch_attrs || !n.branch_attrs.mutations) return; Object.entries(n.branch_attrs.mutations).forEach(([gene, muts]) => { muts.forEach((mut) => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions mutations[`${gene}:${mut}`] ? mutations[`${gene}:${mut}`]++ : (mutations[`${gene}:${mut}`] = 1); }); }); diff --git a/src/util/treeMiscHelpers.js b/src/util/treeMiscHelpers.js index 6960bd8c9..117c6ab25 100644 --- a/src/util/treeMiscHelpers.js +++ b/src/util/treeMiscHelpers.js @@ -103,7 +103,7 @@ function validateUrl(url) { if (url.startsWith("https_")) url = url.replace("https_", "https:"); const urlObj = new URL(url); return urlObj.href; - } catch (err) { + } catch (_err) { console.warn(`Dataset provided the invalid URL ${url}`); return undefined; } diff --git a/src/util/treeVisibilityHelpers.js b/src/util/treeVisibilityHelpers.js index 20987d265..101447021 100644 --- a/src/util/treeVisibilityHelpers.js +++ b/src/util/treeVisibilityHelpers.js @@ -25,7 +25,7 @@ export const strainNameToIdx = (nodes, name) => { /** * Find the node with the given label name & value * NOTE: if there are multiple nodes with the same label then `null` is returned - * + * * @param {Array} nodes tree nodes (flat) * @param {string} labelName label name * @param {string} labelValue label value @@ -157,7 +157,7 @@ const getInView = (tree) => { let inView; try { inView = tree.nodes.map((d) => d.shell.inView); - } catch (e) { + } catch (_err) { /* edge case: this fn may be called before the shell structure of the nodes * has been created (i.e. phyloTree's not run yet). In this case, it's * safe to assume that everything's in view */ diff --git a/src/version.js b/src/version.js index fcc0c5a6a..72dd2e177 100644 --- a/src/version.js +++ b/src/version.js @@ -1,5 +1 @@ -const version = "2.72.0"; - -module.exports = { - version -}; +export const version = "2.72.0"; diff --git a/test/colorScales.test.js b/test/colorScales.test.js index fb61a75a1..e0debdcc3 100644 --- a/test/colorScales.test.js +++ b/test/colorScales.test.js @@ -1,6 +1,6 @@ import { unknownColor, createNonContinuousScaleFromProvidedScaleMap } from "../src/util/colorScale"; -const crypto = require("crypto"); +import crypto from "crypto" /* ---------------------------------------------------------------------- */ diff --git a/test/request_urls.js b/test/request_urls.js deleted file mode 100644 index 8b96b1d0f..000000000 --- a/test/request_urls.js +++ /dev/null @@ -1,26 +0,0 @@ -/* eslint no-unused-expressions: off */ - -/* HOW TO RUN THESE TESTS? -npx mocha -*/ - -const chai = require('chai'); -chai.use(require('chai-http')); - -const expect = chai.expect; - -const isValidURLCallback = (url) => { - return (done) => { - chai.request(url) - .get('') - .end((err, res) => { - expect(err).to.be.null; - expect(res).to.have.status(200); - done(); // Call done to signal callback end - }); - }; -}; - - -it('loads the spash page as expected', isValidURLCallback('http://localhost:4000')); -it('loads /zika', isValidURLCallback('http://localhost:4000/zika')); diff --git a/test/server-fetch.test.js b/test/server-fetch.test.js new file mode 100644 index 000000000..da163d6c3 --- /dev/null +++ b/test/server-fetch.test.js @@ -0,0 +1,89 @@ +/** + * Exercises the CLI's default `/charon/getDataset` handler against real on-disk + * JSONs (provisioned into `test/fetched-jsons/` via `npm run fetch-test-data`). + * + * We stand up a minimal express server using the exact handler factory that + * `auspice view` / `auspice develop` use, listen on an ephemeral port, and make + * real HTTP requests so the streaming response path is exercised end-to-end. + * + * The `mumps` fixtures consist of a v2 dataset (`mumps.json`) plus its + * tip-frequencies sidecar (`mumps_tip-frequencies.json`). The client requests + * sidecars via the same getDataset route with a `type` query param, e.g. + * `/charon/getDataset?prefix=mumps&type=tip-frequencies` + * (see src/actions/loadData.js). + */ + +import express from "express"; +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { setUpGetDatasetHandler } from "../cli/server/getDataset.ts"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const DATA_DIR = path.resolve(__dirname, "fetched-jsons"); +const MAIN_FIXTURE = path.join(DATA_DIR, "mumps.json"); +const SIDECAR_FIXTURE = path.join(DATA_DIR, "mumps_tip-frequencies.json"); + +const fixturesAvailable = fs.existsSync(MAIN_FIXTURE) && fs.existsSync(SIDECAR_FIXTURE); + +/* The fetched JSONs are gitignored; skip (with a clear message) rather than fail + * if they haven't been provisioned via `npm run fetch-test-data`. */ +const describeOrSkip = fixturesAvailable ? describe : describe.skip; +if (!fixturesAvailable) { + // eslint-disable-next-line no-console + console.error( + `[server-fetch.test] Skipping: fixtures missing in ${DATA_DIR}. ` + + `Provision them with \`npm run fetch-test-data\`.` + ); + process.exit(1) +} + +describeOrSkip("CLI default getDataset handler", () => { + let server; + let baseUrl; + + beforeAll((done) => { + const app = express(); + /* `dataPaths` mirrors the structure produced by `processPathArguments`: + * an object mapping an absolute path to the set of data types to serve from it. */ + const dataPaths = { [DATA_DIR]: new Set(["datasets"]) }; + app.get("/charon/getDataset", setUpGetDatasetHandler(dataPaths)); + server = app.listen(0, () => { + const { port } = server.address(); + baseUrl = `http://127.0.0.1:${port}/charon/getDataset`; + done(); + }); + }); + + afterAll((done) => { + server.close(done); + }); + + it("serves the main (v2) dataset JSON (sanity check)", async () => { + const res = await fetch(`${baseUrl}?prefix=mumps`); + expect(res.status).toBe(200); + const json = await res.json(); + expect(json.version).toBe("v2"); + expect(json).toHaveProperty("tree"); + }); + + it("serves the tip-frequencies sidecar JSON", async () => { + const res = await fetch(`${baseUrl}?prefix=mumps&type=tip-frequencies`); + expect(res.status).toBe(200); + const json = await res.json(); + const expected = JSON.parse(fs.readFileSync(SIDECAR_FIXTURE, "utf8")); + expect(json.pivots).toEqual(expected.pivots); + }); + + it("a valid prefix but unknown type is a 400 (Bad Request)", async () => { + const res = await fetch(`${baseUrl}?prefix=mumps&type=foo`); + expect(res.status).toBe(400); + }); + + it("dataset doesn't exist is a 404 (Not Found)", async () => { + const res = await fetch(`${baseUrl}?prefix=foo`); + expect(res.status).toBe(404); + }); + +}); diff --git a/test/smoke-test/urls.test.js b/test/smoke-test/urls.test.js index d2a06abe0..3036ee7ef 100644 --- a/test/smoke-test/urls.test.js +++ b/test/smoke-test/urls.test.js @@ -1,5 +1,5 @@ -const {readFileSync} = require('fs'); -const {expect, test} = require('@playwright/test'); +import {readFileSync} from 'fs'; +import {expect, test} from '@playwright/test'; test.describe.configure({ mode: 'parallel' }); @@ -7,7 +7,7 @@ const testCases = []; // Load a suite of smoke tests from a simple text file. File is of the format: // [path], [comma separated text assertions] -const text = readFileSync(require.resolve('./urls.txt'), 'utf8').trim(); +const text = readFileSync(new URL('./urls.txt', import.meta.url), 'utf8').trim(); for (const line of text.split(/\r?\n/)) { if (line.length === 0) continue; // allow for blank lines. if (line.match(/^#/)) continue; // allow for comments. diff --git a/test/smoke-test/urls.txt b/test/smoke-test/urls.txt index b23726da0..dea68580b 100644 --- a/test/smoke-test/urls.txt +++ b/test/smoke-test/urls.txt @@ -2,9 +2,6 @@ # comma separated list of text assertions (text we expect to find in the # rendered page): -# Following smoke test passed locally, but failed stochastically on GitHub Actions -#/ncov/global, novel coronavirus, Phylogeny, Diversity, Filter by Location - /zika, Zika virus evolution, rectangular /zika?c=region&l=clock&legend=open&m=div&r=region, Zika virus evolution, rectangular diff --git a/tsconfig.json b/tsconfig.json index 23bb52501..7723a7abe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,6 +19,7 @@ Visit https://aka.ms/tsconfig.json for a detailed list of options. /* Modules */ "module": "esnext", /* Specify what module code is generated. Required for dynamic `import()`. */ "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + "allowImportingTsExtensions": true, /* Allow .ts extensions in imports (required as cli .ts files are pulled in transitively). */ "types": ["node", "webpack-env", "jest"], /* Specify type package names to be included without being referenced in a source file. */ "resolveJsonModule": true, /* Allow importing .json files. */ @@ -53,5 +54,8 @@ Visit https://aka.ms/tsconfig.json for a detailed list of options. "include": [ "src/**/*", "test/**/*" + ], + "exclude": [ + "cli/**/*" ] } diff --git a/webpack.config.js b/webpack.config.cjs similarity index 98% rename from webpack.config.js rename to webpack.config.cjs index f6a4168c3..3b1329f19 100644 --- a/webpack.config.js +++ b/webpack.config.cjs @@ -3,7 +3,7 @@ const path = require("path"); const webpack = require("webpack"); const CompressionPlugin = require('compression-webpack-plugin'); const fs = require('fs'); -const utils = require('./cli/utils'); +const utils = require('./cli/utils.ts'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const LodashModuleReplacementPlugin = require('lodash-webpack-plugin'); @@ -212,6 +212,7 @@ const generateConfig = ({extensionPath, devMode=false, customOutputPath, analyze resolve: { alias: aliasesToResolve, extensions: ['.ts', '.tsx', '...'], + fullySpecified: false, fallback: { buffer: require.resolve("buffer/"), fs: false @@ -269,6 +270,11 @@ const generateConfig = ({extensionPath, devMode=false, customOutputPath, analyze }, module: { rules: [ + { + test: /\.(ts|js)x?$/, + type: "javascript/auto", + resolve: { fullySpecified: false } + }, { test: /\.(ts|js)x?$/, loader: 'babel-loader',