From 1cac6517a36f9efef137b3df817caafc05c82bdb Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Wed, 23 Oct 2024 17:35:19 +0100 Subject: [PATCH 01/22] Revert "[back-end] Add test data (#24)" This reverts commit 537e5c320e3b4efcf80709f312cb606f141b299a. Reason: this commit breaks back-end tests in this branch, but we cannot use the new datastructure added without adding a new implementation, which is out of the scope of this branch. --- .../test/data/config/dataset-A/config.json | 156 ------------------ .../datasets/dataset-A/initiatives/0.json | 17 -- .../datasets/dataset-A/initiatives/1.json | 17 -- .../data/datasets/dataset-A/locations.json | 4 - .../data/datasets/dataset-A/searchable.json | 16 -- apps/back-end/test/data/datasets/test-A.json | 1 + .../test/data/datasets/test-A/items/0.json | 1 + .../test-A/search/a:foo/b:bar/text.json | 1 + .../datasets/test-A/search/a:foo/text.json | 1 + 9 files changed, 4 insertions(+), 210 deletions(-) delete mode 100644 apps/back-end/test/data/config/dataset-A/config.json delete mode 100644 apps/back-end/test/data/datasets/dataset-A/initiatives/0.json delete mode 100644 apps/back-end/test/data/datasets/dataset-A/initiatives/1.json delete mode 100644 apps/back-end/test/data/datasets/dataset-A/locations.json delete mode 100644 apps/back-end/test/data/datasets/dataset-A/searchable.json create mode 100644 apps/back-end/test/data/datasets/test-A.json create mode 100644 apps/back-end/test/data/datasets/test-A/items/0.json create mode 100644 apps/back-end/test/data/datasets/test-A/search/a:foo/b:bar/text.json create mode 100644 apps/back-end/test/data/datasets/test-A/search/a:foo/text.json diff --git a/apps/back-end/test/data/config/dataset-A/config.json b/apps/back-end/test/data/config/dataset-A/config.json deleted file mode 100644 index d12eeadc..00000000 --- a/apps/back-end/test/data/config/dataset-A/config.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "prefixes": { - "https://dev.lod.coop/essglobal/2.1/standard/activities-ica/": "aci", - "https://dev.lod.coop/essglobal/2.1/standard/base-membership-type/": "bmt", - "https://dev.lod.coop/essglobal/2.1/standard/organisational-structure/": "os" - }, - "languages": ["en", "fr"], - "fields": { - "uid": "value", - "name": "value", - "description": "value", - "website": { - "type": "multi", - "of": { - "type": "value" - } - }, - "dc_domains": { - "type": "multi", - "of": { "type": "value" } - }, - "country_id": { - "type": "vocab", - "uri": "coun" - }, - "primary_activity": { - "type": "vocab", - "uri": "aci", - "titleUri": "ui:primary_activity" - }, - "organisational_structure": { - "type": "vocab", - "uri": "os", - "titleUri": "ui:organisational_structure" - }, - "typology": { - "type": "vocab", - "uri": "bmt", - "titleUri": "ui:typology" - }, - "latitude": "value", - "longitude": "value", - "geocontainer_lat": "value", - "geocontainer_lon": "value", - "geocoded_addr": "value", - "data_sources": { - "type": "multi", - "of": { "type": "vocab", "uri": "dso" } - } - }, - "vocabs": { - "aci": { - "en": { - "title": "Economic Activity", - "terms": { - "ICA210": "Housing", - "ICA220": "Transport", - "ICA230": "Utilities" - } - }, - "fr": { - "title": "Secteur économique", - "terms": { - "ICA210": "Logement", - "ICA220": "Transports", - "ICA230": "Services publics" - } - } - }, - "bmt": { - "en": { - "title": "Typology", - "terms": { - "BMT10": "Consumer/Users", - "BMT20": "Producers", - "BMT30": "Workers" - } - }, - "fr": { - "title": "Typologie", - "terms": { - "BMT10": "Consommateurs/usagers", - "BMT20": "Producteurs", - "BMT30": "Travailleurs" - } - } - }, - "os": { - "en": { - "title": "Structure Type", - "terms": { - "OS60": "Workers cooperative", - "OS80": "Consumer/User coops", - "OS90": "Producer cooperative" - } - }, - "fr": { - "title": "Type de structure", - "terms": { - "OS50": "Entreprise (autre)", - "OS60": "Coopératives de travail associé", - "OS80": "Coopératives de consommateurs", - "OS90": "Coopératives de producteurs" - } - } - }, - "coun": { - "en": { - "title": "Country", - "terms": { - "GB": "United Kingdom", - "FR": "France" - } - }, - "fr": { - "title": "Pays", - "terms": { - "GB": "Royaume-Uni", - "FR": "France" - } - } - }, - "dso": { - "en": { - "title": "Data Source", - "terms": { - "CUK": "Co-operatives UK", - "DC": "DotCoop", - "ICA": "International Cooperative Alliance", - "NCBA": "National Cooperative Business Association (USA)" - } - }, - "fr": { - "title": "Source de données", - "terms": { - "CUK": "Co-operatives UK", - "DC": "DotCoop", - "ICA": "Alliance coopérative internationale", - "NCBA": "National Cooperative Business Association (USA)" - } - } - }, - "ui": { - "en": { - "primary_activity": "Primary Activity", - "organisational_structure": "Organisational Structure", - "typology": "Typology" - }, - "fr": { - "primary_activity": "Activité principale", - "organisational_structure": "Structure organisationnelle", - "typology": "Typologie" - } - } - } -} diff --git a/apps/back-end/test/data/datasets/dataset-A/initiatives/0.json b/apps/back-end/test/data/datasets/dataset-A/initiatives/0.json deleted file mode 100644 index 37052eeb..00000000 --- a/apps/back-end/test/data/datasets/dataset-A/initiatives/0.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "uid": "test/cuk/R000001", - "name": "Apples Co-op", - "description": "We sell apples", - "website": "https://apples.coop", - "dc_domains": ["apples.coop", "orchards.coop"], - "country_id": "coun:GB", - "primary_activity": "aci:ICA210", - "organisational_structure": "os:OS60", - "typology": "bmt:BMT20", - "latitude": 51.507476, - "longitude": -0.127825, - "geocontainer_lat": 51.50747643, - "geocontainer_lon": -0.12782464, - "geocoded_addr": "1 West Street, Sheffield, S1 2AB, UK", - "data_sources": ["dso:DC", "dso:CUK"] -} diff --git a/apps/back-end/test/data/datasets/dataset-A/initiatives/1.json b/apps/back-end/test/data/datasets/dataset-A/initiatives/1.json deleted file mode 100644 index bccecf94..00000000 --- a/apps/back-end/test/data/datasets/dataset-A/initiatives/1.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "uid": "test/cuk/R000002", - "name": "Pears United", - "description": "We sell pears", - "website": "https://pears.coop", - "dc_domains": ["pears.coop", "pearsunited.coop"], - "country_id": "coun:FR", - "primary_activity": "aci:ICA230", - "organisational_structure": "os:OS90", - "typology": "bmt:BMT30", - "latitude": 50.850452, - "longitude": 0.924728, - "geocontainer_lat": 50.85045216, - "geocontainer_lon": 0.92472819, - "geocoded_addr": "79 rue de la Mare aux Carats, 34090 Montpellier, France", - "data_sources": ["dso:DC"] -} diff --git a/apps/back-end/test/data/datasets/dataset-A/locations.json b/apps/back-end/test/data/datasets/dataset-A/locations.json deleted file mode 100644 index e8996e0f..00000000 --- a/apps/back-end/test/data/datasets/dataset-A/locations.json +++ /dev/null @@ -1,4 +0,0 @@ -[ - [-0.12783, 51.50748], - [0.92473, 50.85045] -] diff --git a/apps/back-end/test/data/datasets/dataset-A/searchable.json b/apps/back-end/test/data/datasets/dataset-A/searchable.json deleted file mode 100644 index 20ca7235..00000000 --- a/apps/back-end/test/data/datasets/dataset-A/searchable.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "country_id": "coun:GB", - "primary_activity": "aci:ICA210", - "organisational_structure": "os:OS60", - "typology": "bmt:BMT20", - "searchString": "test co-op 1 west street sheffield s1 2ab uk test.coop" - }, - { - "country_id": "coun:FR", - "primary_activity": "aci:ICA230", - "organisational_structure": "os:OS90", - "typology": "bmt:BMT30", - "searchString": "pears united 79 rue de la mare aux carats 34090 montpellier france pears.coop" - } -] diff --git a/apps/back-end/test/data/datasets/test-A.json b/apps/back-end/test/data/datasets/test-A.json new file mode 100644 index 00000000..70aaa390 --- /dev/null +++ b/apps/back-end/test/data/datasets/test-A.json @@ -0,0 +1 @@ +[[1,2],[3,2],[5,2],["a",3]] diff --git a/apps/back-end/test/data/datasets/test-A/items/0.json b/apps/back-end/test/data/datasets/test-A/items/0.json new file mode 100644 index 00000000..d1f40b48 --- /dev/null +++ b/apps/back-end/test/data/datasets/test-A/items/0.json @@ -0,0 +1 @@ +{"id":0, "a":"a:foo", "b":"b:bar", "desc": "humbug"} diff --git a/apps/back-end/test/data/datasets/test-A/search/a:foo/b:bar/text.json b/apps/back-end/test/data/datasets/test-A/search/a:foo/b:bar/text.json new file mode 100644 index 00000000..22fd8752 --- /dev/null +++ b/apps/back-end/test/data/datasets/test-A/search/a:foo/b:bar/text.json @@ -0,0 +1 @@ +[2] diff --git a/apps/back-end/test/data/datasets/test-A/search/a:foo/text.json b/apps/back-end/test/data/datasets/test-A/search/a:foo/text.json new file mode 100644 index 00000000..7660873d --- /dev/null +++ b/apps/back-end/test/data/datasets/test-A/search/a:foo/text.json @@ -0,0 +1 @@ +[1] From c3d64e2706129740cce0b76e16d88cbc761aef03 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Tue, 22 Oct 2024 14:59:36 +0100 Subject: [PATCH 02/22] [back-end, common] test/validation.test.ts - move from back-end to common This is really testing things in the common package and so should live there. Pragmatically, doing it in the back-end is possible but requires more work re-building than it shoud do. --- {apps/back-end => libs/common}/test/validation.test.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {apps/back-end => libs/common}/test/validation.test.ts (100%) diff --git a/apps/back-end/test/validation.test.ts b/libs/common/test/validation.test.ts similarity index 100% rename from apps/back-end/test/validation.test.ts rename to libs/common/test/validation.test.ts From f7c1d2dc981566978d09e8ae63eb6c2f1e996b27 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Tue, 22 Oct 2024 14:57:09 +0100 Subject: [PATCH 03/22] [common] package.json - npm install --save-dev vite-test --- libs/common/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/common/package.json b/libs/common/package.json index e46efd64..3f8cfd82 100644 --- a/libs/common/package.json +++ b/libs/common/package.json @@ -26,6 +26,7 @@ "typescript": "^5.5.4", "vite": "^5.4.3", "vite-node": "^2.0.5", - "vite-plugin-dts": "^4.1.0" + "vite-plugin-dts": "^4.1.0", + "vite-test": "^0.4.0" } } From 0aef4fc76b02dc2af1ca18c3a2a94573eca1700f Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Tue, 22 Oct 2024 17:58:00 +0100 Subject: [PATCH 04/22] [common] package.json - npm install --save glob I'd use the version of glob included with node, but it's still experimental in the one in the version of node being used at the time of writing (22.4.1). But, the glob and slurp functions seems generally useful enough to warrant this addition here in common. --- libs/common/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/common/package.json b/libs/common/package.json index 3f8cfd82..81961735 100644 --- a/libs/common/package.json +++ b/libs/common/package.json @@ -18,6 +18,7 @@ "dependencies": { "@ts-rest/core": "^3.51.0", "@ts-rest/open-api": "^3.51.0", + "glob": "^11.0.0", "zod": "^3.23.8" }, "devDependencies": { From 0374ebb220c0c800a2430b01842b9a39ba06b7ef Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Tue, 22 Oct 2024 15:01:26 +0100 Subject: [PATCH 05/22] [common] package.json - implement a vitest-based test runscript and tests currently pass --- libs/common/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/package.json b/libs/common/package.json index 81961735..27764af4 100644 --- a/libs/common/package.json +++ b/libs/common/package.json @@ -13,7 +13,7 @@ "prebuild": "npm run generate-openapi", "build": "vite build", "generate-openapi": "npx vite-node src/api/generate-openapi.ts", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "vitest run" }, "dependencies": { "@ts-rest/core": "^3.51.0", From 6277fb10b8b57d3ffdc388a49de798066223b025 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Wed, 16 Oct 2024 12:45:12 +0100 Subject: [PATCH 06/22] [common] contract.ts - move regular expressions into their own file i.e. src/rsdefs.ts We expect to get more in upcoming commits. --- libs/common/src/api/contract.ts | 61 ++------------------------------ libs/common/src/rxdefs.ts | 62 +++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 58 deletions(-) create mode 100644 libs/common/src/rxdefs.ts diff --git a/libs/common/src/api/contract.ts b/libs/common/src/api/contract.ts index 7ef8b772..cb0d74d2 100644 --- a/libs/common/src/api/contract.ts +++ b/libs/common/src/api/contract.ts @@ -1,73 +1,18 @@ import { initContract } from "@ts-rest/core"; import { z } from "zod"; import { extendZodWithOpenApi } from "@anatine/zod-openapi"; +import * as Rx from "../rxdefs.js"; extendZodWithOpenApi(z); const c = initContract(); -/** A regex testing for an *URL-safe* base64 string (RFC4648 sect 5) */ -const UrlSafeBase64Rx = /^[A-Z0-9_-]+$/i; - -/** A string regular expression matching an XML NCName - * - * - An NCName is an XML Name, but with no colons allowed. - * - An XML Name is a complicated beast, but loosely a Unicode version of \w, - * or in other words, a unicode alphanumeric symbolic identifier. - * - It can contain digits, letters, hyphens, periods and underscores and - * certain unicode equivalents. - * - But it must not start with digits, a hyphen or a period, nor certain - * unicode equivalents. - * - * The regex for an XML Name is adapted from O'Reilly Regex Cookbook section 8.4, - * "XML 1.0 names (approximate)" - but the colon is removed. - * - * Paraphrasing that book's explanation: - * - the name start character can be a [:_] or - * - any of the following Unicode categories: - * - Lowercase letter (Ll) - * - Uppercase letter (Lu) - * - Titlecase letter (Lt) - * - Letter without case (Lo) - * - Letter number (Nl) - * - subsequent characters can also include [.-] or - * - Mark (M) - * - Modifier letter (Lm) - * - Decimal digit (Nd) - * - * This definition is not compiled, as it is intended for composition below. - * Therefore it is wrapped in a non-capturing group to isolate it without affecting - * the captures which may be defined around it. - * - * This regex requires node 10+ to be able to use /u and \p. - * - */ -const NCName = - "(?:[_\\p{Ll}\\p{Lu}\\p{Lt}\\p{Lo}\\p{Nl}][_.\\p{L}\\p{M}\\p{Nd}\\p{Nl}-]*)"; - -/** Match a QName - * - * Paraphrasing https://en.wikipedia.org/wiki/QName - * - A QName is an NCName (see above) - * - Or two of them delimited by a colon. - * - * Note: for our purposes, we *require* a colon delimiter - it can't just be a NCName. - * This is because we need an abbreviation with which to look up the URL prefix. - * - * FIXME Perhaps we should also disallow common URI scheme prefixes. - * FIXME Maybe we don't care about unicode? - * - * This regex requires node 10+ to be able to use /u and \p. - * - */ -const QNameRx = new RegExp(`^${NCName}[:]${NCName}$`, "gsu"); - const Location = z.array(z.number()).min(2).max(2); -const DatasetId = z.string().regex(UrlSafeBase64Rx); +const DatasetId = z.string().regex(Rx.UrlSafeBase64); const DatasetItemId = z.coerce.number().int().nonnegative(); const DatasetItem = z.object({}).passthrough(); const Dataset = z.array(Location); -const QName = z.string().regex(QNameRx); +const QName = z.string().regex(Rx.QName); const VersionInfo = z.object({ name: z.string(), buildTime: z.string().datetime({ offset: false }), diff --git a/libs/common/src/rxdefs.ts b/libs/common/src/rxdefs.ts new file mode 100644 index 00000000..ccfe7852 --- /dev/null +++ b/libs/common/src/rxdefs.ts @@ -0,0 +1,62 @@ +/** Regular expression definitions */ + +/** A regex testing for an *URL-safe* base64 string (RFC4648 sect 5) */ +export const UrlSafeBase64 = /^[A-Za-z0-9_-]+$/imsu; + +/** A regular expression matching an XML NCName + * + * - An NCName is an XML Name, but with no colons allowed. + * - An XML Name is a complicated beast, but loosely a Unicode version of \w, + * or in other words, a unicode alphanumeric symbolic identifier. + * - It can contain digits, letters, hyphens, periods and underscores and + * certain unicode equivalents. + * - But it must not start with digits, a hyphen or a period, nor certain + * unicode equivalents. + * + * The regex for an XML Name is adapted from O'Reilly Regex Cookbook section 8.4, + * "XML 1.0 names (approximate)" - but the colon is removed. + * + * Paraphrasing that book's explanation: + * - the name start character can be a [:_] or + * - any of the following Unicode categories: + * - Lowercase letter (Ll) + * - Uppercase letter (Lu) + * - Titlecase letter (Lt) + * - Letter without case (Lo) + * - Letter number (Nl) + * - subsequent characters can also include [.-] or + * - Mark (M) + * - Modifier letter (Lm) + * - Decimal digit (Nd) + * + * This definition is is wrapped in a non-capturing group to isolate it without affecting + * captures which may be defined around it in derived regular expressions. + * + * This regex requires node 10+ to be able to use /u and \p. + * + */ +export const NCName = + /(?:[_\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nl}][_.\p{L}\p{M}\p{Nd}\p{Nl}-]*)/imsu; + +/** Match a QName + * + * Paraphrasing https://en.wikipedia.org/wiki/QName + * - A QName is an NCName (see above) + * - Or two of them delimited by a colon. + * + * Note: for our purposes, we *require* a colon delimiter - it can't just be a NCName. + * This is because we need an abbreviation with which to look up the URL prefix. + * + * This definition is is wrapped in a non-capturing group to isolate it without affecting + * captures which may be defined around it in derived regular expressions. + * + * FIXME Perhaps we should also disallow common URI scheme prefixes. + * FIXME Maybe we don't care about unicode? + * + * This regex requires node 10+ to be able to use /u and \p. + * + */ +export const QName = new RegExp( + `^${NCName.source}[:]${NCName.source}$`, + "imsu", +); From ab612c7e7781127f7759eee5fdf695b110d4d266 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Tue, 22 Oct 2024 18:09:09 +0100 Subject: [PATCH 07/22] [common] test/file-utils.ts - currently just defines slurp() variations "slurp" is a function name (and an idea) I stole from Perl's ecosystem. These functions are intended to make loading a file a one-call operation, and so to make loading files in tests shorter. I'd have included some globbing functionality here except there is a problem inferring the caller's source directory from inside a function, which makes it seem less useful (so that feature is now going into a "glorp" function in the tests themselves, committed later). The functions may also be useful more generally. I'm putting these in the test/ directory as they're NodeJS specific, so don't really belong in a common module shared with the front-end. If the back-end tests (or even the back-end implementation) wants to use them, we will need to move them to another common library which can target NodeJS. --- libs/common/test/file-utils.ts | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 libs/common/test/file-utils.ts diff --git a/libs/common/test/file-utils.ts b/libs/common/test/file-utils.ts new file mode 100644 index 00000000..63b1d48d --- /dev/null +++ b/libs/common/test/file-utils.ts @@ -0,0 +1,42 @@ +import { readFileSync } from "node:fs"; +import { readFile } from "node:fs/promises"; + +/** Read a file asynchronously, given its path + */ +async function slurp( + path: string, + opts: { + enc?: BufferEncoding; + } = {}, +): Promise { + return readFile(path, opts.enc ?? "utf8").then((it) => it.toString()); +} + +/** Read a file synchronously, given its path + */ +function slurpSync( + path: string, + opts: { + enc?: BufferEncoding; + } = {}, +): string { + return readFileSync(path, opts.enc ?? "utf8").toString(); +} + +/** Read in a JSON file asynchronously, given its path. */ +async function slurpJson( + path: string, + reviver?: (this: any, key: any, value: any) => any, +): Promise { + return slurp(path).then((x) => JSON.parse(x, reviver)); +} + +/** Read in a JSON file synchronously, given its path. */ +function slurpJsonSync( + path: string, + reviver?: (this: any, key: any, value: any) => any, +): Promise { + return JSON.parse(slurpSync(path), reviver); +} + +export { slurp, slurpSync, slurpJson, slurpJsonSync }; From e0d85680c08f7333c1317805767354f19043f1b5 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Wed, 16 Oct 2024 13:34:32 +0100 Subject: [PATCH 08/22] [common] src/rxutils.ts - regular expression building utilities Hopefully these make the expressions easier to build from components. Javascript regular expressions don't have some of the advanced conveniences of those I've used in the past in Perl such as interpolation or whitespace indentation - at least not yet. These small utilities aim to mitigate the use of complicated regular expressions, allowing small parts to be defined and then composed, making their construction more comprehensible (if not the resulting regular expressions!) Tests will be added in an upcoming commit. --- libs/common/src/rxutils.ts | 91 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 libs/common/src/rxutils.ts diff --git a/libs/common/src/rxutils.ts b/libs/common/src/rxutils.ts new file mode 100644 index 00000000..190cdd3a --- /dev/null +++ b/libs/common/src/rxutils.ts @@ -0,0 +1,91 @@ +/** A collection of Regular expression building utilities. */ +const RxUtils = (() => { + /** Map a string or RegExp to a string */ + const _str = (component: string | RegExp) => + component instanceof RegExp ? component.source : component; + + /** Wrap patterns in a non-capturing group, separated by a delimiter + * + * If there are more than one pattern, each one is also wrapped in a non-capturing group. + * + * Note, unlike the other functions here, this one is for internal use and + * returns a string, not a RegExp. + */ + const _wrap = ( + delim: "" | "|", + ...components: Array + ): string => { + switch (components.length) { + case 0: + return ""; + case 1: + return `(?:${_str(components[0])})`; + default: + return ( + "(?:" + components.map((c) => `(?:${_str(c)})`).join(delim) + ")" + ); + } + }; + + /** Wrap patterns in a non-capturing group, separated by a delimiter. + * + * If there are more than one pattern, each one is also wrapped in a non-capturing group. + */ + const wrap = ( + delim: "" | "|", + ...components: Array + ): RegExp => new RegExp(_wrap(delim, ...components)); + + /** Simply concatenate the patterns passed, no grouping or delimiter */ + const conc = (...components: Array): RegExp => + new RegExp(components.map((c) => _str(c)).join("")); + + /** Match the patterns passed as a sequence */ + const seq = (...components: Array): RegExp => + new RegExp(_wrap("", ...components)); + + /** Match just one of the patterns passed */ + const oneOf = (...components: Array): RegExp => + new RegExp(_wrap("|", ...components)); + + /** Match at least n of one the patterns passed */ + const min = (n: 0 | 1, ...components: Array): RegExp => { + const expr = _wrap("|", ...components); + switch (n) { + case 0: + return new RegExp(`${expr}*`); + case 1: + return new RegExp(`${expr}+`); + } + }; + + /** Matches zero or one of the patterns passed */ + const maybe = (...components: Array): RegExp => { + const expr = _wrap("|", ...components); + return new RegExp(`${expr}?`); + }; + + /** Match unicode, all or nothing. + * + * Ensures the regular expression has msu flags and is anchored to match all + * of a string or nothing. + */ + const uaon = (component: string | RegExp): RegExp => { + let [expr, flags] = + component instanceof RegExp + ? [component.source, component.flags] + : [component, ""]; + + expr = expr.replace(/^\^?/ms, "^"); // ensure anchor at start + expr = expr.replace(/\$?$/ms, "$"); // ensure anchor at end + + // Extend and deduplicate flags + flags = [...new Set((flags + "msu").split(""))].join(""); + + return new RegExp(expr, flags); + }; + + return { conc, maybe, min, seq, oneOf, uaon, wrap }; +})(); + +export default RxUtils; From 47a98f1d033c65dcefbb70546499b0a0b269dc8d Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Wed, 16 Oct 2024 13:38:15 +0100 Subject: [PATCH 09/22] [common] src/iso63901.ts - ISO639-1 language code definitions For validating two-letter language codes like 'en', 'es', 'ko'. --- libs/common/src/iso639-1.ts | 229 ++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 libs/common/src/iso639-1.ts diff --git a/libs/common/src/iso639-1.ts b/libs/common/src/iso639-1.ts new file mode 100644 index 00000000..87103bcb --- /dev/null +++ b/libs/common/src/iso639-1.ts @@ -0,0 +1,229 @@ +/** Defines the ISO639 set-1 language codes, mapped to the English name of the language. + * + * Derived on 14/10/2024 from https://www.loc.gov/standards/iso639-2/php/English_list.php + * + * Note: sorted alphabetically, and any duplicate codes from the table have been + * necessarily commented out, somewhat arbitrarily (I've aimed to leave the most + * obvious names, which are here mainly for convenience at the moment.) + */ +export const Iso639Set1Codes = { + // ISO 639-1 code: "English Name of Language" + aa: "Afar", + ab: "Abkhazian", + ae: "Avestan", + af: "Afrikaans", + ak: "Akan", + am: "Amharic", + an: "Aragonese", + ar: "Arabic", + as: "Assamese", + av: "Avaric", + ay: "Aymara", + az: "Azerbaijani", + ba: "Bashkir", + be: "Belarusian", + bg: "Bulgarian", + bi: "Bislama", + bm: "Bambara", + bn: "Bengali", + bo: "Tibetan", + br: "Breton", + bs: "Bosnian", + ca: "Catalan", + //ca: "Valencian", + ce: "Chechen", + ch: "Chamorro", + co: "Corsican", + cr: "Cree", + cs: "Czech", + cu: "Church Slavic", + //cu: "Church Slavonic", + //cu: "Old Bulgarian", + //cu: "Old Church Slavonic", + //cu: "Old Slavonic", + cv: "Chuvash", + cy: "Welsh", + da: "Danish", + de: "German", + //dv: "Dhivehi", + //dv: "Divehi", + dv: "Maldivian", + dz: "Dzongkha", + ee: "Ewe", + el: "Greek, Modern (1453-)", + en: "English", + eo: "Esperanto", + //es: "Castilian", + es: "Spanish", + et: "Estonian", + eu: "Basque", + fa: "Persian", + ff: "Fulah", + fi: "Finnish", + fj: "Fijian", + fo: "Faroese", + fr: "French", + fy: "Western Frisian", + ga: "Irish", + //gd: "Gaelic", + gd: "Scottish Gaelic", + gl: "Galician", + gn: "Guarani", + gu: "Gujarati", + gv: "Manx", + ha: "Hausa", + he: "Hebrew", + hi: "Hindi", + ho: "Hiri Motu", + hr: "Croatian", + //ht: "Haitian Creole", + ht: "Haitian", + hu: "Hungarian", + hy: "Armenian", + hz: "Herero", + ia: "Interlingua (International Auxiliary Language Association)", + id: "Indonesian", + ie: "Interlingue", + //ie: "Occidental", + ig: "Igbo", + //ii: "Nuosu", + ii: "Sichuan Yi", + ik: "Inupiaq", + io: "Ido", + is: "Icelandic", + it: "Italian", + iu: "Inuktitut", + ja: "Japanese", + jv: "Javanese", + ka: "Georgian", + kg: "Kongo", + //ki: "Gikuyu", + ki: "Kikuyu", + kj: "Kuanyama", + //kj: "Kwanyama", + kk: "Kazakh", + kl: "Greenlandic", + //kl: "Kalaallisut", + km: "Central Khmer", + kn: "Kannada", + ko: "Korean", + kr: "Kanuri", + ks: "Kashmiri", + ku: "Kurdish", + kv: "Komi", + kw: "Cornish", + //ky: "Kirghiz", + ky: "Kyrgyz", + la: "Latin", + //lb: "Letzeburgesch", + lb: "Luxembourgish", + lg: "Ganda", + li: "Limburgan", + //li: "Limburger", + //li: "Limburgish", + ln: "Lingala", + lo: "Lao", + lt: "Lithuanian", + lu: "Luba-Katanga", + lv: "Latvian", + mg: "Malagasy", + mh: "Marshallese", + mi: "Maori", + mk: "Macedonian", + ml: "Malayalam", + mn: "Mongolian", + mr: "Marathi", + ms: "Malay", + mt: "Maltese", + my: "Burmese", + na: "Nauru", + //nb: "Bokmål, Norwegian", + nb: "Norwegian Bokmål", + //nd: "Ndebele, North", + nd: "North Ndebele", + ne: "Nepali", + ng: "Ndonga", + nl: "Dutch", + //nl: "Flemish", + nn: "Norwegian Nynorsk", + //nn: "Nynorsk, Norwegian", + no: "Norwegian", + //nr: "Ndebele, South", + nr: "South Ndebele", + //nv: "Navaho", + nv: "Navajo", + ny: "Chewa", + //ny: "Chichewa", + //ny: "Nyanja", + oc: "Occitan (post 1500)", + oj: "Ojibwa", + om: "Oromo", + or: "Oriya", + os: "Ossetian", + //os: "Ossetic", + //pa: "Panjabi", + pa: "Punjabi", + pi: "Pali", + pl: "Polish", + ps: "Pashto", + //ps: "Pushto", + pt: "Portuguese", + qu: "Quechua", + rm: "Romansh", + rn: "Rundi", + //ro: "Moldavian", + //ro: "Moldovan", + ro: "Romanian", + ru: "Russian", + rw: "Kinyarwanda", + sa: "Sanskrit", + sc: "Sardinian", + sd: "Sindhi", + se: "Northern Sami", + sg: "Sango", + //si: "Sinhala", + si: "Sinhalese", + sk: "Slovak", + sl: "Slovenian", + sm: "Samoan", + sn: "Shona", + so: "Somali", + sq: "Albanian", + sr: "Serbian", + ss: "Swati", + st: "Sotho, Southern", + su: "Sundanese", + sv: "Swedish", + sw: "Swahili", + ta: "Tamil", + te: "Telugu", + tg: "Tajik", + th: "Thai", + ti: "Tigrinya", + tk: "Turkmen", + tl: "Tagalog", + tn: "Tswana", + to: "Tonga (Tonga Islands)", + tr: "Turkish", + ts: "Tsonga", + tt: "Tatar", + tw: "Twi", + ty: "Tahitian", + ug: "Uighur", + //ug: "Uyghur", + uk: "Ukrainian", + ur: "Urdu", + uz: "Uzbek", + ve: "Venda", + vi: "Vietnamese", + vo: "Volapük", + wa: "Walloon", + wo: "Wolof", + xh: "Xhosa", + yi: "Yiddish", + yo: "Yoruba", + //za: "Chuang", + za: "Zhuang", + zh: "Chinese", + zu: "Zulu", +} as const; From fdb17d6ce982a512d3f4c40ddafdcc994a53e66a Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Thu, 17 Oct 2024 17:28:17 +0100 Subject: [PATCH 10/22] [common] src/rxdefs.ts - add PrefixURI pattern This matches the particular class of URIs which seem appropriate for SKOS vocab URI prefixes. Details in the comments. --- libs/common/src/rxdefs.ts | 74 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/libs/common/src/rxdefs.ts b/libs/common/src/rxdefs.ts index ccfe7852..03d8e280 100644 --- a/libs/common/src/rxdefs.ts +++ b/libs/common/src/rxdefs.ts @@ -1,4 +1,6 @@ /** Regular expression definitions */ +import RxUtils from "./rxutils.js"; +const { min, seq, conc, maybe } = RxUtils; /** A regex testing for an *URL-safe* base64 string (RFC4648 sect 5) */ export const UrlSafeBase64 = /^[A-Za-z0-9_-]+$/imsu; @@ -60,3 +62,75 @@ export const QName = new RegExp( `^${NCName.source}[:]${NCName.source}$`, "imsu", ); + +/** A Prefix-URI scheme. + * + * Note, limited to http and https. + */ +const Scheme = /https?:\/\//; + +/** A domain name component. + * + * A letter, optionally followed by letters, digits or hyphens, but ending with a letter or digit. + * + * Based on https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.1 + */ +const DomainLabel = /[A-Za-z](?:[A-Za-z0-9-]*[A-Za-z0-9])?/; + +/** A domain name. + * + * A domain label optionally followed by more domain labels, each prefixed with a period + * + * Based on https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.1 + */ +const Domain = seq(DomainLabel, min(0, seq("[.]", DomainLabel))); + +/** A percent-encoded URI character. */ +const PctEnc = /%[0-9A-Fa-f]{2}/; + +// URI reserved characters +// reserved /[:/?#\[\]@!$&'()*+,;=]/; + +/** A valid (non-percent-encoded) URI path character. + * + * Path characters can be any of: + * - unreserved chars /[A-Za-z0-9._~-]/ + * - percent-encoded chars i./%[A-Za-z0-9]{2}/ + * - sub-delims chars /[!$&'()*+,;=]/ + * - colon or at chars /[:@]/ + * + * Based on https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 + */ +const PathChar = /[A-Za-z0-9._~!$&'()*+,;=:@-]/; + +/** A segment of a URI path, without '/' delimiters. */ +const PathSegment = min(1, PctEnc, PathChar); + +// This is roughly based on the RFC's "path-abempty" syntax - the other kinds +// seem not to fit a http URL, or be special cases of path-abempty +// (path-absolute, path-noscheme, path-rootless, path-empty) +const Path = seq(min(0, seq("/", PathSegment)), maybe("/")); + +/** Matches a Prefix URI. + * + * A prefix URI is one which is a common prefix to a linked-data SKOS vocab + * namespace, i.e. one to which the term IDs can be suffixed to, in order to get + * a full URI for the term. It also tends to be a URI for the SKOS vocab as a + * whole. + * + * Constructed based on the RFC3986 spec, narrowed for our purposes. + * https://datatracker.ietf.org/doc/html/rfc3986 + * + * This does not match the full specification of an URI as outloned in RFC3986, + * as we have more restrictive ideas of a valid URI. Specifically: + * - no local URIs, like http://localhost + * - no IPv6 ot IPv6 hostnames + * - no port address, like http://example.com:1234 + * - no user or password, like http://joe:secret@example.com + * - only http or https schemes (not ftp, gopher, ldap, etc; also we insist on lower case) + * - no querystring, like http://example.com/somewhere?querystring + * - a trailing anchor character ("#") is allowed, but nothing more (the URI needs to be + * ready for anchor IDs to be suffixed) + * + */ +export const PrefixUri = conc(Scheme, Domain, Path, maybe("#")); From 7dd165070e09af8ceca832f62577cc61742dab02 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Thu, 10 Oct 2024 15:03:35 +0100 Subject: [PATCH 11/22] [common] contract.ts - add validations for ConfigData type Ready for getConfig endpoint, which will be added in an upcoming commit (it will affect the front-end and back end) Note, we can remove the anchors in the QName regex as ZodRegex adds those for us. This makes all the regex definitions consistently non-anchored, which makes them composable into other regexes. --- libs/common/src/api/contract.ts | 46 ++++++++++++++++++++++++++++++++- libs/common/src/rxdefs.ts | 5 +--- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/libs/common/src/api/contract.ts b/libs/common/src/api/contract.ts index cb0d74d2..3e0b5c00 100644 --- a/libs/common/src/api/contract.ts +++ b/libs/common/src/api/contract.ts @@ -2,17 +2,54 @@ import { initContract } from "@ts-rest/core"; import { z } from "zod"; import { extendZodWithOpenApi } from "@anatine/zod-openapi"; import * as Rx from "../rxdefs.js"; +import RxUtils from "../rxutils.js"; +import { Iso639Set1Codes } from "../iso639-1.js"; extendZodWithOpenApi(z); const c = initContract(); +/** Helper function to generate Zod refinements from a RegExp + * + * It promotes the RegExp to be a unicode, entire-string match, if it is + * not already. + * + * It also sets the validation error message attribute from the message parameter. + * + * @returns a Zod validator generated by the Zod.string().refine() method + */ +function ZodRegex(rx: RegExp, message: string) { + return z.string().refine((v: any) => RxUtils.uaon(rx).test(String(v)), { + message, + }); +} + const Location = z.array(z.number()).min(2).max(2); const DatasetId = z.string().regex(Rx.UrlSafeBase64); const DatasetItemId = z.coerce.number().int().nonnegative(); const DatasetItem = z.object({}).passthrough(); const Dataset = z.array(Location); -const QName = z.string().regex(Rx.QName); +const NCName = ZodRegex(Rx.NCName, "Invalid NCName format"); +const QName = ZodRegex(Rx.QName, "Invalid QName format"); +// Developer note: PrefixUri is regex based, as it attempts to avoid the .url() deficiencies in +// https://github.com/colinhacks/zod/issues/2236. But also our concept of a URI is narrowed, see +// documentation for Rx.PrefixUri. +const PrefixUri = ZodRegex(Rx.PrefixUri, "Invalid prefix URI format"); +const PrefixIndex = z.record(PrefixUri, NCName); +// Zod.enum needs some hand-holding to be happy with using object keys, as it wants a +// guaranteed non-zero length list +const [lang0, ...langs] = Object.keys(Iso639Set1Codes); +const Iso639Set1Code = z.enum([lang0, ...langs]); +const VocabDef = z.object({ + title: z.string(), + terms: z.record(NCName, z.string()), +}); +const I18nVocabDefs = z.record(Iso639Set1Code, VocabDef); +const VocabIndex = z.record(NCName, I18nVocabDefs); +const ConfigData = z.object({ + prefixes: PrefixIndex, + vocabs: VocabIndex, +}); const VersionInfo = z.object({ name: z.string(), buildTime: z.string().datetime({ offset: false }), @@ -24,12 +61,19 @@ const ErrorInfo = z.object({ message: z.string() }).passthrough(); export const schemas = { Location, + ConfigData, DatasetId, DatasetItemId, DatasetItem, Dataset, + I18nVocabDefs, + Iso639Set1Code, + NCName, + PrefixUri, + PrefixIndex, QName, VersionInfo, + VocabIndex, ErrorInfo, }; diff --git a/libs/common/src/rxdefs.ts b/libs/common/src/rxdefs.ts index 03d8e280..e9eb0582 100644 --- a/libs/common/src/rxdefs.ts +++ b/libs/common/src/rxdefs.ts @@ -58,10 +58,7 @@ export const NCName = * This regex requires node 10+ to be able to use /u and \p. * */ -export const QName = new RegExp( - `^${NCName.source}[:]${NCName.source}$`, - "imsu", -); +export const QName = new RegExp(`${NCName.source}[:]${NCName.source}`, "imsu"); /** A Prefix-URI scheme. * From e2931d423506b1555eb596cad054559e0d252a46 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Thu, 17 Oct 2024 17:04:15 +0100 Subject: [PATCH 12/22] [common] test/validation.test.ts - add tests for PrefixUri --- libs/common/test/validation.test.ts | 85 ++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/libs/common/test/validation.test.ts b/libs/common/test/validation.test.ts index 3fba5dd0..9f9aca3a 100644 --- a/libs/common/test/validation.test.ts +++ b/libs/common/test/validation.test.ts @@ -3,7 +3,7 @@ import { expect, test } from "vitest"; import { schemas } from "@mykomap/common"; -const { DatasetId, QName } = schemas; +const { DatasetId, QName, PrefixUri } = schemas; test("testing DatasetId validation", async (t) => { const expectTrue = ["0", "A", "z", "_", "-", "01234", "Quick-Brown-Fox_42"]; @@ -42,3 +42,86 @@ test("testing QName validation", async (t) => { expect(QName.safeParse(it).success, `parsing '${it}'`).toBeFalsy(), ); }); + +test("testing PrefixUri validation", async (t) => { + const expectTrue = [ + "http://a", + "http://a/", + "http://e.a", + "http://e.a/", + "http://example.com", + "http://EXAMPLE.COM", + "http://example.Com", + "https://example.com", + "http://www.example.com", + "http://www.example.com/", + "https://w3-example/", + "https://w3-example1.com/", + "http://example.com#", + "http://example.com/#", + "http://example.com/foo", + "http://example.com/foo/", + "http://example.com/foo/bar", + "http://example.com/foo/bar/", + "http://example.com/foo#", + "http://example.com/foo/#", + "http://example.com/foo/bar#", + "http://example.com/foo/bar/#", + "http://example.com/%2e%4F", + "http://example.com/A-Za-z0-9._~!$&'()*+,;=:@-%20/", + ]; + const expectFalse = [ + "http://", + "http://-", + "http://.", + "http://.a", + "http://-a", + "http://a-", + "http://a.", + "http://3a.com", + "http://a.3com", + "http://-a.com", + "http://a-.com", + "http://a.-com", + "http://a.-com", + "http://a.com-", + "http://a_b", + "http://a_b.c", + "http://a@b", + "http://a@b.c", + "http://a:b.c", + "http://b.c:8000", + "HTTP://example.com", + "Http://example.com", + "htt://example.com", + "httpss://example.com", + "http//example.com", + "http:/example.com", + "http:///example.com", + "http//:/example.com", + "http/example.com", + "http/:example.com", + "http//:example.com", + "http://example.com?", + "http://example.com/?", + "http://example.com?q", + "http://example.com/?q", + "http://example.com/foo?q", + "http://example.com/foo/?q", + "http://example.com/#?", + "http://example.com#?", + "http://example.com#q", + "http://example.com/#q", + 'http://example.com/foo"bar', + "http://example.com//foobar", + "http://example.com/foo//bar", + "http://example.com/foobar//", + ]; + + expectTrue.forEach((it) => + expect(PrefixUri.safeParse(it).success, `parsing '${it}'`).toBeTruthy(), + ); + expectFalse.forEach((it) => + expect(PrefixUri.safeParse(it).success, `parsing '${it}'`).toBeFalsy(), + ); +}); From 0c3b248c160acf79b105b36649b89742d96b28d8 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Tue, 22 Oct 2024 15:15:05 +0100 Subject: [PATCH 13/22] [common] test/validation.test.ts - add Iso639Set1Code validation --- libs/common/test/validation.test.ts | 36 ++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/libs/common/test/validation.test.ts b/libs/common/test/validation.test.ts index 9f9aca3a..72d1194c 100644 --- a/libs/common/test/validation.test.ts +++ b/libs/common/test/validation.test.ts @@ -3,7 +3,7 @@ import { expect, test } from "vitest"; import { schemas } from "@mykomap/common"; -const { DatasetId, QName, PrefixUri } = schemas; +const { DatasetId, QName, PrefixUri, Iso639Set1Code } = schemas; test("testing DatasetId validation", async (t) => { const expectTrue = ["0", "A", "z", "_", "-", "01234", "Quick-Brown-Fox_42"]; @@ -125,3 +125,37 @@ test("testing PrefixUri validation", async (t) => { expect(PrefixUri.safeParse(it).success, `parsing '${it}'`).toBeFalsy(), ); }); + +test("testing Iso639Set1Code validation", async (t) => { + const expectTrue = ["en", "fr", "ko", "es"]; + const expectFalse = [ + "xe", + "En", + "eN", + "EN", + "enn", + "en ", + " en", + "e ", + "e n", + " ", + "e_", + "_e", + "e-", + "-e", + "__", + "--", + "'en'", + "en:", + "e:", + ]; + expectTrue.forEach((it) => + expect( + Iso639Set1Code.safeParse(it).success, + `parsing '${it}'`, + ).toBeTruthy(), + ); + expectFalse.forEach((it) => + expect(Iso639Set1Code.safeParse(it).success, `parsing '${it}'`).toBeFalsy(), + ); +}); From e46d48a6191cc94ea866d09846b3592d4eea6d70 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Tue, 22 Oct 2024 23:33:20 +0100 Subject: [PATCH 14/22] [common] test/validation.test.ts - define helpers to shorten and dedupe - expectValid - expectInvalid --- libs/common/test/validation.test.ts | 90 +++++++++++++++-------------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/libs/common/test/validation.test.ts b/libs/common/test/validation.test.ts index 72d1194c..0e7ec36a 100644 --- a/libs/common/test/validation.test.ts +++ b/libs/common/test/validation.test.ts @@ -1,24 +1,52 @@ /// -import { expect, test } from "vitest"; -import { schemas } from "@mykomap/common"; +import { expect, test, Assertion } from "vitest"; +import { schemas } from "../src/index.js"; +import { ZodType } from "zod"; const { DatasetId, QName, PrefixUri, Iso639Set1Code } = schemas; +/** Creates expectations on validating each of an array of cases + * + * Mainly here to provide the common part of expectValid and expectInvalid. + */ +function expectSafeParse( + validation: Z, + cases: unknown[], + expectTest: (assert: Assertion) => void, +) { + cases.forEach((it) => { + const result = validation.safeParse(it); + expectTest(expect(result.success, `parsing '${it}'...\n${result.error}`)); + }); +} + +/** Expect Zod validations of each of the cases given to be truthy */ +function expectValid(validation: Z, cases: unknown[]) { + expectSafeParse(validation, cases, (a) => a.toBeTruthy()); +} + +/** Expect Zod validations of each of the cases given to be falsy */ +function expectInvalid(validation: Z, cases: unknown[]) { + expectSafeParse(validation, cases, (a) => a.toBeFalsy()); +} + test("testing DatasetId validation", async (t) => { - const expectTrue = ["0", "A", "z", "_", "-", "01234", "Quick-Brown-Fox_42"]; - const expectFalse = ["", " ", "/", "?", "&", ":", ".", "="]; - expectTrue.forEach((it) => - expect(DatasetId.safeParse(it).success, `parsing '${it}'`).toBeTruthy(), - ); - expectFalse.forEach((it) => - expect(DatasetId.safeParse(it).success, `parsing '${it}'`).toBeFalsy(), - ); + expectValid(DatasetId, [ + "0", + "A", + "z", + "_", + "-", + "01234", + "Quick-Brown-Fox_42", + ]); + expectInvalid(DatasetId, ["", " ", "/", "?", "&", ":", ".", "="]); }); test("testing QName validation", async (t) => { - const expectTrue = ["a:b", "a1:b1", "_:_", "_1-.:_1-."]; - const expectFalse = [ + expectValid(QName, ["a:b", "a1:b1", "_:_", "_1-.:_1-."]); + expectInvalid(QName, [ "", ":", "a:", @@ -34,17 +62,11 @@ test("testing QName validation", async (t) => { "a:1", "1:a", "-:-", - ]; - expectTrue.forEach((it) => - expect(QName.safeParse(it).success, `parsing '${it}'`).toBeTruthy(), - ); - expectFalse.forEach((it) => - expect(QName.safeParse(it).success, `parsing '${it}'`).toBeFalsy(), - ); + ]); }); test("testing PrefixUri validation", async (t) => { - const expectTrue = [ + expectValid(PrefixUri, [ "http://a", "http://a/", "http://e.a", @@ -69,8 +91,8 @@ test("testing PrefixUri validation", async (t) => { "http://example.com/foo/bar/#", "http://example.com/%2e%4F", "http://example.com/A-Za-z0-9._~!$&'()*+,;=:@-%20/", - ]; - const expectFalse = [ + ]); + expectInvalid(PrefixUri, [ "http://", "http://-", "http://.", @@ -116,19 +138,12 @@ test("testing PrefixUri validation", async (t) => { "http://example.com//foobar", "http://example.com/foo//bar", "http://example.com/foobar//", - ]; - - expectTrue.forEach((it) => - expect(PrefixUri.safeParse(it).success, `parsing '${it}'`).toBeTruthy(), - ); - expectFalse.forEach((it) => - expect(PrefixUri.safeParse(it).success, `parsing '${it}'`).toBeFalsy(), - ); + ]); }); test("testing Iso639Set1Code validation", async (t) => { - const expectTrue = ["en", "fr", "ko", "es"]; - const expectFalse = [ + expectValid(Iso639Set1Code, ["en", "fr", "ko", "es"]); + expectInvalid(Iso639Set1Code, [ "xe", "En", "eN", @@ -148,14 +163,5 @@ test("testing Iso639Set1Code validation", async (t) => { "'en'", "en:", "e:", - ]; - expectTrue.forEach((it) => - expect( - Iso639Set1Code.safeParse(it).success, - `parsing '${it}'`, - ).toBeTruthy(), - ); - expectFalse.forEach((it) => - expect(Iso639Set1Code.safeParse(it).success, `parsing '${it}'`).toBeFalsy(), - ); + ]); }); From a739cd5106f67111eee45b3fcb5fed8e9dcd38c9 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Tue, 22 Oct 2024 23:35:05 +0100 Subject: [PATCH 15/22] [common] test/validation.test.ts - define glorpJson helper to shorten and dedupe --- libs/common/test/validation.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/libs/common/test/validation.test.ts b/libs/common/test/validation.test.ts index 0e7ec36a..2cf999e4 100644 --- a/libs/common/test/validation.test.ts +++ b/libs/common/test/validation.test.ts @@ -3,6 +3,9 @@ import { expect, test, Assertion } from "vitest"; import { schemas } from "../src/index.js"; import { ZodType } from "zod"; +import { slurpJsonSync } from "./file-utils.js"; +import { globSync } from "glob"; +import { join } from "node:path"; const { DatasetId, QName, PrefixUri, Iso639Set1Code } = schemas; @@ -31,6 +34,14 @@ function expectInvalid(validation: Z, cases: unknown[]) { expectSafeParse(validation, cases, (a) => a.toBeFalsy()); } +/** Find files given by a glob and parse as JSON */ +function glorpJson(pathGlob: string) { + const paths = globSync(pathGlob).map((path) => + join(import.meta.dirname, path), + ); + return paths.map((path) => slurpJsonSync(path)); +} + test("testing DatasetId validation", async (t) => { expectValid(DatasetId, [ "0", From 937f5584ccb6adf11b236be518f78217a0574cda Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Tue, 22 Oct 2024 23:38:46 +0100 Subject: [PATCH 16/22] [common] test/validation.test.ts - add tests for ConfigData --- .../validation/ConfigData/case-1.good.json | 30 +++++++++++++++++++ libs/common/test/validation.test.ts | 7 ++++- 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 libs/common/test/data/validation/ConfigData/case-1.good.json diff --git a/libs/common/test/data/validation/ConfigData/case-1.good.json b/libs/common/test/data/validation/ConfigData/case-1.good.json new file mode 100644 index 00000000..e116519f --- /dev/null +++ b/libs/common/test/data/validation/ConfigData/case-1.good.json @@ -0,0 +1,30 @@ +{ + "prefixes": { + "https://dev.lod.coop/essglobal/2.1/standard/activities-modified/": "am" + }, + "vocabs": { + "am": { + "en": { + "title": "Activities (Modified)", + "terms": { + "AM10": "Arts, Media, Culture & Leisure", + "AM20": "Campaigning, Activism & Advocacy", + "AM30": "Community & Collective Spaces", + "AM40": "Education", + "AM50": "Energy", + "AM60": "Food", + "AM70": "Goods & Services", + "AM80": "Health, Social Care & Wellbeing", + "AM90": "Housing", + "AM100": "Money & Finance", + "AM110": "Nature, Conservation & Environment", + "AM120": "Reduce, Reuse, Repair & Recycle", + "AM130": "Agriculture", + "AM140": "Industry", + "AM150": "Utilities", + "AM160": "Transport" + } + } + } + } +} diff --git a/libs/common/test/validation.test.ts b/libs/common/test/validation.test.ts index 2cf999e4..f5feb9d1 100644 --- a/libs/common/test/validation.test.ts +++ b/libs/common/test/validation.test.ts @@ -7,7 +7,7 @@ import { slurpJsonSync } from "./file-utils.js"; import { globSync } from "glob"; import { join } from "node:path"; -const { DatasetId, QName, PrefixUri, Iso639Set1Code } = schemas; +const { DatasetId, QName, PrefixUri, Iso639Set1Code, ConfigData } = schemas; /** Creates expectations on validating each of an array of cases * @@ -176,3 +176,8 @@ test("testing Iso639Set1Code validation", async (t) => { "e:", ]); }); + +test("testing ConfigData validation", async (t) => { + expectValid(ConfigData, glorpJson("data/validation/vocabIndex/*.good.json")); + expectInvalid(ConfigData, glorpJson("data/validation/vocabIndex/*.bad.json")); +}); From f3c2cfc97a9a0c567fb7605c67c4cd5ce367e230 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Wed, 23 Oct 2024 12:51:47 +0100 Subject: [PATCH 17/22] [common] contract.ts - add getConfig endpoint This responds with a ConfigData object --- libs/common/src/api/contract.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/libs/common/src/api/contract.ts b/libs/common/src/api/contract.ts index 3e0b5c00..3240ebb2 100644 --- a/libs/common/src/api/contract.ts +++ b/libs/common/src/api/contract.ts @@ -165,6 +165,32 @@ export const contract = c.router({ }), }, }, + getConfig: { + method: "GET", + path: "/dataset/:datasetId/config", + summary: "obtain various configured parameters for a map", + description: + "Obtains configured parameters for a map, which amongst other things, " + + "include default values for various options, and definitions of " + + "vocabulary terms with their localised labels, that are used to " + + "interpret identifers in the data and/or elsewhere.", + pathParams: z.object({ + datasetId: DatasetId.openapi({ + // description: "uniquely specifies the dataset wanted", + }), + }), + responses: { + 200: ConfigData.openapi({ + // description: "variuos configured parameters for a map", + }), + 400: ErrorInfo.openapi({ + // description: "bad input parameter", + }), + 404: ErrorInfo.openapi({ + // description: "no such map", + }), + }, + }, getVersion: { method: "GET", path: "/version", From b45452216ee2d9ff9d9b7f98cf40d29e65947ba1 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Thu, 17 Oct 2024 18:27:57 +0100 Subject: [PATCH 18/22] [common] src/api/mykomap-openapi.json - regenerated spec --- libs/common/src/api/mykomap-openapi.json | 117 +++++++++++++++++++++-- 1 file changed, 110 insertions(+), 7 deletions(-) diff --git a/libs/common/src/api/mykomap-openapi.json b/libs/common/src/api/mykomap-openapi.json index b5fba42c..7bff9d9b 100644 --- a/libs/common/src/api/mykomap-openapi.json +++ b/libs/common/src/api/mykomap-openapi.json @@ -13,7 +13,7 @@ "required": true, "schema": { "type": "string", - "pattern": "^[A-Z0-9_-]+$" + "pattern": "^[A-Za-z0-9_-]+$" } } ], @@ -90,7 +90,7 @@ "required": true, "schema": { "type": "string", - "pattern": "^[A-Z0-9_-]+$" + "pattern": "^[A-Za-z0-9_-]+$" } }, { @@ -108,13 +108,11 @@ { "type": "array", "items": { - "type": "string", - "pattern": "^(?:[_\\p{Ll}\\p{Lu}\\p{Lt}\\p{Lo}\\p{Nl}][_.\\p{L}\\p{M}\\p{Nd}\\p{Nl}-]*)[:](?:[_\\p{Ll}\\p{Lu}\\p{Lt}\\p{Lo}\\p{Nl}][_.\\p{L}\\p{M}\\p{Nd}\\p{Nl}-]*)$" + "type": "string" } }, { - "type": "string", - "pattern": "^(?:[_\\p{Ll}\\p{Lu}\\p{Lt}\\p{Lo}\\p{Nl}][_.\\p{L}\\p{M}\\p{Nd}\\p{Nl}-]*)[:](?:[_\\p{Ll}\\p{Lu}\\p{Lt}\\p{Lo}\\p{Nl}][_.\\p{L}\\p{M}\\p{Nd}\\p{Nl}-]*)$" + "type": "string" } ] } @@ -190,7 +188,7 @@ "required": true, "schema": { "type": "string", - "pattern": "^[A-Z0-9_-]+$" + "pattern": "^[A-Za-z0-9_-]+$" } }, { @@ -259,6 +257,111 @@ } } }, + "/dataset/{datasetId}/config": { + "get": { + "description": "Obtains configured parameters for a map, which amongst other things, include default values for various options, and definitions of vocabulary terms with their localised labels, that are used to interpret identifers in the data and/or elsewhere.", + "summary": "obtain various configured parameters for a map", + "tags": [], + "parameters": [ + { + "name": "datasetId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]+$" + } + } + ], + "operationId": "getConfig", + "responses": { + "200": { + "description": "200", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "prefixes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "vocabs": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "terms": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "title", + "terms" + ] + } + } + } + }, + "required": [ + "prefixes", + "vocabs" + ] + } + } + } + }, + "400": { + "description": "400", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "additionalProperties": true + } + } + } + }, + "404": { + "description": "404", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "additionalProperties": true + } + } + } + } + } + } + }, "/version": { "get": { "description": "Obtains version information about the backend Mykomap server, in the form of a JSON object", From 45a9333efdbfb9cebd907d8c72f6a8f79fd71570 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Thu, 17 Oct 2024 17:03:27 +0100 Subject: [PATCH 19/22] [back-end] src/routes.ts - add stub getConfig implementation --- apps/back-end/src/routes.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/back-end/src/routes.ts b/apps/back-end/src/routes.ts index afed75c1..ce164f45 100644 --- a/apps/back-end/src/routes.ts +++ b/apps/back-end/src/routes.ts @@ -137,6 +137,13 @@ export function MykomapRouter( return reply; }, + async getConfig(req) { + return { + status: 404, + body: { message: "whoops" }, + }; + }, + async getVersion(req) { return { body: __BUILD_INFO__, From b57e53352dff9057da2feee8354426a757231477 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Thu, 17 Oct 2024 18:27:13 +0100 Subject: [PATCH 20/22] [back-end] src/routes.ts - add getConfig implementation Which gets the data verbatim from the data store. --- apps/back-end/src/routes.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/back-end/src/routes.ts b/apps/back-end/src/routes.ts index ce164f45..40eaa87d 100644 --- a/apps/back-end/src/routes.ts +++ b/apps/back-end/src/routes.ts @@ -137,11 +137,16 @@ export function MykomapRouter( return reply; }, - async getConfig(req) { - return { - status: 404, - body: { message: "whoops" }, - }; + async getConfig({ params: { datasetId }, request, reply }) { + // Validate the parameters some more + + if (!sendJson(request, reply, filePath("datasets", datasetId, "config"))) + throw new TsRestResponseError(contract.getDataset, { + status: 404, + body: { message: `unknown datasetId '${datasetId}'` }, + }); + + return reply; }, async getVersion(req) { From 77e8738f50c69bb0a69b349812a16a5b1dc51e26 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Thu, 17 Oct 2024 18:34:40 +0100 Subject: [PATCH 21/22] [back-end] test/data/datasets/test-A/config.json - dummy config Adapted from oxford dataset. --- .../test/data/datasets/test-A/config.json | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 apps/back-end/test/data/datasets/test-A/config.json diff --git a/apps/back-end/test/data/datasets/test-A/config.json b/apps/back-end/test/data/datasets/test-A/config.json new file mode 100644 index 00000000..e116519f --- /dev/null +++ b/apps/back-end/test/data/datasets/test-A/config.json @@ -0,0 +1,30 @@ +{ + "prefixes": { + "https://dev.lod.coop/essglobal/2.1/standard/activities-modified/": "am" + }, + "vocabs": { + "am": { + "en": { + "title": "Activities (Modified)", + "terms": { + "AM10": "Arts, Media, Culture & Leisure", + "AM20": "Campaigning, Activism & Advocacy", + "AM30": "Community & Collective Spaces", + "AM40": "Education", + "AM50": "Energy", + "AM60": "Food", + "AM70": "Goods & Services", + "AM80": "Health, Social Care & Wellbeing", + "AM90": "Housing", + "AM100": "Money & Finance", + "AM110": "Nature, Conservation & Environment", + "AM120": "Reduce, Reuse, Repair & Recycle", + "AM130": "Agriculture", + "AM140": "Industry", + "AM150": "Utilities", + "AM160": "Transport" + } + } + } + } +} From b1123817d18f4bec86c5aa01d14274d8827899ea Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Wed, 23 Oct 2024 12:02:43 +0100 Subject: [PATCH 22/22] package-lock.json --- package-lock.json | 1202 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 1029 insertions(+), 173 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd961ecf..a845ef3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -115,6 +115,7 @@ "dependencies": { "@ts-rest/core": "^3.51.0", "@ts-rest/open-api": "^3.51.0", + "glob": "^11.0.0", "zod": "^3.23.8" }, "devDependencies": { @@ -123,7 +124,8 @@ "typescript": "^5.5.4", "vite": "^5.4.3", "vite-node": "^2.0.5", - "vite-plugin-dts": "^4.1.0" + "vite-plugin-dts": "^4.1.0", + "vite-test": "^0.4.0" } }, "node_modules/@adobe/css-tools": { @@ -3626,7 +3628,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -6520,6 +6521,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", @@ -7290,7 +7302,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -7303,7 +7314,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -7782,7 +7792,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -7841,6 +7850,43 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -7887,7 +7933,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -7969,6 +8014,16 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -8237,6 +8292,13 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, "node_modules/chromatic": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-11.12.0.tgz", @@ -8607,7 +8669,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -8931,6 +8992,13 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/devtools-protocol": { + "version": "0.0.818844", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.818844.tgz", + "integrity": "sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -9021,7 +9089,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, "license": "MIT" }, "node_modules/editorconfig": { @@ -9061,7 +9128,6 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, "license": "MIT" }, "node_modules/encodeurl": { @@ -9346,175 +9412,515 @@ "@esbuild/win32-x64": "0.23.1" } }, - "node_modules/esbuild-register": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", - "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", + "node_modules/esbuild-android-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz", + "integrity": "sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "peerDependencies": { - "esbuild": ">=0.12 <1" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/esbuild-android-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz", + "integrity": "sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6" + "node": ">=12" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "node_modules/esbuild-darwin-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz", + "integrity": "sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "node_modules/esbuild-darwin-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz", + "integrity": "sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" + "node": ">=12" } }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/esbuild-freebsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz", + "integrity": "sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=0.10.0" + "node": ">=12" } }, - "node_modules/eslint": { - "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.", + "node_modules/esbuild-freebsd-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz", + "integrity": "sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@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", - "@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.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", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "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.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=12" } }, - "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "node_modules/esbuild-linux-32": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz", + "integrity": "sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/eslint-config-react-app": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", - "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", + "node_modules/esbuild-linux-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz", + "integrity": "sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/core": "^7.16.0", - "@babel/eslint-parser": "^7.16.3", - "@rushstack/eslint-patch": "^1.1.0", - "@typescript-eslint/eslint-plugin": "^5.5.0", - "@typescript-eslint/parser": "^5.5.0", - "babel-preset-react-app": "^10.0.1", - "confusing-browser-globals": "^1.0.11", - "eslint-plugin-flowtype": "^8.0.3", - "eslint-plugin-import": "^2.25.3", - "eslint-plugin-jest": "^25.3.0", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.27.1", - "eslint-plugin-react-hooks": "^4.3.0", - "eslint-plugin-testing-library": "^5.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=14.0.0" + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz", + "integrity": "sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz", + "integrity": "sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz", + "integrity": "sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz", + "integrity": "sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz", + "integrity": "sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz", + "integrity": "sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz", + "integrity": "sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz", + "integrity": "sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-register": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", + "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz", + "integrity": "sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz", + "integrity": "sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz", + "integrity": "sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz", + "integrity": "sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint": { + "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.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", + "@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.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", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "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.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-config-react-app": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", + "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/eslint-parser": "^7.16.3", + "@rushstack/eslint-patch": "^1.1.0", + "@typescript-eslint/eslint-plugin": "^5.5.0", + "@typescript-eslint/parser": "^5.5.0", + "babel-preset-react-app": "^10.0.1", + "confusing-browser-globals": "^1.0.11", + "eslint-plugin-flowtype": "^8.0.3", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jest": "^25.3.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.27.1", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-testing-library": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" }, "peerDependencies": { "eslint": "^8.0.0" @@ -10402,6 +10808,43 @@ "node": ">=0.10.0" } }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fast-content-type-parse": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz", @@ -10688,6 +11131,16 @@ "reusify": "^1.0.4" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -10914,7 +11367,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", @@ -10975,6 +11427,13 @@ "node": ">= 0.6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -11191,7 +11650,6 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", - "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -11228,7 +11686,6 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -12042,7 +12499,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12343,7 +12799,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/isobject": { @@ -12454,7 +12909,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -12846,7 +13300,6 @@ "version": "11.0.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", - "dev": true, "license": "ISC", "engines": { "node": "20 || >=22" @@ -13140,12 +13593,18 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT" + }, "node_modules/mlly": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.2.tgz", @@ -13255,6 +13714,52 @@ "node": ">= 0.6" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -13674,7 +14179,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { @@ -13774,7 +14278,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -13790,7 +14293,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^11.0.0", @@ -13849,6 +14351,13 @@ "pbf": "bin/pbf" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", @@ -14550,6 +15059,16 @@ "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", "license": "MIT" }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -14586,6 +15105,13 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -14621,6 +15147,141 @@ "node": ">=6" } }, + "node_modules/puppeteer": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-5.5.0.tgz", + "integrity": "sha512-OM8ZvTXAhfgFA7wBIIGlPQzvyEETzDjeRa4mZRCRHxYL+GNH5WAuYUQdja3rpWZvkX/JKqmuVgbsxDNsDFjMEg==", + "deprecated": "< 22.8.2 is no longer supported", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.818844", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^4.0.0", + "node-fetch": "^2.6.1", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "engines": { + "node": ">=10.18.1" + } + }, + "node_modules/puppeteer/node_modules/agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/puppeteer/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/puppeteer/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/puppeteer/node_modules/https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "5", + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/puppeteer/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/puppeteer/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/puppeteer/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -15627,7 +16288,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -15640,7 +16300,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -15676,7 +16335,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -15943,7 +16601,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -15962,7 +16619,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -15977,7 +16633,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -15987,14 +16642,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -16108,7 +16761,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -16125,7 +16777,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -16138,7 +16789,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -16445,6 +17095,36 @@ "postcss": "^8.0.0" } }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/telejson": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/telejson/-/telejson-7.2.0.tgz", @@ -16586,6 +17266,13 @@ "real-require": "^0.2.0" } }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -17017,6 +17704,42 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/unbzip2-stream/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -17440,6 +18163,129 @@ } } }, + "node_modules/vite-test": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/vite-test/-/vite-test-0.4.0.tgz", + "integrity": "sha512-3aIexyCxhyCMxuvNOh+IW0vt/k+x8pLRpvqSadGAt8gNmLkx1GjTgSw0Ta+Nh06WAUnIUgJyLKl/T+9k54VyVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "puppeteer": "^5.5.0", + "vite": "^2.0.0-beta.59", + "zora": "^4.0.2" + }, + "bin": { + "vite-test": "bin/ci.mjs" + } + }, + "node_modules/vite-test/node_modules/@esbuild/linux-loong64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz", + "integrity": "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-test/node_modules/esbuild": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.54.tgz", + "integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/linux-loong64": "0.14.54", + "esbuild-android-64": "0.14.54", + "esbuild-android-arm64": "0.14.54", + "esbuild-darwin-64": "0.14.54", + "esbuild-darwin-arm64": "0.14.54", + "esbuild-freebsd-64": "0.14.54", + "esbuild-freebsd-arm64": "0.14.54", + "esbuild-linux-32": "0.14.54", + "esbuild-linux-64": "0.14.54", + "esbuild-linux-arm": "0.14.54", + "esbuild-linux-arm64": "0.14.54", + "esbuild-linux-mips64le": "0.14.54", + "esbuild-linux-ppc64le": "0.14.54", + "esbuild-linux-riscv64": "0.14.54", + "esbuild-linux-s390x": "0.14.54", + "esbuild-netbsd-64": "0.14.54", + "esbuild-openbsd-64": "0.14.54", + "esbuild-sunos-64": "0.14.54", + "esbuild-windows-32": "0.14.54", + "esbuild-windows-64": "0.14.54", + "esbuild-windows-arm64": "0.14.54" + } + }, + "node_modules/vite-test/node_modules/rollup": { + "version": "2.77.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.77.3.tgz", + "integrity": "sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/vite-test/node_modules/vite": { + "version": "2.9.18", + "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.18.tgz", + "integrity": "sha512-sAOqI5wNM9QvSEE70W3UGMdT8cyEn0+PmJMTFvTB8wB0YbYUWw3gUbY62AOyrXosGieF2htmeLATvNxpv/zNyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.14.27", + "postcss": "^8.4.13", + "resolve": "^1.22.0", + "rollup": ">=2.59.0 <2.78.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": ">=12.2.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "less": "*", + "sass": "*", + "stylus": "*" + }, + "peerDependenciesMeta": { + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + } + } + }, "node_modules/vite/node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -18271,7 +19117,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -18397,7 +19242,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -18416,7 +19260,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -18434,7 +19277,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -18444,7 +19286,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -18460,14 +19301,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -18482,7 +19321,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -18637,6 +19475,17 @@ "node": ">=8" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -18658,6 +19507,13 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zora": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/zora/-/zora-4.1.0.tgz", + "integrity": "sha512-tFowFksaFIwp0hzZ9dhM1rlaicS6VNjNMxEH3phgR5Glz3EMAoRujjJ50aTLAsl//SArTdG/kBt5EuvhqS6mww==", + "dev": true, + "license": "MIT" } } }