From f1cf561b037db72751bf1e6e4704a6fe4bd5f224 Mon Sep 17 00:00:00 2001 From: james hadfield Date: Tue, 27 May 2025 11:51:26 +1200 Subject: [PATCH 01/18] [cli] Allow one or more positional path args The previous interface was slightly cumbersome - I often found myself wanting to source both datasets & narratives from some directory X and having to type in the rather long `--datasetDir X --narrativeDir X`, and often I'd misspell / pluralise the argument names; using a simple positional argument `X` is much nicer. The ability to use multiple directories is also functionality I've long wanted. The previous CLI interface is preserved in its entirety for backwards compatibility and is mutually exclusive with the new positional-args style. --- cli/develop.js | 20 +++- cli/server/getAvailable.js | 159 +++++++++++++++++++------------ cli/server/getDataset.js | 103 +++++++++++++++++--- cli/server/getDatasetHelpers.js | 63 ++++-------- cli/server/getNarrative.js | 62 +++++++----- cli/server/processPaths.js | 77 +++++++++++++++ cli/utils.js | 24 ++--- cli/view.js | 91 ++++++++++-------- docs/introduction/how-to-run.rst | 12 +-- 9 files changed, 398 insertions(+), 213 deletions(-) create mode 100644 cli/server/processPaths.js diff --git a/cli/develop.js b/cli/develop.js index e6d6831fc..87d594704 100644 --- a/cli/develop.js +++ b/cli/develop.js @@ -5,11 +5,12 @@ 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 view = require("./view"); const version = require('../src/version').version; const chalk = require('chalk'); const generateWebpackConfig = require("../webpack.config.js").default; const SUPPRESS = require('argparse').Const.SUPPRESS; +const { processPathArguments } = require("./server/processPaths"); const addParser = (parser) => { const description = `Launch auspice in development mode. @@ -21,8 +22,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. */ @@ -31,6 +31,8 @@ const addParser = (parser) => { const run = (args) => { + const dataPaths = processPathArguments(args) + /* Basic server set up */ const app = express(); app.set('port', process.env.PORT || 4000); @@ -69,10 +71,18 @@ 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 = 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("/")); diff --git a/cli/server/getAvailable.js b/cli/server/getAvailable.js index c7e66418b..6c6d25b64 100644 --- a/cli/server/getAvailable.js +++ b/cli/server/getAvailable.js @@ -1,94 +1,104 @@ const utils = require("../utils"); const fs = require('fs'); +const path = require("path"); const { promisify } = require('util'); const { findAvailableSecondTreeOptions } = require('./getDatasetHelpers'); const readdir = promisify(fs.readdir); -const getAvailableDatasets = async (localDataPath) => { +const getAvailableDatasets = (dir, files) => { 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") - )) + + /* 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(".json", "") + .replace("_tree.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) - }); + v1Files.forEach((filepath) => { + datasets.push({ + request: filepath, + v2: false, + secondTreeOptions: findAvailableSecondTreeOptions(filepath, v1Files), + fileType: 'dataset', + dir, }); - } 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 getAvailableNarratives = (dir, files) => { + 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', + })); }; -const setUpGetAvailableHandler = ({datasetsPath, narrativesPath}) => { +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) => { utils.log("GET AVAILABLE returning locally available datasets & narratives"); - const datasets = await getAvailableDatasets(datasetsPath); - const narratives = await getAvailableNarratives(narrativesPath); - res.json({datasets, narratives}); + let resources = [] + for (const [p, dataTypes] of Object.entries(dataPaths)) { + 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)); }; }; @@ -97,5 +107,28 @@ const setUpGetAvailableHandler = ({datasetsPath, narrativesPath}) => { module.exports = { setUpGetAvailableHandler, getAvailableDatasets, - getAvailableNarratives + getAvailableNarratives, }; + +function availableResponseStructure(resources) { + const datasets = resources.filter((r) => r.fileType==='dataset') + .map((r) => ({request: r.request, buildUrl: r.buildUrl, secondTreeOptions: r.secondTreeOptions})); + const narratives = resources.filter((r) => r.fileType==='narrative') + .map((r) => ({request: r.request})); + return {datasets, narratives}; +} + +/** + * First match wins + */ +function unique(resources) { + 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; + }) +} \ No newline at end of file diff --git a/cli/server/getDataset.js b/cli/server/getDataset.js index a7965bee3..abc3929b8 100644 --- a/cli/server/getDataset.js +++ b/cli/server/getDataset.js @@ -1,25 +1,106 @@ +const { promisify } = require('util'); +const path = require("path"); +const fs = require('fs'); const getAvailable = require("./getAvailable"); const helpers = require("./getDatasetHelpers"); +const utils = require("../utils"); -const setUpGetDatasetHandler = ({datasetsPath}) => { +const readdir = promisify(fs.readdir); + +/** + * Returns a route handler which responds to requests by serving the relevant JSON file + * from disk. + */ +const setUpGetDatasetHandler = (dataPaths) => { return async (req, res) => { + + let requestInfo; 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); + requestInfo = helpers.interpretRequest(req); } 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); + return helpers.handleError(res, err.message, err.message, 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) { + /** + * Iterate through the dataPaths and return the first match we find + * (this "first match wins" approach mirrors that of `getAvailable`) + */ + const allAvailableDatasets = [] + for (const [dir, dataTypes] of Object.entries(dataPaths)) { + if (!dataTypes.has('datasets')) continue; + let files = []; + try { + files = await readdir(dir); + } catch (err) { + utils.warn(`Error reading datasets from ${dir}: ${err.message}`) + } + + 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; +} + + module.exports = { setUpGetDatasetHandler }; diff --git a/cli/server/getDatasetHelpers.js b/cli/server/getDatasetHelpers.js index 82a5b537d..f46a55928 100644 --- a/cli/server/getDatasetHelpers.js +++ b/cli/server/getDatasetHelpers.js @@ -11,7 +11,6 @@ const utils = require("../utils"); const queryString = require("query-string"); -const path = require("path"); const convertFromV1 = require("./convertJsonSchemas").convertFromV1; const fs = require("fs"); @@ -27,12 +26,15 @@ const splitPrefixIntoParts = (url) => url /** - * 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]}`); 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}; @@ -46,64 +48,34 @@ 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) => { +const closestMatch = (requestedDatasetParts, availableDatasets) => { let matchingDatasets = availableDatasets; let i; - const matchDatasetRequest = (d) => d.request.split("/")[i] === info.parts[i]; + const matchDatasetRequest = (d) => 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) => { if (typeof info.address === "string") { @@ -173,8 +145,7 @@ const findAvailableSecondTreeOptions = (currentDatasetUrl, availableDatasetUrls) module.exports = { interpretRequest, - redirectIfDatapathMatchFound, - makeFetchAddresses, + closestMatch, handleError, sendJson, findAvailableSecondTreeOptions diff --git a/cli/server/getNarrative.js b/cli/server/getNarrative.js index e3112bdc6..9cd87dbf1 100644 --- a/cli/server/getNarrative.js +++ b/cli/server/getNarrative.js @@ -1,38 +1,50 @@ const queryString = require("query-string"); -const path = require("path"); +const { promisify } = require('util'); const fs = require("fs"); const utils = require("../utils"); +const getAvailable = require("./getAvailable"); -const setUpGetNarrativeHandler = ({narrativesPath}) => { +const readdir = promisify(fs.readdir); + + +const setUpGetNarrativeHandler = (dataPaths) => { return async (req, res) => { + utils.log(`GET NARRATIVE request received: ${req.url}`); 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; + 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 - 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}`); + for (const [p, dataTypes] of Object.entries(dataPaths)) { + 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.message}`; + utils.warn(errorMessage); + return res.status(500).type("text/plain").send(errorMessage); } - 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); } - }; + const errorMessage = `No matching narrative found`; + utils.warn(errorMessage); + res.status(404).type("text/plain").send(errorMessage); + } }; module.exports = { diff --git a/cli/server/processPaths.js b/cli/server/processPaths.js new file mode 100644 index 000000000..7f0070315 --- /dev/null +++ b/cli/server/processPaths.js @@ -0,0 +1,77 @@ +const utils = require("../utils"); + +/** + * 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. + * + * If no paths are provided via arguments then we attempt to choose sensible defaults + * (via `defaultDataPaths()`), however in my (james) experience it's never worked + * well an I recommend providing paths via args. + */ +function processPathArguments(args) { + + 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.datasetDir || args.narrativeDir) && args.path.length>0) { + utils.error("Incompatible arguments defining paths for datasets and/or narratives: " + + "You must either specify the paths as named arguments (--datasetDir and/or --narrativeDir) " + + "_or_ specify one or more positional arguments."); + } + + 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}' `) + } + + const dataPaths = Object.fromEntries( + (args.path.length ? + args.path.map(utils.cleanUpPathname) : + args.datasetDir ? + [utils.cleanUpPathname(args.datasetDir)] : + utils.defaultDataPaths() + ).map((p) => [p, new Set(['datasets'])]) + ); + + for (const narrativePath of + (args.path.length ? + args.path.map(utils.cleanUpPathname) : + args.narrativeDir ? + [utils.cleanUpPathname(args.narrativeDir)] : + utils.defaultDataPaths({narrative: true}))) { + 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; +} + + +module.exports = { + processPathArguments, +}; diff --git a/cli/utils.js b/cli/utils.js index 4a82d0aa5..8c6ddafe0 100644 --- a/cli/utils.js +++ b/cli/utils.js @@ -31,6 +31,7 @@ const cleanUpPathname = (pathIn) => { } pathOut = path.resolve(pathOut); if (!fs.existsSync(pathOut)) { + warn(`path ${pathOut} doesn't exist`) return undefined; } return pathOut; @@ -45,18 +46,12 @@ const getCurrentDirectoriesFor = (type) => { 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 defaultDataPaths = ({narrative=false} = {}) => { + return isNpmGlobalInstall() ? + [getCurrentDirectoriesFor(narrative ? "narratives" : "data")] : + [path.join(path.resolve(__dirname), "..", narrative ? "narratives" : "data")]; +} const readFilePromise = (fileName) => { return new Promise((resolve, reject) => { @@ -97,7 +92,8 @@ module.exports = { log, warn, error, - resolveLocalDirectory, readFilePromise, - exportIndexDotHtml + exportIndexDotHtml, + cleanUpPathname, + defaultDataPaths }; diff --git a/cli/view.js b/cli/view.js index 3fa582572..3a514c0bf 100644 --- a/cli/view.js +++ b/cli/view.js @@ -11,7 +11,7 @@ const utils = require("./utils"); const version = require('../src/version').version; const chalk = require('chalk'); const SUPPRESS = require('argparse').Const.SUPPRESS; - +const { processPathArguments } = require("./server/processPaths"); const addParser = (parser) => { const description = `Launch a local server to view locally available datasets & narratives. @@ -22,13 +22,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 +49,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); - }); +function customRouteHandlers(app, handlersPath) { + utils.verbose(`Loading handlers from ${handlersPath}`); + const customCode = require(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", require("./server/getAvailable").setUpGetAvailableHandler(dataPaths)); + app.get("/charon/getDataset", require("./server/getDataset").setUpGetDatasetHandler(dataPaths)); + app.get("/charon/getNarrative", require("./server/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) + .map(([p, dataTypes]) => `${p} (${Array.from(dataTypes).join(', ')})`) + .join(sep); +} const getAuspiceBuild = (customBuildOnly) => { const cwd = path.resolve(process.cwd()); @@ -109,6 +107,8 @@ function hasAuspiceBuild(directory) { } const run = (args) => { + const dataPaths = processPathArguments(args) + /* Basic server set up */ const app = express(); app.set('port', process.env.PORT || 4000); @@ -128,9 +128,17 @@ const run = (args) => { let handlerMsg = ""; if (args.gh_pages) { handlerMsg = serveRelativeFilepaths({app, dir: path.resolve(args.gh_pages)}); + } else if (args.handlers) { + handlerMsg = 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) => { @@ -167,6 +175,9 @@ const run = (args) => { module.exports = { 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..55e82a7d1 100644 --- a/docs/introduction/how-to-run.rst +++ b/docs/introduction/how-to-run.rst @@ -22,7 +22,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 +31,9 @@ To analyse your own data, please see the tutorials on the `nextstrain docs `. From a5d0df0e14ac266ee2349707729999b76edcfeb6 Mon Sep 17 00:00:00 2001 From: james hadfield Date: Wed, 3 Jun 2026 13:05:03 +1200 Subject: [PATCH 02/18] Refactor fetch-test-data --- scripts/fetch-test-data | 54 +++++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/scripts/fetch-test-data b/scripts/fetch-test-data index 2a74ac750..7d95d331e 100755 --- a/scripts/fetch-test-data +++ b/scripts/fetch-test-data @@ -1,12 +1,50 @@ -#!/usr/bin/env bash +#!/usr/bin/env node -# Provision specific JSONs for tests +const fs = require('fs'); +const path = require('path'); -set -x -e -o pipefail +const DEST = path.join(__dirname, '..', 'test', 'fetched-jsons'); +const CHARON = 'https://nextstrain.org/charon/getDataset?prefix='; -DEST="test/fetched-jsons" -CHARON="https://nextstrain.org/charon/getDataset?prefix=" -FETCH="curl --fail --compressed" +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}, +]; -${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 +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); +}); From b6afa864d20745f1394f67b7576a1b8c0261c98c Mon Sep 17 00:00:00 2001 From: james hadfield Date: Mon, 8 Jun 2026 09:56:22 +1200 Subject: [PATCH 03/18] Update node to v24 / v26 --- .github/workflows/ci.yaml | 4 ++-- package-lock.json | 4 ++-- package.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3eca90d18..970aed5d3 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 diff --git a/package-lock.json b/package-lock.json index 119203516..27dbd65cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -130,8 +130,8 @@ "typescript": "~5.9" }, "engines": { - "node": "^20 || ^22 || ^24", - "npm": "^9 || ^10 || ^11" + "node": "^24 || ^26", + "npm": "^11" } }, "node_modules/@asamuzakjp/css-color": { diff --git a/package.json b/package.json index 82a5df6d4..3a48acd64 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "repository": "github:nextstrain/auspice", "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" From fbc6283587e75aa692e2032d2d7fcb835b464092 Mon Sep 17 00:00:00 2001 From: james hadfield Date: Wed, 3 Jun 2026 14:17:34 +1200 Subject: [PATCH 04/18] [cli] Add server fetch test for getDataset handler Stands up the default getDataset handler on an ephemeral port and makes real HTTP requests against the JSONs in test/fetched-jsons, covering both the main (v2) dataset and the tip-frequencies sidecar. Co-Authored-By: Claude Opus 4.8 --- test/server-fetch.test.js | 86 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 test/server-fetch.test.js diff --git a/test/server-fetch.test.js b/test/server-fetch.test.js new file mode 100644 index 000000000..ede7c7c76 --- /dev/null +++ b/test/server-fetch.test.js @@ -0,0 +1,86 @@ +/** + * 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). + */ + +const express = require("express"); +const fs = require("fs"); +const path = require("path"); +const { setUpGetDatasetHandler } = require("../cli/server/getDataset"); + +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); + }); + +}); From 4afc15d5a98bfab9e34dbec5e4591d4ab321476e Mon Sep 17 00:00:00 2001 From: james hadfield Date: Mon, 8 Jun 2026 13:15:00 +1200 Subject: [PATCH 05/18] Migrate server and build tooling from CommonJS to ES Modules Convert all server-side code (cli/, auspice.js, index.js, src/version.js) and the test file from CommonJS require/module.exports syntax to ES module import/export syntax. This also addresses the downstream effects on webpack bundling for the frontend code in src/. package.json: Added "type": "module" to declare the package as ESM. This makes Node treat all .js files as ES modules by default. Replaced the "main" field with "exports" to use the modern package entry point specification. Added babel-plugin-transform-import-meta as a dev dependency for Jest compatibility. Server code (cli/, auspice.js, index.js, src/version.js): Converted all require() calls to import statements and all module.exports/exports assignments to named or default exports. For packages that use CommonJS internally (argparse, webpack config), createRequire(import.meta.url) is used to obtain a require function that can load CJS modules from an ESM context. __dirname shim: Since __dirname is not available in ES modules, files that reference it (cli/utils.js, cli/build.js, cli/develop.js, cli/view.js) now derive it via path.dirname(fileURLToPath(import.meta.url)). The test file also uses this pattern. Dynamic require(handlersPath) in cli/view.js: The user-provided custom handlers path was previously loaded with require(). This is now await import(handlersPath), which required making customRouteHandlers() and run() async functions. Config file renames: webpack.config.js and babel.config.js are renamed to .cjs since they use CommonJS syntax (module.exports) and would otherwise be treated as ESM due to the package-level "type": "module" setting. Babel transform for Jest: Jest runs tests through babel-jest which transforms ESM to CJS. However, babel leaves import.meta.url untouched, causing a runtime error when Jest executes the transformed code. Added babel-plugin-transform-import-meta (test env only) to convert import.meta.url to its CJS equivalent during testing. The test/server-fetch.test.js file itself was also converted to ESM syntax. webpack.config.cjs updates: Added resolve.fullySpecified: false (both top-level and as a per-module rule) to allow extensionless imports in the bundled frontend code -- without this, webpack enforces mandatory file extensions for all imports in "type": "module" packages. The JS/TS rule also sets type: "javascript/auto" so webpack parses source files in mixed mode, recognizing both require() and import syntax. module.hot to import.meta.webpackHot: In src/root.js and src/store.ts, the webpack HMR API was accessed via module.hot which is a CJS-only global. Replaced with import.meta.webpackHot, the ESM equivalent. The HMR accept callback in store.ts now uses dynamic import() instead of require() to reload reducers. Frontend require() calls for assets: Several React components used require() to load images (SVG, PNG, JPG). These are converted to static import statements at the top of each file, which webpack asset/resource loader handles identically. Affected files: navBar/content.js, splash/splash.js, framework/fine-print.js, framework/footer-descriptions.js, info/byline.js. Dynamic extension loading (src/util/extensions.ts): The extensions.ts file retains its synchronous require(`@extensions/...`) pattern. This works because webpack's javascript/auto parser mode (set via the module rule) handles require() at build time even in a "type": "module" package. An earlier approach using top-level await with dynamic import() caused async module semantics to cascade through the dependency graph (via util/globals.js), which broke component rendering by wrapping module exports in async module namespace objects. Co-Authored-By: Claude Opus 4.6 --- auspice.js | 14 ++++---- babel.config.js => babel.config.cjs | 3 ++ cli/build.js | 18 ++++++---- cli/convert.js | 8 ++--- cli/dev/reverse-proxy.js | 15 ++++---- cli/develop.js | 34 ++++++++++-------- cli/server/convertJsonSchemas.js | 9 ++--- cli/server/getAvailable.js | 25 +++++-------- cli/server/getDataset.js | 19 ++++------ cli/server/getDatasetHelpers.js | 27 +++++--------- cli/server/getNarrative.js | 16 ++++----- cli/server/processPaths.js | 9 ++--- cli/utils.js | 36 ++++++++----------- cli/view.js | 47 ++++++++++++++----------- docs/server/api.rst | 2 +- index.js | 12 +++---- package-lock.json | 38 ++++++++++++++++---- package.json | 4 ++- scripts/diff-lang.js | 7 ++-- scripts/extract-release-notes.js | 2 +- scripts/fetch-test-data | 7 ++-- src/components/framework/fine-print.js | 2 +- src/components/info/byline.js | 3 +- src/components/navBar/content.js | 3 +- src/components/splash/splash.js | 5 ++- src/root.js | 2 +- src/store.ts | 7 ++-- src/util/extensions.ts | 3 -- src/util/parseNarrative.js | 24 ++++--------- src/version.js | 6 +--- test/colorScales.test.js | 2 +- test/server-fetch.test.js | 11 +++--- test/smoke-test/urls.test.js | 6 ++-- webpack.config.js => webpack.config.cjs | 6 ++++ 34 files changed, 213 insertions(+), 219 deletions(-) rename babel.config.js => babel.config.cjs (93%) rename webpack.config.js => webpack.config.cjs (98%) diff --git a/auspice.js b/auspice.js index 6b352c44b..a230ca7a4 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.js"; +import * as build from "./cli/build.js"; +import * as develop from "./cli/develop.js"; +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 93% rename from babel.config.js rename to babel.config.cjs index 25c781165..ef3d88762 100644 --- a/babel.config.js +++ b/babel.config.cjs @@ -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.js index ad4cb34e0..545eadbb6 100644 --- a/cli/build.js +++ b/cli/build.js @@ -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.js"; + +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 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}); @@ -71,7 +77,7 @@ function getCustomOutputPath(extensions) { return false; } -module.exports = { +export { addParser, run }; diff --git a/cli/convert.js b/cli/convert.js index fa0ef3938..afb2429dd 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.js"; 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..10dc00956 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.js"; +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.js index 87d594704..dc69c12b0 100644 --- a/cli/develop.js +++ b/cli/develop.js @@ -1,16 +1,22 @@ /* 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 view = 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.js"; +import * as view from "./view.js"; +import { version } from '../src/version.js'; +import chalk from 'chalk'; +import { processPathArguments } from "./server/processPaths.js"; + +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 { processPathArguments } = require("./server/processPaths"); const addParser = (parser) => { const description = `Launch auspice in development mode. @@ -30,7 +36,7 @@ const addParser = (parser) => { }; -const run = (args) => { +const run = async (args) => { const dataPaths = processPathArguments(args) /* Basic server set up */ @@ -73,7 +79,7 @@ const run = (args) => { if (args.gh_pages) { handlerMsg = view.serveRelativeFilepaths({app, dir: path.resolve(args.gh_pages)}); } else if (args.handlers) { - handlerMsg = view.customRouteHandlers(app, path.resolve(args.handlers)); + handlerMsg = await view.customRouteHandlers(app, path.resolve(args.handlers)); } else { handlerMsg = view.defaultRouteHandlers({app, dataPaths}); } @@ -115,7 +121,7 @@ const run = (args) => { }; -module.exports = { +export { addParser, run }; diff --git a/cli/server/convertJsonSchemas.js b/cli/server/convertJsonSchemas.js index f95e16629..be90c4355 100644 --- a/cli/server/convertJsonSchemas.js +++ b/cli/server/convertJsonSchemas.js @@ -1,4 +1,4 @@ -const utils = require("../utils"); +import * as utils from "../utils.js"; /** 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 index 6c6d25b64..1f67d8603 100644 --- a/cli/server/getAvailable.js +++ b/cli/server/getAvailable.js @@ -1,12 +1,12 @@ -const utils = require("../utils"); -const fs = require('fs'); -const path = require("path"); -const { promisify } = require('util'); -const { findAvailableSecondTreeOptions } = require('./getDatasetHelpers'); +import * as utils from "../utils.js"; +import fs from 'fs'; +import path from "path"; +import { promisify } from 'util'; +import { findAvailableSecondTreeOptions } from './getDatasetHelpers.js'; const readdir = promisify(fs.readdir); -const getAvailableDatasets = (dir, files) => { +export const getAvailableDatasets = (dir, files) => { 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 @@ -62,7 +62,7 @@ const getAvailableDatasets = (dir, files) => { return datasets; }; -const getAvailableNarratives = (dir, files) => { +export const getAvailableNarratives = (dir, files) => { return files .filter((file) => file.endsWith(".md") && file!=="README.md") .map((file) => ({ @@ -72,7 +72,7 @@ const getAvailableNarratives = (dir, files) => { })); }; -const setUpGetAvailableHandler = (dataPaths) => { +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. @@ -103,13 +103,6 @@ const setUpGetAvailableHandler = (dataPaths) => { }; - -module.exports = { - setUpGetAvailableHandler, - getAvailableDatasets, - getAvailableNarratives, -}; - function availableResponseStructure(resources) { const datasets = resources.filter((r) => r.fileType==='dataset') .map((r) => ({request: r.request, buildUrl: r.buildUrl, secondTreeOptions: r.secondTreeOptions})); @@ -131,4 +124,4 @@ function unique(resources) { seen[r.fileType].add(r.request); return true; }) -} \ No newline at end of file +} diff --git a/cli/server/getDataset.js b/cli/server/getDataset.js index abc3929b8..788048314 100644 --- a/cli/server/getDataset.js +++ b/cli/server/getDataset.js @@ -1,9 +1,9 @@ -const { promisify } = require('util'); -const path = require("path"); -const fs = require('fs'); -const getAvailable = require("./getAvailable"); -const helpers = require("./getDatasetHelpers"); -const utils = require("../utils"); +import { promisify } from 'util'; +import path from "path"; +import fs from 'fs'; +import * as getAvailable from "./getAvailable.js"; +import * as helpers from "./getDatasetHelpers.js"; +import * as utils from "../utils.js"; const readdir = promisify(fs.readdir); @@ -11,7 +11,7 @@ const readdir = promisify(fs.readdir); * Returns a route handler which responds to requests by serving the relevant JSON file * from disk. */ -const setUpGetDatasetHandler = (dataPaths) => { +export const setUpGetDatasetHandler = (dataPaths) => { return async (req, res) => { let requestInfo; @@ -99,8 +99,3 @@ async function matchDatasetFile(dataPaths, requestInfo) { } return allAvailableDatasets; } - - -module.exports = { - setUpGetDatasetHandler -}; diff --git a/cli/server/getDatasetHelpers.js b/cli/server/getDatasetHelpers.js index f46a55928..b99a7ef16 100644 --- a/cli/server/getDatasetHelpers.js +++ b/cli/server/getDatasetHelpers.js @@ -8,13 +8,12 @@ * */ +import * as utils from "../utils.js"; +import queryString from "query-string"; +import { convertFromV1 } from "./convertJsonSchemas.js"; +import fs from "fs"; -const utils = require("../utils"); -const queryString = require("query-string"); -const convertFromV1 = require("./convertJsonSchemas").convertFromV1; -const fs = require("fs"); - -const handleError = (res, clientMsg, serverMsg="", code=500) => { +export const handleError = (res, clientMsg, serverMsg="", code=500) => { utils.warn(`${clientMsg} -- ${serverMsg}`); return res.status(code).type("text/plain").send(clientMsg); }; @@ -32,7 +31,7 @@ const splitPrefixIntoParts = (url) => url * @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) => { +export const interpretRequest = (req) => { 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"); @@ -57,7 +56,7 @@ const interpretRequest = (req) => { * Given a request for which there is no perfect match, return a close match * if one exists else return false */ -const closestMatch = (requestedDatasetParts, availableDatasets) => { +export const closestMatch = (requestedDatasetParts, availableDatasets) => { let matchingDatasets = availableDatasets; let i; const matchDatasetRequest = (d) => d.request.split("/")[i] === requestedDatasetParts[i]; @@ -77,7 +76,7 @@ const closestMatch = (requestedDatasetParts, availableDatasets) => { }; -const sendJson = async (res, info) => { +export const sendJson = async (res, info) => { 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 @@ -112,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) => { const currentDatasetUrlArr = currentDatasetUrl.split('/'); const availableTangleTreeOptions = availableDatasetUrls.filter((datasetUrl) => { @@ -142,11 +141,3 @@ const findAvailableSecondTreeOptions = (currentDatasetUrl, availableDatasetUrls) return availableTangleTreeOptions; }; - -module.exports = { - interpretRequest, - closestMatch, - handleError, - sendJson, - findAvailableSecondTreeOptions -}; diff --git a/cli/server/getNarrative.js b/cli/server/getNarrative.js index 9cd87dbf1..4cd81f8ba 100644 --- a/cli/server/getNarrative.js +++ b/cli/server/getNarrative.js @@ -1,13 +1,13 @@ -const queryString = require("query-string"); -const { promisify } = require('util'); -const fs = require("fs"); -const utils = require("../utils"); -const getAvailable = require("./getAvailable"); +import queryString from "query-string"; +import { promisify } from 'util'; +import fs from "fs"; +import * as utils from "../utils.js"; +import * as getAvailable from "./getAvailable.js"; const readdir = promisify(fs.readdir); -const setUpGetNarrativeHandler = (dataPaths) => { +export const setUpGetNarrativeHandler = (dataPaths) => { return async (req, res) => { utils.log(`GET NARRATIVE request received: ${req.url}`); const query = queryString.parse(req.url.split('?')[1]); @@ -46,7 +46,3 @@ const setUpGetNarrativeHandler = (dataPaths) => { res.status(404).type("text/plain").send(errorMessage); } }; - -module.exports = { - setUpGetNarrativeHandler, -}; diff --git a/cli/server/processPaths.js b/cli/server/processPaths.js index 7f0070315..1c07eb59a 100644 --- a/cli/server/processPaths.js +++ b/cli/server/processPaths.js @@ -1,4 +1,4 @@ -const utils = require("../utils"); +import * as utils from "../utils.js"; /** * Returns an object linking (absolute) paths to a set whose members indicate whether @@ -13,7 +13,7 @@ const utils = require("../utils"); * (via `defaultDataPaths()`), however in my (james) experience it's never worked * well an I recommend providing paths via args. */ -function processPathArguments(args) { +export function processPathArguments(args) { if (args.handlers) { if (args.datasetDir || args.narrativeDir || args.path.length>0) { @@ -70,8 +70,3 @@ function processPathArguments(args) { return dataPaths; } - - -module.exports = { - processPathArguments, -}; diff --git a/cli/utils.js b/cli/utils.js index 8c6ddafe0..88dc32c8b 100644 --- a/cli/utils.js +++ b/cli/utils.js @@ -1,20 +1,23 @@ /* eslint no-console: off */ -const fs = require('fs'); -const chalk = require('chalk'); -const path = require("path"); +import fs from 'fs'; +import chalk from 'chalk'; +import path from 'path'; +import { fileURLToPath } from 'url'; -const verbose = (msg) => { +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +export const verbose = (msg) => { if (global.AUSPICE_VERBOSE) { console.log(chalk.greenBright(`[verbose]\t${msg}`)); } }; -const log = (msg) => { +export const log = (msg) => { console.log(chalk.blueBright(msg)); }; -const warn = (msg) => { +export const warn = (msg) => { console.warn(chalk.yellowBright(`[warning]\t${msg}`)); }; -const error = (msg) => { +export const error = (msg) => { console.error(chalk.redBright(`[error]\t${msg}`)); process.exit(2); }; @@ -23,7 +26,7 @@ const isNpmGlobalInstall = () => { return __dirname.indexOf("lib/node_modules/auspice") !== -1; }; -const cleanUpPathname = (pathIn) => { +export const cleanUpPathname = (pathIn) => { let pathOut = pathIn; if (!pathOut.endsWith("/")) pathOut += "/"; if (pathOut.startsWith("~")) { @@ -47,13 +50,13 @@ const getCurrentDirectoriesFor = (type) => { }; -const defaultDataPaths = ({narrative=false} = {}) => { +export const defaultDataPaths = ({narrative=false} = {}) => { return isNpmGlobalInstall() ? [getCurrentDirectoriesFor(narrative ? "narratives" : "data")] : [path.join(path.resolve(__dirname), "..", narrative ? "narratives" : "data")]; } -const readFilePromise = (fileName) => { +export const readFilePromise = (fileName) => { return new Promise((resolve, reject) => { fs.readFile(fileName, 'utf8', (err, data) => { if (err) { @@ -71,7 +74,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}) => { if (path.resolve(__dirname, "..") === process.cwd()) { warn("Cannot export index.html to the auspice source directory."); return; @@ -86,14 +89,3 @@ const exportIndexDotHtml = ({relative=false}) => { } fs.writeFileSync(outputFilePath, data); }; - -module.exports = { - verbose, - log, - warn, - error, - readFilePromise, - exportIndexDotHtml, - cleanUpPathname, - defaultDataPaths -}; diff --git a/cli/view.js b/cli/view.js index 3a514c0bf..9f5af9b4a 100644 --- a/cli/view.js +++ b/cli/view.js @@ -1,17 +1,24 @@ /* 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'); + +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.js"; +import { version } from '../src/version.js'; +import chalk from 'chalk'; +import { processPathArguments } from "./server/processPaths.js"; +import { setUpGetAvailableHandler } from "./server/getAvailable.js"; +import { setUpGetDatasetHandler } from "./server/getDataset.js"; +import { setUpGetNarrativeHandler } from "./server/getNarrative.js"; + +const require = createRequire(import.meta.url); +const __dirname = path.dirname(fileURLToPath(import.meta.url)); const SUPPRESS = require('argparse').Const.SUPPRESS; -const { processPathArguments } = require("./server/processPaths"); const addParser = (parser) => { const description = `Launch a local server to view locally available datasets & narratives. @@ -49,9 +56,9 @@ const serveRelativeFilepaths = ({app, dir}) => { return `JSON requests will be served relative to ${dir}.`; }; -function customRouteHandlers(app, handlersPath) { +async function customRouteHandlers(app, handlersPath) { utils.verbose(`Loading handlers from ${handlersPath}`); - const customCode = require(handlersPath); + const customCode = await import(handlersPath); app.get("/charon/getAvailable", customCode.getAvailable); app.get("/charon/getDataset", customCode.getDataset); app.get("/charon/getNarrative", customCode.getNarrative); @@ -62,9 +69,9 @@ function customRouteHandlers(app, handlersPath) { * Adds route handlers for the three canonical charon routes to the *app* */ function defaultRouteHandlers({app, dataPaths}) { - app.get("/charon/getAvailable", require("./server/getAvailable").setUpGetAvailableHandler(dataPaths)); - app.get("/charon/getDataset", require("./server/getDataset").setUpGetDatasetHandler(dataPaths)); - app.get("/charon/getNarrative", require("./server/getNarrative").setUpGetNarrativeHandler(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 + @@ -106,7 +113,7 @@ function hasAuspiceBuild(directory) { ) } -const run = (args) => { +const run = async (args) => { const dataPaths = processPathArguments(args) /* Basic server set up */ @@ -129,7 +136,7 @@ const run = (args) => { if (args.gh_pages) { handlerMsg = serveRelativeFilepaths({app, dir: path.resolve(args.gh_pages)}); } else if (args.handlers) { - handlerMsg = customRouteHandlers(app, path.resolve(args.handlers)); + handlerMsg = await customRouteHandlers(app, path.resolve(args.handlers)); } else { handlerMsg = defaultRouteHandlers({app, dataPaths}); } @@ -172,7 +179,7 @@ const run = (args) => { }; -module.exports = { +export { addParser, run, addDatasetNarrativePathArgs, diff --git a/docs/server/api.rst b/docs/server/api.rst index 9201a7fb8..a85c7279a 100644 --- a/docs/server/api.rst +++ b/docs/server/api.rst @@ -142,7 +142,7 @@ Currently .. code:: js - const auspice = require("auspice"); + import auspice from "auspice"; returns an object with two properties: diff --git a/index.js b/index.js index fe9962920..749ffe267 100644 --- a/index.js +++ b/index.js @@ -1,16 +1,12 @@ /** 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 }; +export const parseNarrativeFile = undefined; diff --git a/package-lock.json b/package-lock.json index 27dbd65cc..fc1d19de1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -117,6 +117,7 @@ "@types/webpack-env": "^1.18.2", "@typescript-eslint/eslint-plugin": "^5.57.0", "@typescript-eslint/parser": "^5.57.0", + "babel-plugin-transform-import-meta": "^2.3.3", "chai": "^4.1.2", "chai-http": "^4.0.0", "eslint": "^8.39.0", @@ -8312,6 +8313,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", @@ -17297,9 +17312,10 @@ } }, "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", @@ -22940,6 +22956,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", @@ -29471,9 +29497,9 @@ } }, "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", diff --git a/package.json b/package.json index 3a48acd64..b119f613c 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "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": "^24 || ^26", @@ -13,7 +14,7 @@ "bin": { "auspice": "./auspice.js" }, - "main": "index.js", + "exports": "./index.js", "scripts": { "view": "node auspice.js view --verbose", "dev": "node auspice.js develop --verbose", @@ -139,6 +140,7 @@ "@types/webpack-env": "^1.18.2", "@typescript-eslint/eslint-plugin": "^5.57.0", "@typescript-eslint/parser": "^5.57.0", + "babel-plugin-transform-import-meta": "^2.3.3", "chai": "^4.1.2", "chai-http": "^4.0.0", "eslint": "^8.39.0", 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 7d95d331e..b1346db19 100755 --- a/scripts/fetch-test-data +++ b/scripts/fetch-test-data @@ -1,7 +1,10 @@ #!/usr/bin/env node -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)); const DEST = path.join(__dirname, '..', 'test', 'fetched-jsons'); const CHARON = 'https://nextstrain.org/charon/getDataset?prefix='; 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..cb695d2b9 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); 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/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/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..f5cb3d5bc 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`; } @@ -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/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/server-fetch.test.js b/test/server-fetch.test.js index ede7c7c76..05326624c 100644 --- a/test/server-fetch.test.js +++ b/test/server-fetch.test.js @@ -13,10 +13,13 @@ * (see src/actions/loadData.js). */ -const express = require("express"); -const fs = require("fs"); -const path = require("path"); -const { setUpGetDatasetHandler } = require("../cli/server/getDataset"); +import express from "express"; +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { setUpGetDatasetHandler } from "../cli/server/getDataset.js"; + +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"); 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/webpack.config.js b/webpack.config.cjs similarity index 98% rename from webpack.config.js rename to webpack.config.cjs index f6a4168c3..83b1f4b6a 100644 --- a/webpack.config.js +++ b/webpack.config.cjs @@ -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', From bf764dcd7ea73c6c0288f488c2cef1c72fb243a7 Mon Sep 17 00:00:00 2001 From: james hadfield Date: Mon, 8 Jun 2026 14:41:24 +1200 Subject: [PATCH 06/18] [cli] Migrate server files from JavaScript to TypeScript Convert 8 cli files from .js to .ts, leveraging Node 24's native type stripping to run them directly without a build step. tsc is used for type-checking only (noEmit). Files converted: - cli/utils.ts - cli/server/getDataset.ts - cli/server/getDatasetHelpers.ts - cli/server/getAvailable.ts - cli/server/getNarrative.ts - cli/server/processPaths.ts - cli/view.ts - cli/develop.ts - cli/build.ts Supporting changes: - Add cli/tsconfig.json extending root with nodenext module resolution and allowImportingTsExtensions for Node-native .ts imports - Add allowImportingTsExtensions to root tsconfig.json (needed because cli .ts files are pulled in transitively via test imports) - Exclude cli/**/* from root tsconfig include/add to exclude (prevents checking cli files under the wrong moduleResolution) - Update package.json type-check script to run both tsconfigs - Update all import paths across 11 files (.js -> .ts extensions) - Update webpack.config.cjs require for cli/utils.ts - Add eslint-disable for explicit-function-return-type and consistent-type-assertions in converted files (incremental migration) - Apply chalk 2.x workaround (type assertion) for nodenext compat - Fix TypeScript strict errors: unknown in catch blocks, Object.entries casting for Set methods, undefined-in-object check Co-Authored-By: Claude Opus 4.6 --- auspice.js | 6 +++--- babel.config.cjs | 2 +- cli/{build.js => build.ts} | 4 ++-- cli/convert.js | 2 +- cli/dev/reverse-proxy.js | 2 +- cli/{develop.js => develop.ts} | 20 ++++++++++--------- cli/server/convertJsonSchemas.js | 2 +- .../{getAvailable.js => getAvailable.ts} | 8 ++++---- cli/server/{getDataset.js => getDataset.ts} | 15 +++++++------- ...DatasetHelpers.js => getDatasetHelpers.ts} | 4 ++-- .../{getNarrative.js => getNarrative.ts} | 8 ++++---- .../{processPaths.js => processPaths.ts} | 4 ++-- cli/tsconfig.json | 12 +++++++++++ cli/{utils.js => utils.ts} | 17 +++++++++------- cli/{view.js => view.ts} | 20 ++++++++++--------- package.json | 4 ++-- test/server-fetch.test.js | 2 +- tsconfig.json | 4 ++++ webpack.config.cjs | 2 +- 19 files changed, 81 insertions(+), 57 deletions(-) rename cli/{build.js => build.ts} (97%) rename cli/{develop.js => develop.ts} (90%) rename cli/server/{getAvailable.js => getAvailable.ts} (95%) rename cli/server/{getDataset.js => getDataset.ts} (87%) rename cli/server/{getDatasetHelpers.js => getDatasetHelpers.ts} (97%) rename cli/server/{getNarrative.js => getNarrative.ts} (89%) rename cli/server/{processPaths.js => processPaths.ts} (97%) create mode 100644 cli/tsconfig.json rename cli/{utils.js => utils.ts} (83%) rename cli/{view.js => view.ts} (93%) diff --git a/auspice.js b/auspice.js index a230ca7a4..292d4058d 100755 --- a/auspice.js +++ b/auspice.js @@ -2,9 +2,9 @@ import argparse from 'argparse'; import { version } from './src/version.js'; -import * as view from "./cli/view.js"; -import * as build from "./cli/build.js"; -import * as develop from "./cli/develop.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({ diff --git a/babel.config.cjs b/babel.config.cjs index ef3d88762..6f1bd17b0 100644 --- a/babel.config.cjs +++ 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 diff --git a/cli/build.js b/cli/build.ts similarity index 97% rename from cli/build.js rename to cli/build.ts index 545eadbb6..53f0bee14 100644 --- a/cli/build.js +++ b/cli/build.ts @@ -2,7 +2,7 @@ import webpack from "webpack"; import path from "path"; import { fileURLToPath } from 'url'; import { createRequire } from 'module'; -import * as utils from "./utils.js"; +import * as utils from "./utils.ts"; const require = createRequire(import.meta.url); const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -10,7 +10,7 @@ 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. diff --git a/cli/convert.js b/cli/convert.js index afb2429dd..b9335f00d 100644 --- a/cli/convert.js +++ b/cli/convert.js @@ -1,7 +1,7 @@ /* eslint no-console: off */ import fs from "fs"; import { convertFromV1 } from "./server/convertJsonSchemas.js"; -import * as utils from "./utils.js"; +import * as utils from "./utils.ts"; const addParser = (parser) => { diff --git a/cli/dev/reverse-proxy.js b/cli/dev/reverse-proxy.js index 10dc00956..8c349b82c 100644 --- a/cli/dev/reverse-proxy.js +++ b/cli/dev/reverse-proxy.js @@ -1,4 +1,4 @@ -import * as utils from "../utils.js"; +import * as utils from "../utils.ts"; import { URL } from "url"; import fetch from "node-fetch"; diff --git a/cli/develop.js b/cli/develop.ts similarity index 90% rename from cli/develop.js rename to cli/develop.ts index dc69c12b0..1060ec269 100644 --- a/cli/develop.js +++ b/cli/develop.ts @@ -6,11 +6,13 @@ 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.js"; -import * as view from "./view.js"; +import * as utils from "./utils.ts"; +import * as view from "./view.ts"; import { version } from '../src/version.js'; -import chalk from 'chalk'; -import { processPathArguments } from "./server/processPaths.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)); @@ -18,7 +20,7 @@ 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. @@ -36,7 +38,7 @@ const addParser = (parser) => { }; -const run = async (args) => { +const run = async (args): Promise => { const dataPaths = processPathArguments(args) /* Basic server set up */ @@ -46,7 +48,7 @@ const run = async (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; @@ -59,7 +61,7 @@ const run = async (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; } @@ -90,7 +92,7 @@ const run = async (args) => { 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---------------------------------------------------"); diff --git a/cli/server/convertJsonSchemas.js b/cli/server/convertJsonSchemas.js index be90c4355..d36bb102f 100644 --- a/cli/server/convertJsonSchemas.js +++ b/cli/server/convertJsonSchemas.js @@ -1,4 +1,4 @@ -import * as utils from "../utils.js"; +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 diff --git a/cli/server/getAvailable.js b/cli/server/getAvailable.ts similarity index 95% rename from cli/server/getAvailable.js rename to cli/server/getAvailable.ts index 1f67d8603..7063e5b8a 100644 --- a/cli/server/getAvailable.js +++ b/cli/server/getAvailable.ts @@ -1,8 +1,8 @@ -import * as utils from "../utils.js"; +import * as utils from "../utils.ts"; import fs from 'fs'; import path from "path"; import { promisify } from 'util'; -import { findAvailableSecondTreeOptions } from './getDatasetHelpers.js'; +import { findAvailableSecondTreeOptions } from './getDatasetHelpers.ts'; const readdir = promisify(fs.readdir); @@ -77,10 +77,10 @@ export const setUpGetAvailableHandler = (dataPaths) => { * Remember that auspice itself only serves local files. * Servers often use their own handler instead of this. */ - return async (req, res) => { + return async (_req, res) => { utils.log("GET AVAILABLE returning locally available datasets & narratives"); let resources = [] - for (const [p, dataTypes] of Object.entries(dataPaths)) { + 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); diff --git a/cli/server/getDataset.js b/cli/server/getDataset.ts similarity index 87% rename from cli/server/getDataset.js rename to cli/server/getDataset.ts index 788048314..b7e90448b 100644 --- a/cli/server/getDataset.js +++ b/cli/server/getDataset.ts @@ -1,9 +1,9 @@ import { promisify } from 'util'; import path from "path"; import fs from 'fs'; -import * as getAvailable from "./getAvailable.js"; -import * as helpers from "./getDatasetHelpers.js"; -import * as utils from "../utils.js"; +import * as getAvailable from "./getAvailable.ts"; +import * as helpers from "./getDatasetHelpers.ts"; +import * as utils from "../utils.ts"; const readdir = promisify(fs.readdir); @@ -18,7 +18,8 @@ export const setUpGetDatasetHandler = (dataPaths) => { try { requestInfo = helpers.interpretRequest(req); } catch (err) { - return helpers.handleError(res, err.message, err.message, 400); + const msg = err instanceof Error ? err.message : String(err); + return helpers.handleError(res, msg, msg, 400); } const matchResult = await matchDatasetFile(dataPaths, requestInfo); @@ -65,13 +66,13 @@ async function matchDatasetFile(dataPaths, requestInfo) { * (this "first match wins" approach mirrors that of `getAvailable`) */ const allAvailableDatasets = [] - for (const [dir, dataTypes] of Object.entries(dataPaths)) { + for (const [dir, dataTypes] of Object.entries(dataPaths) as [string, Set][]) { if (!dataTypes.has('datasets')) continue; - let files = []; + let files: string[] = []; try { files = await readdir(dir); } catch (err) { - utils.warn(`Error reading datasets from ${dir}: ${err.message}`) + utils.warn(`Error reading datasets from ${dir}: ${err instanceof Error ? err.message : err}`) } if (requestInfo.dataType==='dataset') { diff --git a/cli/server/getDatasetHelpers.js b/cli/server/getDatasetHelpers.ts similarity index 97% rename from cli/server/getDatasetHelpers.js rename to cli/server/getDatasetHelpers.ts index b99a7ef16..5f9b2ae98 100644 --- a/cli/server/getDatasetHelpers.js +++ b/cli/server/getDatasetHelpers.ts @@ -8,7 +8,7 @@ * */ -import * as utils from "../utils.js"; +import * as utils from "../utils.ts"; import queryString from "query-string"; import { convertFromV1 } from "./convertJsonSchemas.js"; import fs from "fs"; @@ -36,7 +36,7 @@ export const interpretRequest = (req) => { 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 */ diff --git a/cli/server/getNarrative.js b/cli/server/getNarrative.ts similarity index 89% rename from cli/server/getNarrative.js rename to cli/server/getNarrative.ts index 4cd81f8ba..7afa8a637 100644 --- a/cli/server/getNarrative.js +++ b/cli/server/getNarrative.ts @@ -1,8 +1,8 @@ import queryString from "query-string"; import { promisify } from 'util'; import fs from "fs"; -import * as utils from "../utils.js"; -import * as getAvailable from "./getAvailable.js"; +import * as utils from "../utils.ts"; +import * as getAvailable from "./getAvailable.ts"; const readdir = promisify(fs.readdir); @@ -23,7 +23,7 @@ export const setUpGetNarrativeHandler = (dataPaths) => { .replace(/^\//, "") // remove leading slash .replace(/\/$/, "") // remove ending slash - for (const [p, dataTypes] of Object.entries(dataPaths)) { + for (const [p, dataTypes] of Object.entries(dataPaths) as [string, Set][]) { if (!dataTypes.has('narratives')) continue; try { const files = await readdir(p); @@ -36,7 +36,7 @@ export const setUpGetNarrativeHandler = (dataPaths) => { } // else go scan the next dataPaths (directory) } catch (err) { - const errorMessage = `Narratives couldn't be served -- ${err.message}`; + 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); } diff --git a/cli/server/processPaths.js b/cli/server/processPaths.ts similarity index 97% rename from cli/server/processPaths.js rename to cli/server/processPaths.ts index 1c07eb59a..d88b2462f 100644 --- a/cli/server/processPaths.js +++ b/cli/server/processPaths.ts @@ -1,4 +1,4 @@ -import * as utils from "../utils.js"; +import * as utils from "../utils.ts"; /** * Returns an object linking (absolute) paths to a set whose members indicate whether @@ -64,7 +64,7 @@ export function processPathArguments(args) { } } - if (undefined in dataPaths) { + if ("undefined" in dataPaths) { utils.error("One or more provided paths does not exist") } 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 83% rename from cli/utils.js rename to cli/utils.ts index 88dc32c8b..39a6fcc82 100644 --- a/cli/utils.js +++ b/cli/utils.ts @@ -1,28 +1,31 @@ /* eslint no-console: off */ import fs from 'fs'; -import chalk from 'chalk'; +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 __dirname = path.dirname(fileURLToPath(import.meta.url)); -export const verbose = (msg) => { +export const verbose = (msg): void => { if (global.AUSPICE_VERBOSE) { console.log(chalk.greenBright(`[verbose]\t${msg}`)); } }; -export const log = (msg) => { +export const log = (msg): void => { console.log(chalk.blueBright(msg)); }; -export const warn = (msg) => { +export const warn = (msg): void => { console.warn(chalk.yellowBright(`[warning]\t${msg}`)); }; -export const error = (msg) => { +export const error = (msg): void => { console.error(chalk.redBright(`[error]\t${msg}`)); process.exit(2); }; -const isNpmGlobalInstall = () => { +const isNpmGlobalInstall = (): boolean => { return __dirname.indexOf("lib/node_modules/auspice") !== -1; }; @@ -74,7 +77,7 @@ export 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) */ -export 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; diff --git a/cli/view.js b/cli/view.ts similarity index 93% rename from cli/view.js rename to cli/view.ts index 9f5af9b4a..a218bf620 100644 --- a/cli/view.js +++ b/cli/view.ts @@ -1,4 +1,5 @@ /* eslint no-console: off */ +/* eslint-disable @typescript-eslint/explicit-function-return-type, @typescript-eslint/consistent-type-assertions */ import path from "path"; import fs from "fs"; @@ -8,13 +9,14 @@ 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.js"; +import * as utils from "./utils.ts"; import { version } from '../src/version.js'; -import chalk from 'chalk'; -import { processPathArguments } from "./server/processPaths.js"; -import { setUpGetAvailableHandler } from "./server/getAvailable.js"; -import { setUpGetDatasetHandler } from "./server/getDataset.js"; -import { setUpGetNarrativeHandler } from "./server/getNarrative.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)); @@ -75,7 +77,7 @@ function defaultRouteHandlers({app, 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) + (Object.entries(dataPaths) as [string, Set][]) .map(([p, dataTypes]) => `${p} (${Array.from(dataTypes).join(', ')})`) .join(sep); } @@ -126,7 +128,7 @@ const run = async (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, @@ -148,7 +150,7 @@ const run = async (args) => { }); /* 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"}}); }); diff --git a/package.json b/package.json index b119f613c..579fdadc7 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,8 @@ "prepare": "npm run build", "lint": "eslint --max-warnings=0 .", "lint:fix": "eslint --fix .", - "type-check": "tsc", - "type-check:watch": "npm run type-check -- --watch", + "type-check": "tsc && tsc --project cli/tsconfig.json", + "type-check:watch": "tsc --watch", "get-data": "env bash ./scripts/get-data.sh", "fetch-test-data": "./scripts/fetch-test-data", "heroku-postbuild": "npm run build && npm run get-data", diff --git a/test/server-fetch.test.js b/test/server-fetch.test.js index 05326624c..da163d6c3 100644 --- a/test/server-fetch.test.js +++ b/test/server-fetch.test.js @@ -17,7 +17,7 @@ import express from "express"; import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; -import { setUpGetDatasetHandler } from "../cli/server/getDataset.js"; +import { setUpGetDatasetHandler } from "../cli/server/getDataset.ts"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); 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.cjs b/webpack.config.cjs index 83b1f4b6a..3b1329f19 100644 --- a/webpack.config.cjs +++ 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'); From 31a91fcfb2b73519e6c8f747945913bdd2805729 Mon Sep 17 00:00:00 2001 From: james hadfield Date: Tue, 9 Jun 2026 12:56:50 +1200 Subject: [PATCH 07/18] [cli] update api code/docs The docs and code had fallen out of sync over the years. * `parseNarrativeFile` was not only deprecated as the docs indicated ("This function is deprecated as of vXXX"), it no longer existed in the codebase. * The docs for the getNarrative route incorrectly indicated the `type` query as optional --- docs/server/api.rst | 54 +++++---------------------------------------- index.js | 1 - 2 files changed, 5 insertions(+), 50 deletions(-) diff --git a/docs/server/api.rst b/docs/server/api.rst index a85c7279a..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/index.js b/index.js index 749ffe267..be52de20b 100644 --- a/index.js +++ b/index.js @@ -9,4 +9,3 @@ import { convertFromV1 } from "./cli/server/convertJsonSchemas.js"; export { convertFromV1 }; -export const parseNarrativeFile = undefined; From 7b0af966af71b758b576f1832c6beffc59209cfb Mon Sep 17 00:00:00 2001 From: james hadfield Date: Mon, 8 Jun 2026 17:52:08 +1200 Subject: [PATCH 08/18] [cli] Add explicit return types Co-Authored-By: Claude Opus 4.6 --- cli/build.ts | 4 ++-- cli/server/getAvailable.ts | 32 ++++++++++++++++++++++++-------- cli/server/getDataset.ts | 6 ++++-- cli/server/getDatasetHelpers.ts | 14 +++++++------- cli/server/getNarrative.ts | 3 ++- cli/server/processPaths.ts | 8 +++++--- cli/utils.ts | 10 +++++----- 7 files changed, 49 insertions(+), 28 deletions(-) diff --git a/cli/build.ts b/cli/build.ts index 53f0bee14..c86e0267f 100644 --- a/cli/build.ts +++ b/cli/build.ts @@ -30,7 +30,7 @@ const addParser = (parser): void => { 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; @@ -70,7 +70,7 @@ 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(); } diff --git a/cli/server/getAvailable.ts b/cli/server/getAvailable.ts index 7063e5b8a..888a2d689 100644 --- a/cli/server/getAvailable.ts +++ b/cli/server/getAvailable.ts @@ -6,7 +6,22 @@ import { findAvailableSecondTreeOptions } from './getDatasetHelpers.ts'; const readdir = promisify(fs.readdir); -export const getAvailableDatasets = (dir, files) => { +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 @@ -62,7 +77,7 @@ export const getAvailableDatasets = (dir, files) => { return datasets; }; -export const getAvailableNarratives = (dir, files) => { +export const getAvailableNarratives = (dir, files): NarrativeInfo[] => { return files .filter((file) => file.endsWith(".md") && file!=="README.md") .map((file) => ({ @@ -77,9 +92,10 @@ export const setUpGetAvailableHandler = (dataPaths) => { * Remember that auspice itself only serves local files. * Servers often use their own handler instead of this. */ - return async (_req, res) => { + return async (_req, res): Promise => { utils.log("GET AVAILABLE returning locally available datasets & narratives"); - let resources = [] + 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) @@ -103,10 +119,10 @@ export const setUpGetAvailableHandler = (dataPaths) => { }; -function availableResponseStructure(resources) { - const datasets = resources.filter((r) => r.fileType==='dataset') +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.fileType==='narrative') + const narratives = resources.filter((r): r is NarrativeInfo => r.fileType==='narrative') .map((r) => ({request: r.request})); return {datasets, narratives}; } @@ -114,7 +130,7 @@ function availableResponseStructure(resources) { /** * First match wins */ -function unique(resources) { +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)) { diff --git a/cli/server/getDataset.ts b/cli/server/getDataset.ts index b7e90448b..e9a2b5331 100644 --- a/cli/server/getDataset.ts +++ b/cli/server/getDataset.ts @@ -2,6 +2,7 @@ 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"; @@ -12,7 +13,7 @@ const readdir = promisify(fs.readdir); * from disk. */ export const setUpGetDatasetHandler = (dataPaths) => { - return async (req, res) => { + return async (req, res): Promise => { let requestInfo; try { @@ -60,12 +61,13 @@ const SIDECARS = { * - 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) { +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[] = []; diff --git a/cli/server/getDatasetHelpers.ts b/cli/server/getDatasetHelpers.ts index 5f9b2ae98..74fcd3da3 100644 --- a/cli/server/getDatasetHelpers.ts +++ b/cli/server/getDatasetHelpers.ts @@ -13,12 +13,12 @@ import queryString from "query-string"; import { convertFromV1 } from "./convertJsonSchemas.js"; import fs from "fs"; -export 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("/"); @@ -31,7 +31,7 @@ const splitPrefixIntoParts = (url) => url * @returns {string[]} ret.parts is the dataset name spit on '/' character * @returns {string} ret.dataType is the dataset type ('dataset', 'root-sequence' etc) */ -export const interpretRequest = (req) => { +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"); @@ -56,10 +56,10 @@ export const interpretRequest = (req) => { * Given a request for which there is no perfect match, return a close match * if one exists else return false */ -export const closestMatch = (requestedDatasetParts, availableDatasets) => { +export const closestMatch = (requestedDatasetParts, availableDatasets): string | false => { let matchingDatasets = availableDatasets; let i; - const matchDatasetRequest = (d) => d.request.split("/")[i] === requestedDatasetParts[i]; + const matchDatasetRequest = (d): boolean => d.request.split("/")[i] === requestedDatasetParts[i]; // Filter gradually by path fragment, starting from the root for (i = 0; i < requestedDatasetParts.length; i++) { const newMatchingDatasets = matchingDatasets.filter(matchDatasetRequest); @@ -76,7 +76,7 @@ export const closestMatch = (requestedDatasetParts, availableDatasets) => { }; -export 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 @@ -111,7 +111,7 @@ export 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 `"/"` */ -export const findAvailableSecondTreeOptions = (currentDatasetUrl, availableDatasetUrls) => { +export const findAvailableSecondTreeOptions = (currentDatasetUrl, availableDatasetUrls): string[] => { const currentDatasetUrlArr = currentDatasetUrl.split('/'); const availableTangleTreeOptions = availableDatasetUrls.filter((datasetUrl) => { diff --git a/cli/server/getNarrative.ts b/cli/server/getNarrative.ts index 7afa8a637..6fe7fd39c 100644 --- a/cli/server/getNarrative.ts +++ b/cli/server/getNarrative.ts @@ -8,7 +8,7 @@ const readdir = promisify(fs.readdir); export const setUpGetNarrativeHandler = (dataPaths) => { - return async (req, res) => { + 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; @@ -23,6 +23,7 @@ export const setUpGetNarrativeHandler = (dataPaths) => { .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 { diff --git a/cli/server/processPaths.ts b/cli/server/processPaths.ts index d88b2462f..a319ca1a5 100644 --- a/cli/server/processPaths.ts +++ b/cli/server/processPaths.ts @@ -1,5 +1,7 @@ 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. @@ -13,7 +15,7 @@ import * as utils from "../utils.ts"; * (via `defaultDataPaths()`), however in my (james) experience it's never worked * well an I recommend providing paths via args. */ -export function processPathArguments(args) { +export function processPathArguments(args): Record { if (args.handlers) { if (args.datasetDir || args.narrativeDir || args.path.length>0) { @@ -42,13 +44,13 @@ export function processPathArguments(args) { utils.warn(`[DEPRECATED] Instead of 'auspice ... --narrativeDir ${args.narrativeDir}' please use 'auspice ... ${args.narrativeDir}' `) } - const dataPaths = Object.fromEntries( + const dataPaths: Record = Object.fromEntries( (args.path.length ? args.path.map(utils.cleanUpPathname) : args.datasetDir ? [utils.cleanUpPathname(args.datasetDir)] : utils.defaultDataPaths() - ).map((p) => [p, new Set(['datasets'])]) + ).map((p: string) => [p, new Set(['datasets'])]) ); for (const narrativePath of diff --git a/cli/utils.ts b/cli/utils.ts index 39a6fcc82..58546e8a2 100644 --- a/cli/utils.ts +++ b/cli/utils.ts @@ -20,7 +20,7 @@ export const log = (msg): void => { export const warn = (msg): void => { console.warn(chalk.yellowBright(`[warning]\t${msg}`)); }; -export const error = (msg): void => { +export const error = (msg): never => { console.error(chalk.redBright(`[error]\t${msg}`)); process.exit(2); }; @@ -29,7 +29,7 @@ const isNpmGlobalInstall = (): boolean => { return __dirname.indexOf("lib/node_modules/auspice") !== -1; }; -export const cleanUpPathname = (pathIn) => { +export const cleanUpPathname = (pathIn): string | undefined => { let pathOut = pathIn; if (!pathOut.endsWith("/")) pathOut += "/"; if (pathOut.startsWith("~")) { @@ -43,7 +43,7 @@ export const cleanUpPathname = (pathIn) => { return pathOut; }; -const getCurrentDirectoriesFor = (type) => { +const getCurrentDirectoriesFor = (type): string | undefined => { const cwd = process.cwd(); const folderName = type === "data" ? "auspice" : "narratives"; if (fs.existsSync(path.join(cwd, folderName))) { @@ -53,13 +53,13 @@ const getCurrentDirectoriesFor = (type) => { }; -export const defaultDataPaths = ({narrative=false} = {}) => { +export const defaultDataPaths = ({narrative=false} = {}): (string | undefined)[] => { return isNpmGlobalInstall() ? [getCurrentDirectoriesFor(narrative ? "narratives" : "data")] : [path.join(path.resolve(__dirname), "..", narrative ? "narratives" : "data")]; } -export const readFilePromise = (fileName) => { +export const readFilePromise = (fileName): Promise => { return new Promise((resolve, reject) => { fs.readFile(fileName, 'utf8', (err, data) => { if (err) { From 7c153eab4ec6070adab924442886cc59d5304c28 Mon Sep 17 00:00:00 2001 From: james hadfield Date: Mon, 8 Jun 2026 18:34:17 +1200 Subject: [PATCH 09/18] [dev] Update eslint Triggered by existing warnings: WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree. --- .eslintrc.yaml | 2 +- package-lock.json | 1330 ++++++++++++++++++++++----------------------- package.json | 6 +- 3 files changed, 663 insertions(+), 675 deletions(-) diff --git a/.eslintrc.yaml b/.eslintrc.yaml index b8b666983..6f61a78ba 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -32,7 +32,7 @@ rules: "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "destructuredArrayIgnorePattern": "^_" }] 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/package-lock.json b/package-lock.json index fc1d19de1..90bd5911b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -115,12 +115,12 @@ "@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", + "@typescript-eslint/eslint-plugin": "^8.60.1", + "@typescript-eslint/parser": "^8.60.1", "babel-plugin-transform-import-meta": "^2.3.3", "chai": "^4.1.2", "chai-http": "^4.0.0", - "eslint": "^8.39.0", + "eslint": "^8.57.1", "eslint-plugin-react": "^7.2.1", "eslint-plugin-react-hooks": "^4.0.1", "jest": "^29.5.0", @@ -2102,50 +2102,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", @@ -2164,13 +2158,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" }, @@ -2181,11 +2177,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" @@ -2199,6 +2215,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" }, @@ -2207,10 +2224,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" } @@ -2230,13 +2248,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": { @@ -2257,10 +2277,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", @@ -7167,12 +7189,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", @@ -7284,130 +7300,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", @@ -7415,65 +7450,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": { @@ -7484,55 +7530,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", @@ -7540,17 +7572,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", @@ -7750,6 +7790,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" } @@ -9615,9 +9656,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" }, @@ -9658,7 +9700,8 @@ "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", @@ -9757,18 +9800,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", @@ -10153,27 +10184,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", @@ -10181,22 +10215,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": { @@ -10308,6 +10339,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", @@ -10385,10 +10429,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" @@ -10400,23 +10445,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" } @@ -10461,6 +10495,16 @@ "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": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -10483,19 +10527,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", @@ -10511,23 +10542,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", @@ -10558,15 +10572,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", @@ -10591,18 +10596,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", @@ -10616,27 +10609,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" }, @@ -10909,34 +10891,6 @@ "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", @@ -11484,11 +11438,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", @@ -11816,10 +11771,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" } @@ -14249,16 +14205,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", @@ -14438,6 +14384,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", @@ -14659,15 +14619,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", @@ -14852,12 +14803,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", @@ -15172,6 +15117,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", @@ -15566,6 +15529,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", @@ -17187,6 +17160,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", @@ -17254,6 +17275,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", @@ -17317,27 +17351,19 @@ "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", @@ -19546,37 +19572,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", @@ -19592,18 +19610,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" } }, + "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.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, "requires": { "argparse": "^2.0.1" @@ -19618,9 +19642,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": { @@ -19635,13 +19659,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" } }, @@ -19652,9 +19676,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": { @@ -22114,12 +22138,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", @@ -22226,156 +22244,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", @@ -23930,9 +23958,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" } @@ -24027,15 +24055,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", @@ -24323,27 +24342,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", @@ -24351,22 +24371,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": { @@ -24423,21 +24440,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", @@ -24469,6 +24480,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", @@ -24484,16 +24501,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", @@ -24503,20 +24510,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", @@ -24535,12 +24528,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", @@ -24559,15 +24546,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", @@ -24651,23 +24629,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": { @@ -24862,30 +24838,6 @@ "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", @@ -25272,10 +25224,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": { @@ -25499,9 +25451,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": { @@ -27273,12 +27225,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", @@ -27403,6 +27349,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", @@ -27579,12 +27535,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", @@ -27721,12 +27671,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", @@ -27939,6 +27883,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", @@ -28223,6 +28181,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", @@ -29410,6 +29374,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", @@ -29464,6 +29453,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", @@ -29501,21 +29497,13 @@ "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 579fdadc7..703092c3d 100644 --- a/package.json +++ b/package.json @@ -138,12 +138,12 @@ "@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", + "@typescript-eslint/eslint-plugin": "^8.60.1", + "@typescript-eslint/parser": "^8.60.1", "babel-plugin-transform-import-meta": "^2.3.3", "chai": "^4.1.2", "chai-http": "^4.0.0", - "eslint": "^8.39.0", + "eslint": "^8.57.1", "eslint-plugin-react": "^7.2.1", "eslint-plugin-react-hooks": "^4.0.1", "jest": "^29.5.0", From 7229793b6f5e096d33734bf3415b0d8603193906 Mon Sep 17 00:00:00 2001 From: james hadfield Date: Mon, 8 Jun 2026 18:44:27 +1200 Subject: [PATCH 10/18] eslint fixes Errors were new since version bump in parent commit --- .eslintrc.yaml | 2 +- src/actions/updateMetadata/updateMetadata.ts | 2 +- src/components/controls/animation-controls.js | 5 ++--- src/components/controls/filter.js | 1 - src/components/controls/language.js | 2 +- src/components/info/byline.js | 2 +- src/components/map/mapHelpersLatLong.js | 6 +++--- src/components/tree/tree.tsx | 5 ++++- src/util/entropy.js | 19 ++++++++++--------- src/util/parseNarrative.js | 2 +- src/util/treeJsonProcessing.ts | 1 + src/util/treeMiscHelpers.js | 2 +- src/util/treeVisibilityHelpers.js | 4 ++-- test/request_urls.js | 1 + 14 files changed, 29 insertions(+), 25 deletions(-) diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 6f61a78ba..88673e93f 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -29,7 +29,7 @@ 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-require-imports": off # Remove this override once all files use ES6 style imports. 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/info/byline.js b/src/components/info/byline.js index cb695d2b9..33da38f80 100644 --- a/src/components/info/byline.js +++ b/src/components/info/byline.js @@ -172,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/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/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/parseNarrative.js b/src/util/parseNarrative.js index f5cb3d5bc..378ee9185 100644 --- a/src/util/parseNarrative.js +++ b/src/util/parseNarrative.js @@ -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: ""}; 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/test/request_urls.js b/test/request_urls.js index 8b96b1d0f..2a82923dd 100644 --- a/test/request_urls.js +++ b/test/request_urls.js @@ -14,6 +14,7 @@ const isValidURLCallback = (url) => { chai.request(url) .get('') .end((err, res) => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions expect(err).to.be.null; expect(res).to.have.status(200); done(); // Call done to signal callback end From 67343cfbc99119d2671e21220f10c5ee9b68ebfa Mon Sep 17 00:00:00 2001 From: james hadfield Date: Mon, 8 Jun 2026 18:57:35 +1200 Subject: [PATCH 11/18] [tests] Remove unused test This is an old and now unused test that's been fully superseded by the Playwright smoke tests. It was the only test that used "chai" (and "mocha", although that wasn't listed as a dep). I only noticed this as the eslint version bump flagged up an error in the test file --- package-lock.json | 451 ------------------------------------------- package.json | 2 - test/request_urls.js | 27 --- 3 files changed, 480 deletions(-) delete mode 100644 test/request_urls.js diff --git a/package-lock.json b/package-lock.json index 90bd5911b..1d3eadea3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -118,8 +118,6 @@ "@typescript-eslint/eslint-plugin": "^8.60.1", "@typescript-eslint/parser": "^8.60.1", "babel-plugin-transform-import-meta": "^2.3.3", - "chai": "^4.1.2", - "chai-http": "^4.0.0", "eslint": "^8.57.1", "eslint-plugin-react": "^7.2.1", "eslint-plugin-react-hooks": "^4.0.1", @@ -6556,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", @@ -7201,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", @@ -8078,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", @@ -8796,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", @@ -8879,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", @@ -9048,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", @@ -9073,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", @@ -9239,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", @@ -9684,18 +9556,6 @@ "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", @@ -9744,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", @@ -10880,12 +10731,6 @@ } ] }, - "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", @@ -11081,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", @@ -11247,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", @@ -15327,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", @@ -16896,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", @@ -21605,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", @@ -22149,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", @@ -22768,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", @@ -23285,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", @@ -23352,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", @@ -23476,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", @@ -23495,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", @@ -23604,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", @@ -23977,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", @@ -24021,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", @@ -24827,12 +24451,6 @@ "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", @@ -24987,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", @@ -25093,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", @@ -28038,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", @@ -29195,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", diff --git a/package.json b/package.json index 703092c3d..c3271b94d 100644 --- a/package.json +++ b/package.json @@ -141,8 +141,6 @@ "@typescript-eslint/eslint-plugin": "^8.60.1", "@typescript-eslint/parser": "^8.60.1", "babel-plugin-transform-import-meta": "^2.3.3", - "chai": "^4.1.2", - "chai-http": "^4.0.0", "eslint": "^8.57.1", "eslint-plugin-react": "^7.2.1", "eslint-plugin-react-hooks": "^4.0.1", diff --git a/test/request_urls.js b/test/request_urls.js deleted file mode 100644 index 2a82923dd..000000000 --- a/test/request_urls.js +++ /dev/null @@ -1,27 +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) => { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - 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')); From 88cc9cbd7e6991b8070efb61789bb9fd3f6c82f4 Mon Sep 17 00:00:00 2001 From: james hadfield Date: Tue, 9 Jun 2026 08:07:56 +1200 Subject: [PATCH 12/18] [cli] remove default data/narrative dirs This functionality never worked as well as hoped. Bigger picture, this represents a move away from pre-provisioned datasets / narratives and towards "bring your own data", which is the main use case after all! --- cli/develop.ts | 4 +-- cli/server/processPaths.ts | 50 ++++++++++++++++++++------------------ cli/utils.ts | 20 --------------- cli/view.ts | 4 +-- docs/server/overview.rst | 6 ++--- 5 files changed, 34 insertions(+), 50 deletions(-) diff --git a/cli/develop.ts b/cli/develop.ts index 1060ec269..71249f839 100644 --- a/cli/develop.ts +++ b/cli/develop.ts @@ -113,8 +113,8 @@ const run = async (args): Promise => { 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}`); diff --git a/cli/server/processPaths.ts b/cli/server/processPaths.ts index a319ca1a5..298c5c8a6 100644 --- a/cli/server/processPaths.ts +++ b/cli/server/processPaths.ts @@ -10,10 +10,6 @@ type AvailableResourceTypes = Set<'datasets' | 'narratives'>; * 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. - * - * If no paths are provided via arguments then we attempt to choose sensible defaults - * (via `defaultDataPaths()`), however in my (james) experience it's never worked - * well an I recommend providing paths via args. */ export function processPathArguments(args): Record { @@ -31,34 +27,42 @@ export function processPathArguments(args): Record0) { - utils.error("Incompatible arguments defining paths for datasets and/or narratives: " + - "You must either specify the paths as named arguments (--datasetDir and/or --narrativeDir) " + - "_or_ specify one or more positional arguments."); + 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."); + } } - 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}' `) - } const dataPaths: Record = Object.fromEntries( (args.path.length ? args.path.map(utils.cleanUpPathname) : - args.datasetDir ? - [utils.cleanUpPathname(args.datasetDir)] : - utils.defaultDataPaths() + [utils.cleanUpPathname(args.datasetDir)] ).map((p: string) => [p, new Set(['datasets'])]) ); - for (const narrativePath of - (args.path.length ? - args.path.map(utils.cleanUpPathname) : - args.narrativeDir ? - [utils.cleanUpPathname(args.narrativeDir)] : - utils.defaultDataPaths({narrative: true}))) { + 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 { diff --git a/cli/utils.ts b/cli/utils.ts index 58546e8a2..f686242a0 100644 --- a/cli/utils.ts +++ b/cli/utils.ts @@ -25,10 +25,6 @@ export const error = (msg): never => { process.exit(2); }; -const isNpmGlobalInstall = (): boolean => { - return __dirname.indexOf("lib/node_modules/auspice") !== -1; -}; - export const cleanUpPathname = (pathIn): string | undefined => { let pathOut = pathIn; if (!pathOut.endsWith("/")) pathOut += "/"; @@ -43,22 +39,6 @@ export const cleanUpPathname = (pathIn): string | undefined => { return pathOut; }; -const getCurrentDirectoriesFor = (type): string | undefined => { - 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); -}; - - -export const defaultDataPaths = ({narrative=false} = {}): (string | undefined)[] => { - return isNpmGlobalInstall() ? - [getCurrentDirectoriesFor(narrative ? "narratives" : "data")] : - [path.join(path.resolve(__dirname), "..", narrative ? "narratives" : "data")]; -} - export const readFilePromise = (fileName): Promise => { return new Promise((resolve, reject) => { fs.readFile(fileName, 'utf8', (err, data) => { diff --git a/cli/view.ts b/cli/view.ts index a218bf620..062d22d0b 100644 --- a/cli/view.ts +++ b/cli/view.ts @@ -172,8 +172,8 @@ const run = async (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}`); 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. From 649c636010e2ab6e66cdffebc5832fcdbcdef23b Mon Sep 17 00:00:00 2001 From: james hadfield Date: Tue, 9 Jun 2026 11:57:26 +1200 Subject: [PATCH 13/18] [dev] remove unused npm script (This was from an ancient method of deploying auspice) --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index c3271b94d..ce3978220 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "get-data": "env bash ./scripts/get-data.sh", "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" From d502e302848100659cef9f2cb68bff2051d164c0 Mon Sep 17 00:00:00 2001 From: james hadfield Date: Tue, 9 Jun 2026 11:55:03 +1200 Subject: [PATCH 14/18] [tests] update smoke tests to be compatible in a world without the main 'data/' directory. This made me realise our "smoke-tests" are basically a single test! Nonetheless it's worth keeping around the architecture for future improvements --- .github/workflows/ci.yaml | 2 +- DEV_DOCS.md | 2 +- playwright.config.ts | 2 +- scripts/fetch-test-data | 3 ++- test/smoke-test/urls.txt | 3 --- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 970aed5d3..c257f4e67 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -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..ce38e5b63 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`. 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/fetch-test-data b/scripts/fetch-test-data index b1346db19..60bdba662 100755 --- a/scripts/fetch-test-data +++ b/scripts/fetch-test-data @@ -12,7 +12,8 @@ const CHARON = 'https://nextstrain.org/charon/getDataset?prefix='; 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: 'mumps/global@2026-05-30', dst: 'mumps', tipFrequencies: true }, + { src: 'zika@2026-06-05', dst: 'zika'}, ]; async function main() { 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 From 0fcf8b0461b4688327ddf41d32ffc3ae3d1a9c2a Mon Sep 17 00:00:00 2001 From: james hadfield Date: Tue, 9 Jun 2026 12:04:16 +1200 Subject: [PATCH 15/18] Remove get-data initialisation approach This is a relic of earlier times where it was kept in-sync with the canonical nextstrain datasets. We maintain similar functionality in the fetch-test-data script but for testing purposes only. This is a key part of the bigger re-design which moves auspice to a visualisation program/library which you point at your own data. --- README.md | 11 +--- docs/introduction/how-to-run.rst | 9 ---- package.json | 3 +- scripts/get-data.sh | 86 -------------------------------- 4 files changed, 2 insertions(+), 107 deletions(-) delete mode 100755 scripts/get-data.sh 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/docs/introduction/how-to-run.rst b/docs/introduction/how-to-run.rst index 55e82a7d1..000c7def6 100644 --- a/docs/introduction/how-to-run.rst +++ b/docs/introduction/how-to-run.rst @@ -117,12 +117,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/package.json b/package.json index ce3978220..484771e00 100644 --- a/package.json +++ b/package.json @@ -26,9 +26,8 @@ "lint:fix": "eslint --fix .", "type-check": "tsc && tsc --project cli/tsconfig.json", "type-check:watch": "tsc --watch", - "get-data": "env bash ./scripts/get-data.sh", "fetch-test-data": "./scripts/fetch-test-data", - "heroku-postbuild": "npm run build && npm run get-data", + "heroku-postbuild": "npm run build", "test": "jest test/*.js test/*.ts", "smoke-test": "NODE_ENV=test ENV=dev npx playwright test", "diff-lang": "./scripts/diff-lang.js" 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" From 33590b05ba5f8c9dc4ab1e0309b4f23b2162d555 Mon Sep 17 00:00:00 2001 From: james hadfield Date: Tue, 9 Jun 2026 12:10:05 +1200 Subject: [PATCH 16/18] Remove heroku as an auspice app It has previously been useful to run Auspice as a stand-alone Heroku (review-) app, but this has been superseded by standing up a testing nextstrain.org review app as the range of datasets/narratives available is much greater, or a auspice.us app for datasets not available via nextstrain.org. It's simpler (and cheaper!) to drop the Auspice app entirely --- DEV_DOCS.md | 8 +------- app.json | 26 -------------------------- package.json | 1 - 3 files changed, 1 insertion(+), 34 deletions(-) delete mode 100644 app.json diff --git a/DEV_DOCS.md b/DEV_DOCS.md index ce38e5b63..2e7928245 100644 --- a/DEV_DOCS.md +++ b/DEV_DOCS.md @@ -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/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/package.json b/package.json index 484771e00..f3c05dd34 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ "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", "test": "jest test/*.js test/*.ts", "smoke-test": "NODE_ENV=test ENV=dev npx playwright test", "diff-lang": "./scripts/diff-lang.js" From 0250dab43dd141edfc0e1e99e4710a58063b0d24 Mon Sep 17 00:00:00 2001 From: james hadfield Date: Tue, 9 Jun 2026 12:15:11 +1200 Subject: [PATCH 17/18] [docs] update auspice.us note Slightly out of date!!! --- docs/introduction/how-to-run.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/introduction/how-to-run.rst b/docs/introduction/how-to-run.rst index 000c7def6..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 -------------------------------------------- @@ -89,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:: From 24cd1422cf52158053feb4661d43c1dedd2e783c Mon Sep 17 00:00:00 2001 From: Trevor Bedford Date: Wed, 1 Jul 2026 14:38:57 -0700 Subject: [PATCH 18/18] Fix zoom freezing tips whose radius changed mid-transition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A tip-radius update (dispatched on legend-item mouseenter, and on map deme hover) that landed while a tree position transition was still running — e.g. zooming a clade shortly after hovering a division in the legend — used a zero-duration d3 transition. Starting that transition interrupted the in-flight cx/cy transition on the very same tips, freezing them at their pre-zoom positions while the rest of the tree zoomed. On /ebola this left every tip of the hovered division (e.g. "Western Urban") stranded. genericSelectAndModify now applies transitionTime===0 updates directly instead of through a zero-duration transition, so they update the attrs/styles immediately without cancelling other running transitions on those elements. Co-Authored-By: Claude Opus 4.7 --- src/components/tree/phyloTree/change.ts | 26 ++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) 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(