From 7d0b64414a28aaa317483bf2f649b25b042c8d32 Mon Sep 17 00:00:00 2001 From: Jeff Davidson Date: Wed, 23 Oct 2024 21:55:13 -0700 Subject: [PATCH 1/4] Deduplicate new puzzles by URL. When adding a puzzle, we start a transaction to atomically check if a puzzle already exists with that URL and only add it if either no such puzzle exists or if the client indicates that duplicate URLs are allowed. In the event of a transaction failure, we delete the Google document that was created optimistically before saving the puzzle. On the client side, we add a new checkbox to allow duplicate URLs, unchecked by default. If we encounter a duplicate URL error, we show an error indicating that the puzzle may be a dupe and telling the user what to check if the duplicate URLs are intentional. --- imports/client/components/PuzzleModalForm.tsx | 54 ++++++++++++++++++- imports/lib/models/Model.ts | 9 ++-- imports/methods/createPuzzle.ts | 1 + imports/methods/updatePuzzle.ts | 1 + imports/server/gdrive.ts | 23 ++++++++ imports/server/methods/createPuzzle.ts | 48 +++++++++++++++-- imports/server/methods/updatePuzzle.ts | 3 ++ 7 files changed, 131 insertions(+), 8 deletions(-) diff --git a/imports/client/components/PuzzleModalForm.tsx b/imports/client/components/PuzzleModalForm.tsx index 757ae9299..cceebc352 100644 --- a/imports/client/components/PuzzleModalForm.tsx +++ b/imports/client/components/PuzzleModalForm.tsx @@ -1,3 +1,4 @@ +import { Meteor } from "meteor/meteor"; import React, { Suspense, useCallback, @@ -9,6 +10,7 @@ import React, { } from "react"; import Alert from "react-bootstrap/Alert"; import Col from "react-bootstrap/Col"; +import FormCheck from "react-bootstrap/FormCheck"; import type { FormControlProps } from "react-bootstrap/FormControl"; import FormControl from "react-bootstrap/FormControl"; import FormGroup from "react-bootstrap/FormGroup"; @@ -38,6 +40,7 @@ export interface PuzzleModalFormSubmitPayload { tags: string[]; docType?: GdriveMimeTypesType; expectedAnswerCount: number; + allowDuplicateUrls?: boolean; } enum PuzzleModalFormSubmitState { @@ -93,6 +96,9 @@ const PuzzleModalForm = React.forwardRef( const [expectedAnswerCount, setExpectedAnswerCount] = useState( puzzle ? puzzle.expectedAnswerCount : 1, ); + const [allowDuplicateUrls, setAllowDuplicateUrls] = useState< + boolean | undefined + >(puzzle ? undefined : false); const [submitState, setSubmitState] = useState( PuzzleModalFormSubmitState.IDLE, ); @@ -157,6 +163,13 @@ const PuzzleModalForm = React.forwardRef( setExpectedAnswerCountDirty(true); }, []); + const onAllowDuplicateUrlsChange = useCallback( + (event: React.ChangeEvent) => { + setAllowDuplicateUrls(event.currentTarget.checked); + }, + [], + ); + const onFormSubmit = useCallback( (callback: () => void) => { setSubmitState(PuzzleModalFormSubmitState.SUBMITTING); @@ -170,9 +183,24 @@ const PuzzleModalForm = React.forwardRef( if (docType) { payload.docType = docType; } + if (allowDuplicateUrls) { + payload.allowDuplicateUrls = allowDuplicateUrls; + } onSubmit(payload, (error) => { if (error) { - setErrorMessage(error.message); + if ( + error instanceof Meteor.Error && + typeof error.error === "number" && + error.error === 409 + ) { + setErrorMessage( + "A puzzle already exists with this URL - did someone else already add this" + + ' puzzle? To force creation anyway, check the "Allow puzzles with identical' + + ' URLs" box above and try again.', + ); + } else { + setErrorMessage(error.message); + } setSubmitState(PuzzleModalFormSubmitState.FAILED); } else { setSubmitState(PuzzleModalFormSubmitState.IDLE); @@ -181,11 +209,21 @@ const PuzzleModalForm = React.forwardRef( setUrlDirty(false); setTagsDirty(false); setExpectedAnswerCountDirty(false); + setAllowDuplicateUrls(false); callback(); } }); }, - [onSubmit, huntId, title, url, tags, expectedAnswerCount, docType], + [ + onSubmit, + huntId, + title, + url, + tags, + expectedAnswerCount, + docType, + allowDuplicateUrls, + ], ); const show = useCallback(() => { @@ -278,6 +316,17 @@ const PuzzleModalForm = React.forwardRef( ) : null; + const allowDuplicateUrlsCheckbox = + !puzzle && typeof allowDuplicateUrls === "boolean" ? ( + + ) : null; + return ( + {allowDuplicateUrlsCheckbox} diff --git a/imports/lib/models/Model.ts b/imports/lib/models/Model.ts index 4b031791f..259bf5120 100644 --- a/imports/lib/models/Model.ts +++ b/imports/lib/models/Model.ts @@ -4,6 +4,7 @@ import type { IndexDirection, IndexSpecification, CreateIndexesOptions, + ClientSession, } from "mongodb"; import { z } from "zod"; import { IsInsert, IsUpdate, IsUpsert, stringId } from "./customTypes"; @@ -447,6 +448,7 @@ class Model< doc: z.input, options: { bypassSchema?: boolean | undefined; + session?: ClientSession | undefined; } = {}, ): Promise> { const { bypassSchema } = options; @@ -456,9 +458,10 @@ class Model< raw = { ...doc, _id: this.collection._makeNewID() }; } try { - await this.collection - .rawCollection() - .insertOne(raw, { bypassDocumentValidation: true }); + await this.collection.rawCollection().insertOne(raw, { + bypassDocumentValidation: true, + session: options.session, + }); return raw._id; } catch (e) { formatValidationError(e); diff --git a/imports/methods/createPuzzle.ts b/imports/methods/createPuzzle.ts index c3a8a2163..505d5b27b 100644 --- a/imports/methods/createPuzzle.ts +++ b/imports/methods/createPuzzle.ts @@ -9,6 +9,7 @@ export default new TypedMethod< tags: string[]; expectedAnswerCount: number; docType: GdriveMimeTypesType; + allowDuplicateUrls?: boolean; }, string >("Puzzles.methods.create"); diff --git a/imports/methods/updatePuzzle.ts b/imports/methods/updatePuzzle.ts index bab00affa..2144df81f 100644 --- a/imports/methods/updatePuzzle.ts +++ b/imports/methods/updatePuzzle.ts @@ -7,6 +7,7 @@ export default new TypedMethod< url?: string; tags: string[]; expectedAnswerCount: number; + allowDuplicateUrls?: boolean; }, void >("Puzzles.methods.update"); diff --git a/imports/server/gdrive.ts b/imports/server/gdrive.ts index 4c9b147db..70fdfee4e 100644 --- a/imports/server/gdrive.ts +++ b/imports/server/gdrive.ts @@ -86,6 +86,16 @@ async function createDocument( return fileId; } +async function deleteDocument(id: string) { + await checkClientOk(); + if (!GoogleClient.drive) + throw new Meteor.Error(500, "Google integration is disabled"); + + await GoogleClient.drive.files.delete({ + fileId: id, + }); +} + export async function moveDocument(id: string, newParentId: string) { await checkClientOk(); if (!GoogleClient.drive) @@ -301,3 +311,16 @@ export async function ensureDocument( return doc!; } + +export async function deleteUnusedDocument(puzzle: { _id: string }) { + const doc = await Documents.findOneAsync({ puzzle: puzzle._id }); + if (!doc) { + return; + } + + await checkClientOk(); + await withLock(`puzzle:${puzzle._id}:documents`, async () => { + await deleteDocument(doc.value.id); + await Documents.removeAsync(doc._id); + }); +} diff --git a/imports/server/methods/createPuzzle.ts b/imports/server/methods/createPuzzle.ts index 0fef6719b..13d542a6c 100644 --- a/imports/server/methods/createPuzzle.ts +++ b/imports/server/methods/createPuzzle.ts @@ -1,5 +1,6 @@ import { check, Match } from "meteor/check"; import { Meteor } from "meteor/meteor"; +import { MongoInternals } from "meteor/mongo"; import { Random } from "meteor/random"; import Flags from "../../Flags"; import Logger from "../../Logger"; @@ -11,7 +12,7 @@ import Puzzles from "../../lib/models/Puzzles"; import { userMayWritePuzzlesForHunt } from "../../lib/permission_stubs"; import createPuzzle from "../../methods/createPuzzle"; import GlobalHooks from "../GlobalHooks"; -import { ensureDocument } from "../gdrive"; +import { deleteUnusedDocument, ensureDocument } from "../gdrive"; import getOrCreateTagByName from "../getOrCreateTagByName"; import GoogleClient from "../googleClientRefresher"; import defineMethod from "./defineMethod"; @@ -27,11 +28,20 @@ defineMethod(createPuzzle, { docType: Match.OneOf( ...(Object.keys(GdriveMimeTypes) as GdriveMimeTypesType[]), ), + allowDuplicateUrls: Match.Optional(Boolean), }); return arg; }, - async run({ huntId, title, tags, expectedAnswerCount, docType, url }) { + async run({ + huntId, + title, + tags, + expectedAnswerCount, + docType, + url, + allowDuplicateUrls, + }) { check(this.userId, String); const hunt = await Hunts.findOneAsync(huntId); @@ -81,7 +91,39 @@ defineMethod(createPuzzle, { await ensureDocument(fullPuzzle, docType); } - await Puzzles.insertAsync(fullPuzzle); + // In a transaction, look for a puzzle with the same URL. If present, we + // reject the insertion unless the client overrides it. + const client = MongoInternals.defaultRemoteCollectionDriver().mongo.client; + const session = client.startSession(); + try { + await session.withTransaction(async () => { + if (url) { + const existingPuzzleWithUrl = await Puzzles.collection + .rawCollection() + .findOne({ url }, { session }); + if (existingPuzzleWithUrl && !allowDuplicateUrls) { + throw new Meteor.Error( + 409, + `Puzzle with URL ${url} already exists`, + ); + } + } + await Puzzles.insertAsync(fullPuzzle, { session }); + }); + } catch (error) { + // In the case of any error, try to delete the document we created before the transaction. + // If that fails too, let the original error propagate. + try { + await deleteUnusedDocument(fullPuzzle); + } catch (deleteError) { + Logger.warn("Unable to clean up document on failed puzzle creation", { + error: deleteError, + }); + } + throw error; + } finally { + await session.endSession(); + } // Run any puzzle-creation hooks, like creating a default document // attachment or announcing the puzzle to Slack. diff --git a/imports/server/methods/updatePuzzle.ts b/imports/server/methods/updatePuzzle.ts index 1927b5ec4..8021f4f83 100644 --- a/imports/server/methods/updatePuzzle.ts +++ b/imports/server/methods/updatePuzzle.ts @@ -22,6 +22,9 @@ defineMethod(updatePuzzle, { url: Match.Optional(String), tags: [String], expectedAnswerCount: Number, + // We accept this argument since it's provided by the form, but it's not checked here - only + // during puzzle creation, to avoid duplicates when creating new puzzles. + allowDuplicateUrls: Match.Optional(Boolean), }); return arg; From 386f521a94e69aa42e73f4da71cdcdfa18a1af91 Mon Sep 17 00:00:00 2001 From: Jeff Davidson Date: Mon, 25 Nov 2024 21:50:30 -0800 Subject: [PATCH 2/4] Add new APIs for creating puzzles. - Propagate user ID to API methods in authenticator - Add API to list hunts - Add API to list tags for a hunt (for autocompletion) - Add API to create a puzzle Most of the complexity is in the latter. The bulk of the implementation is shared with the existing flow for creating puzzles, but there is some jank: - Wherever we were looking at this.userId, which is populated for Meteor method execution but not this API path, we need to pass an explicit userId instead. One pain point is the auto-inferred userId for the createdBy column of many tables. - We check the types of the input in both the Meteor method (to take advantage of the validate scaffolding) as well as the shared logic (so it's there for API calls). --- imports/server/addPuzzle.ts | 133 ++++++++++++++++++ imports/server/api.ts | 6 + imports/server/api/authenticator.ts | 2 + imports/server/api/resources/createPuzzle.ts | 48 +++++++ imports/server/api/resources/hunts.ts | 42 ++++++ imports/server/api/resources/tags.ts | 34 +++++ imports/server/gdrive.ts | 2 + imports/server/getOrCreateTagByName.ts | 9 +- imports/server/methods/addPuzzleTag.ts | 2 +- .../methods/configureOrganizeGoogleDrive.ts | 2 +- imports/server/methods/createHunt.ts | 2 +- imports/server/methods/createPuzzle.ts | 108 ++------------ .../server/methods/ensurePuzzleDocument.ts | 2 +- imports/server/methods/updatePuzzle.ts | 4 +- package-lock.json | 25 +++- package.json | 1 + 16 files changed, 311 insertions(+), 111 deletions(-) create mode 100644 imports/server/addPuzzle.ts create mode 100644 imports/server/api/resources/createPuzzle.ts create mode 100644 imports/server/api/resources/hunts.ts create mode 100644 imports/server/api/resources/tags.ts diff --git a/imports/server/addPuzzle.ts b/imports/server/addPuzzle.ts new file mode 100644 index 000000000..129418c35 --- /dev/null +++ b/imports/server/addPuzzle.ts @@ -0,0 +1,133 @@ +import { check, Match } from "meteor/check"; +import { Meteor } from "meteor/meteor"; +import { MongoInternals } from "meteor/mongo"; +import { Random } from "meteor/random"; +import Flags from "../Flags"; +import Logger from "../Logger"; +import type { GdriveMimeTypesType } from "../lib/GdriveMimeTypes"; +import GdriveMimeTypes from "../lib/GdriveMimeTypes"; +import Hunts from "../lib/models/Hunts"; +import MeteorUsers from "../lib/models/MeteorUsers"; +import Puzzles from "../lib/models/Puzzles"; +import { userMayWritePuzzlesForHunt } from "../lib/permission_stubs"; +import GlobalHooks from "./GlobalHooks"; +import { deleteUnusedDocument, ensureDocument } from "./gdrive"; +import getOrCreateTagByName from "./getOrCreateTagByName"; +import GoogleClient from "./googleClientRefresher"; + +export default async function addPuzzle({ + userId, + huntId, + title, + tags, + expectedAnswerCount, + docType, + url, + allowDuplicateUrls, +}: { + userId: string; + huntId: string; + title: string; + url?: string; + tags: string[]; + expectedAnswerCount: number; + docType: GdriveMimeTypesType; + allowDuplicateUrls?: boolean; +}) { + check(userId, String); + check(huntId, String); + check(url, Match.Optional(String)); + check(tags, [String]); + check(expectedAnswerCount, Number); + check( + docType, + Match.OneOf(...(Object.keys(GdriveMimeTypes) as GdriveMimeTypesType[])), + ); + check(allowDuplicateUrls, Match.Optional(Boolean)); + + const hunt = await Hunts.findOneAsync(huntId); + if (!hunt) { + throw new Meteor.Error(404, "Unknown hunt id"); + } + + if ( + !userMayWritePuzzlesForHunt(await MeteorUsers.findOneAsync(userId), hunt) + ) { + throw new Meteor.Error( + 401, + `User ${userId} may not create new puzzles for hunt ${huntId}`, + ); + } + + // Look up each tag by name and map them to tag IDs. + const tagIds = await Promise.all( + tags.map(async (tagName) => { + return getOrCreateTagByName(userId, huntId, tagName); + }), + ); + + Logger.info("Creating a new puzzle", { + hunt: huntId, + title, + }); + + const fullPuzzle = { + createdBy: userId, + hunt: huntId, + title, + expectedAnswerCount, + _id: Random.id(), + tags: [...new Set(tagIds)], + answers: [], + url, + }; + + // By creating the document before we save the puzzle, we make sure nobody + // else has a chance to create a document with the wrong config. (This + // requires us to have an _id for the puzzle, which is why we generate it + // manually above instead of letting Meteor do it) + if (GoogleClient.ready() && !(await Flags.activeAsync("disable.google"))) { + await ensureDocument(userId, fullPuzzle, docType); + } + + // In a transaction, look for a puzzle with the same URL. If present, we + // reject the insertion unless the client overrides it. + const client = MongoInternals.defaultRemoteCollectionDriver().mongo.client; + const session = client.startSession(); + try { + await session.withTransaction(async () => { + if (url) { + const existingPuzzleWithUrl = await Puzzles.collection + .rawCollection() + .findOne({ url }, { session }); + if (existingPuzzleWithUrl && !allowDuplicateUrls) { + throw new Meteor.Error(409, `Puzzle with URL ${url} already exists`); + } + } + await Puzzles.insertAsync(fullPuzzle, { session }); + }); + } catch (error) { + // In the case of any error, try to delete the document we created before the transaction. + // If that fails too, let the original error propagate. + try { + await deleteUnusedDocument(fullPuzzle); + } catch (deleteError) { + Logger.warn("Unable to clean up document on failed puzzle creation", { + error: deleteError, + }); + } + throw error; + } finally { + await session.endSession(); + } + + // Run any puzzle-creation hooks, like creating a default document + // attachment or announcing the puzzle to Slack. + Meteor.defer( + Meteor.bindEnvironment(() => { + void GlobalHooks.runPuzzleCreatedHooks(fullPuzzle._id); + }), + ); + + return fullPuzzle._id; +} diff --git a/imports/server/api.ts b/imports/server/api.ts index 65145463b..22aaa24e1 100644 --- a/imports/server/api.ts +++ b/imports/server/api.ts @@ -1,9 +1,15 @@ import express from "express"; import authenticator from "./api/authenticator"; +import createPuzzle from "./api/resources/createPuzzle"; +import hunts from "./api/resources/hunts"; +import tags from "./api/resources/tags"; import users from "./api/resources/users"; const api = express(); api.use(authenticator); api.use("/users", users); +api.use("/hunts", hunts); +api.use("/tags", tags); +api.use("/createPuzzle", createPuzzle); export default api; diff --git a/imports/server/api/authenticator.ts b/imports/server/api/authenticator.ts index 337d0c01a..69f79b214 100644 --- a/imports/server/api/authenticator.ts +++ b/imports/server/api/authenticator.ts @@ -24,6 +24,8 @@ const authenticator: express.Handler = expressAsyncWrapper( return; } + res.locals.userId = key.user; + next(); }, ); diff --git a/imports/server/api/resources/createPuzzle.ts b/imports/server/api/resources/createPuzzle.ts new file mode 100644 index 000000000..7da133a88 --- /dev/null +++ b/imports/server/api/resources/createPuzzle.ts @@ -0,0 +1,48 @@ +import { check, Match } from "meteor/check"; +import { Meteor } from "meteor/meteor"; +import bodyParser from "body-parser"; +import express from "express"; +import type { GdriveMimeTypesType } from "../../../lib/GdriveMimeTypes"; +import GdriveMimeTypes from "../../../lib/GdriveMimeTypes"; +import addPuzzle from "../../addPuzzle"; +import expressAsyncWrapper from "../../expressAsyncWrapper"; + +const createPuzzle = express.Router(); + +createPuzzle.use(bodyParser.json()); + +createPuzzle.post( + "/:huntId", + expressAsyncWrapper(async (req, res) => { + check(req.params.huntId, String); + check(res.locals.userId, String); + check(req.body, { + title: String, + url: Match.Optional(String), + tags: [String], + expectedAnswerCount: Number, + docType: Match.OneOf( + ...(Object.keys(GdriveMimeTypes) as GdriveMimeTypesType[]), + ), + allowDuplicateUrls: Match.Optional(Boolean), + }); + + try { + const id = await addPuzzle({ + userId: res.locals.userId, + huntId: req.params.huntId, + ...req.body, + }); + res.json({ + id, + }); + } catch (error) { + if (error instanceof Meteor.Error && typeof error.error === "number") { + res.sendStatus(error.error); + } + throw error; + } + }), +); + +export default createPuzzle; diff --git a/imports/server/api/resources/hunts.ts b/imports/server/api/resources/hunts.ts new file mode 100644 index 000000000..1c6494de6 --- /dev/null +++ b/imports/server/api/resources/hunts.ts @@ -0,0 +1,42 @@ +import { check } from "meteor/check"; +import express from "express"; +import type { HuntType } from "../../../lib/models/Hunts"; +import Hunts from "../../../lib/models/Hunts"; +import MeteorUsers from "../../../lib/models/MeteorUsers"; +import expressAsyncWrapper from "../../expressAsyncWrapper"; + +const hunts = express.Router(); + +const renderHunt = function renderHunt(hunt: HuntType) { + return { + _id: hunt._id, + name: hunt.name, + }; +}; + +hunts.get( + "/", + expressAsyncWrapper(async (_, res) => { + check(res.locals.userId, String); + + const user = await MeteorUsers.findOneAsync({ _id: res.locals.userId }); + if (!user) { + // Should never happen if the API key passed authentication. + res.sendStatus(500); + return; + } + + const allHunts = Hunts.find({}, { sort: { createdAt: -1 } }); + const userHunts = []; + for await (const hunt of allHunts) { + if (user.hunts?.includes(hunt._id)) { + userHunts.push(renderHunt(hunt)); + } + } + res.json({ + hunts: userHunts, + }); + }), +); + +export default hunts; diff --git a/imports/server/api/resources/tags.ts b/imports/server/api/resources/tags.ts new file mode 100644 index 000000000..c9b0aab44 --- /dev/null +++ b/imports/server/api/resources/tags.ts @@ -0,0 +1,34 @@ +import { check } from "meteor/check"; +import express from "express"; +import MeteorUsers from "../../../lib/models/MeteorUsers"; +import Tags from "../../../lib/models/Tags"; +import expressAsyncWrapper from "../../expressAsyncWrapper"; + +const tags = express.Router(); + +tags.get( + "/:huntId", + expressAsyncWrapper(async (req, res) => { + check(req.params.huntId, String); + check(res.locals.userId, String); + + const user = await MeteorUsers.findOneAsync({ _id: res.locals.userId }); + if (!user) { + // Should never happen if the API key passed authentication. + res.sendStatus(500); + return; + } + + if (!user.hunts?.includes(req.params.huntId)) { + res.sendStatus(403); + return; + } + + const huntTags = Tags.find({ hunt: req.params.huntId }); + res.json({ + tags: await huntTags.mapAsync((tag) => tag.name), + }); + }), +); + +export default tags; diff --git a/imports/server/gdrive.ts b/imports/server/gdrive.ts index 70fdfee4e..54bb8c24c 100644 --- a/imports/server/gdrive.ts +++ b/imports/server/gdrive.ts @@ -263,6 +263,7 @@ export async function ensureHuntFolderPermission( } export async function ensureDocument( + userId: string, puzzle: { _id: string; title: string; @@ -290,6 +291,7 @@ export async function ensureDocument( folderId, ); const newDoc = { + createdBy: userId, hunt: puzzle.hunt, puzzle: puzzle._id, provider: "google" as const, diff --git a/imports/server/getOrCreateTagByName.ts b/imports/server/getOrCreateTagByName.ts index 1b040501f..835557be3 100644 --- a/imports/server/getOrCreateTagByName.ts +++ b/imports/server/getOrCreateTagByName.ts @@ -2,6 +2,7 @@ import Logger from "../Logger"; import Tags from "../lib/models/Tags"; export default async function getOrCreateTagByName( + userId: string, huntId: string, name: string, ): Promise { @@ -11,13 +12,17 @@ export default async function getOrCreateTagByName( } Logger.info("Creating a new tag", { hunt: huntId, name }); - const newTagId = await Tags.insertAsync({ hunt: huntId, name }); + const newTagId = await Tags.insertAsync({ + hunt: huntId, + name, + createdBy: userId, + }); // When creating a `group:*` tag, also ensure a matching `meta-for:` tag exists. if (name.startsWith("group:")) { const groupName = name.slice("group:".length); const metaTagName = `meta-for:${groupName}`; - await getOrCreateTagByName(huntId, metaTagName); + await getOrCreateTagByName(userId, huntId, metaTagName); } return newTagId; diff --git a/imports/server/methods/addPuzzleTag.ts b/imports/server/methods/addPuzzleTag.ts index 35984016d..f0305776a 100644 --- a/imports/server/methods/addPuzzleTag.ts +++ b/imports/server/methods/addPuzzleTag.ts @@ -35,7 +35,7 @@ defineMethod(addPuzzleTag, { } const huntId = puzzle.hunt; - const tagId = await getOrCreateTagByName(huntId, tagName); + const tagId = await getOrCreateTagByName(this.userId, huntId, tagName); Logger.info("Tagging puzzle", { puzzle: puzzleId, tag: tagName }); await Puzzles.updateAsync( diff --git a/imports/server/methods/configureOrganizeGoogleDrive.ts b/imports/server/methods/configureOrganizeGoogleDrive.ts index 5c32726a8..b880a2768 100644 --- a/imports/server/methods/configureOrganizeGoogleDrive.ts +++ b/imports/server/methods/configureOrganizeGoogleDrive.ts @@ -42,7 +42,7 @@ defineMethod(configureOrganizeGoogleDrive, { const puzzles = indexedById(await Puzzles.find().fetchAsync()); for (const d of Documents.find()) { const puzzle = puzzles.get(d.puzzle); - if (puzzle && !d.value.folder) await ensureDocument(puzzle); + if (puzzle && !d.value.folder) await ensureDocument(this.userId, puzzle); } }, }); diff --git a/imports/server/methods/createHunt.ts b/imports/server/methods/createHunt.ts index 950137dd7..e9da3027f 100644 --- a/imports/server/methods/createHunt.ts +++ b/imports/server/methods/createHunt.ts @@ -39,7 +39,7 @@ defineMethod(createHunt, { await addUserToRole(this.userId, huntId, "operator"); for (const tag of DEFAULT_TAGS) { - await getOrCreateTagByName(huntId, tag); + await getOrCreateTagByName(this.userId, huntId, tag); } Meteor.defer(async () => { diff --git a/imports/server/methods/createPuzzle.ts b/imports/server/methods/createPuzzle.ts index 13d542a6c..84dbcd22a 100644 --- a/imports/server/methods/createPuzzle.ts +++ b/imports/server/methods/createPuzzle.ts @@ -1,20 +1,8 @@ import { check, Match } from "meteor/check"; -import { Meteor } from "meteor/meteor"; -import { MongoInternals } from "meteor/mongo"; -import { Random } from "meteor/random"; -import Flags from "../../Flags"; -import Logger from "../../Logger"; import type { GdriveMimeTypesType } from "../../lib/GdriveMimeTypes"; import GdriveMimeTypes from "../../lib/GdriveMimeTypes"; -import Hunts from "../../lib/models/Hunts"; -import MeteorUsers from "../../lib/models/MeteorUsers"; -import Puzzles from "../../lib/models/Puzzles"; -import { userMayWritePuzzlesForHunt } from "../../lib/permission_stubs"; import createPuzzle from "../../methods/createPuzzle"; -import GlobalHooks from "../GlobalHooks"; -import { deleteUnusedDocument, ensureDocument } from "../gdrive"; -import getOrCreateTagByName from "../getOrCreateTagByName"; -import GoogleClient from "../googleClientRefresher"; +import addPuzzle from "../addPuzzle"; import defineMethod from "./defineMethod"; defineMethod(createPuzzle, { @@ -44,95 +32,15 @@ defineMethod(createPuzzle, { }) { check(this.userId, String); - const hunt = await Hunts.findOneAsync(huntId); - if (!hunt) { - throw new Meteor.Error(404, "Unknown hunt id"); - } - - if ( - !userMayWritePuzzlesForHunt( - await MeteorUsers.findOneAsync(this.userId), - hunt, - ) - ) { - throw new Meteor.Error( - 401, - `User ${this.userId} may not create new puzzles for hunt ${huntId}`, - ); - } - - // Look up each tag by name and map them to tag IDs. - const tagIds = await Promise.all( - tags.map(async (tagName) => { - return getOrCreateTagByName(huntId, tagName); - }), - ); - - Logger.info("Creating a new puzzle", { - hunt: huntId, - title, - }); - - const fullPuzzle = { - hunt: huntId, + return addPuzzle({ + userId: this.userId, + huntId, title, + tags, expectedAnswerCount, - _id: Random.id(), - tags: [...new Set(tagIds)], - answers: [], + docType, url, - }; - - // By creating the document before we save the puzzle, we make sure nobody - // else has a chance to create a document with the wrong config. (This - // requires us to have an _id for the puzzle, which is why we generate it - // manually above instead of letting Meteor do it) - if (GoogleClient.ready() && !(await Flags.activeAsync("disable.google"))) { - await ensureDocument(fullPuzzle, docType); - } - - // In a transaction, look for a puzzle with the same URL. If present, we - // reject the insertion unless the client overrides it. - const client = MongoInternals.defaultRemoteCollectionDriver().mongo.client; - const session = client.startSession(); - try { - await session.withTransaction(async () => { - if (url) { - const existingPuzzleWithUrl = await Puzzles.collection - .rawCollection() - .findOne({ url }, { session }); - if (existingPuzzleWithUrl && !allowDuplicateUrls) { - throw new Meteor.Error( - 409, - `Puzzle with URL ${url} already exists`, - ); - } - } - await Puzzles.insertAsync(fullPuzzle, { session }); - }); - } catch (error) { - // In the case of any error, try to delete the document we created before the transaction. - // If that fails too, let the original error propagate. - try { - await deleteUnusedDocument(fullPuzzle); - } catch (deleteError) { - Logger.warn("Unable to clean up document on failed puzzle creation", { - error: deleteError, - }); - } - throw error; - } finally { - await session.endSession(); - } - - // Run any puzzle-creation hooks, like creating a default document - // attachment or announcing the puzzle to Slack. - Meteor.defer( - Meteor.bindEnvironment(() => { - void GlobalHooks.runPuzzleCreatedHooks(fullPuzzle._id); - }), - ); - - return fullPuzzle._id; + allowDuplicateUrls, + }); }, }); diff --git a/imports/server/methods/ensurePuzzleDocument.ts b/imports/server/methods/ensurePuzzleDocument.ts index 2f1ed5d73..007ebce13 100644 --- a/imports/server/methods/ensurePuzzleDocument.ts +++ b/imports/server/methods/ensurePuzzleDocument.ts @@ -26,7 +26,7 @@ defineMethod(ensurePuzzleDocument, { this.unblock(); - await ensureDocument(puzzle); + await ensureDocument(this.userId, puzzle); if (await Flags.activeAsync("disable.google")) { return; diff --git a/imports/server/methods/updatePuzzle.ts b/imports/server/methods/updatePuzzle.ts index 8021f4f83..14877fe65 100644 --- a/imports/server/methods/updatePuzzle.ts +++ b/imports/server/methods/updatePuzzle.ts @@ -53,7 +53,7 @@ defineMethod(updatePuzzle, { // Look up each tag by name and map them to tag IDs. const tagIds = await Promise.all( tags.map(async (tagName) => { - return getOrCreateTagByName(oldPuzzle.hunt, tagName); + return getOrCreateTagByName(this.userId!, oldPuzzle.hunt, tagName); }), ); @@ -88,7 +88,7 @@ defineMethod(updatePuzzle, { if (oldPuzzle.title !== title) { Meteor.defer( Meteor.bindEnvironment(async () => { - const doc = await ensureDocument({ + const doc = await ensureDocument(this.userId!, { _id: puzzleId, title, hunt: oldPuzzle.hunt, diff --git a/package-lock.json b/package-lock.json index be37886b6..20b77797b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11364,9 +11364,9 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==" }, "qs": { "version": "6.13.0", @@ -14160,6 +14160,25 @@ "vary": "~1.1.2" }, "dependencies": { + "body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, "call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", diff --git a/package.json b/package.json index 96792c16e..944f3f88b 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@popperjs/core": "^2.11.8", "aws4": "^1.13.2", "bcrypt": "^5.1.1", + "body-parser": "^1.20.3", "bootstrap": "^5.3.3", "classnames": "^2.5.1", "discord.js": "^12.5.3", From 0853a937d0f05abfd00ebdbbbfd763ae27df9fff Mon Sep 17 00:00:00 2001 From: Jeff Davidson Date: Wed, 1 Jan 2025 22:36:18 -0800 Subject: [PATCH 3/4] Expose API key on profile page. Allows users to (re)generate and copy-paste a key for external use. --- imports/client/components/OwnProfilePage.tsx | 80 ++++++++++++++++++- imports/client/components/ProfilePage.tsx | 14 +++- imports/{server => lib}/models/APIKeys.ts | 8 +- imports/lib/publications/apiKeysForSelf.ts | 3 + imports/server/api/authenticator.ts | 2 +- imports/server/ensureAPIKey.ts | 2 +- imports/server/methods/rollAPIKey.ts | 2 +- .../migrations/51-backfill-updated-at.ts | 2 +- imports/server/publications/apiKeysForSelf.ts | 13 +++ imports/server/publications/index.ts | 1 + 10 files changed, 116 insertions(+), 11 deletions(-) rename imports/{server => lib}/models/APIKeys.ts (57%) create mode 100644 imports/lib/publications/apiKeysForSelf.ts create mode 100644 imports/server/publications/apiKeysForSelf.ts diff --git a/imports/client/components/OwnProfilePage.tsx b/imports/client/components/OwnProfilePage.tsx index 1c1790cb4..790973d6b 100644 --- a/imports/client/components/OwnProfilePage.tsx +++ b/imports/client/components/OwnProfilePage.tsx @@ -12,10 +12,13 @@ import FormControl from "react-bootstrap/FormControl"; import FormGroup from "react-bootstrap/FormGroup"; import FormLabel from "react-bootstrap/FormLabel"; import FormText from "react-bootstrap/FormText"; +import InputGroup from "react-bootstrap/InputGroup"; +import CopyToClipboard from "react-copy-to-clipboard"; import Flags from "../../Flags"; import { formatDiscordName } from "../../lib/discord"; import linkUserDiscordAccount from "../../methods/linkUserDiscordAccount"; import linkUserGoogleAccount from "../../methods/linkUserGoogleAccount"; +import rollAPIKey from "../../methods/rollAPIKey"; import unlinkUserDiscordAccount from "../../methods/unlinkUserDiscordAccount"; import unlinkUserGoogleAccount from "../../methods/unlinkUserGoogleAccount"; import updateProfile from "../../methods/updateProfile"; @@ -292,7 +295,13 @@ enum OwnProfilePageSubmitState { ERROR = "error", } -const OwnProfilePage = ({ initialUser }: { initialUser: Meteor.User }) => { +const OwnProfilePage = ({ + initialUser, + initialAPIKey, +}: { + initialUser: Meteor.User; + initialAPIKey?: string; +}) => { const [displayName, setDisplayName] = useState( initialUser.displayName ?? "", ); @@ -306,6 +315,9 @@ const OwnProfilePage = ({ initialUser }: { initialUser: Meteor.User }) => { OwnProfilePageSubmitState.IDLE, ); const [submitError, setSubmitError] = useState(""); + const [showAPIKey, setShowAPIKey] = useState(false); + const [regeneratingAPIKey, setRegeneratingAPIKey] = useState(false); + const [APIKeyError, setAPIKeyError] = useState(); const handleDisplayNameFieldChange: NonNullable< FormControlProps["onChange"] @@ -358,6 +370,27 @@ const OwnProfilePage = ({ initialUser }: { initialUser: Meteor.User }) => { setSubmitState(OwnProfilePageSubmitState.IDLE); }, []); + const toggleShowAPIKey = useCallback(() => { + setShowAPIKey(!showAPIKey); + }, [showAPIKey]); + + const regenerateAPIKey = useCallback(() => { + setRegeneratingAPIKey(true); + setAPIKeyError(""); + rollAPIKey.call({}, (error) => { + if (error) { + setAPIKeyError(error.message); + } else { + setAPIKeyError(""); + } + setRegeneratingAPIKey(false); + }); + }, []); + + const dismissAPIKeyAlert = useCallback(() => { + setAPIKeyError(""); + }, []); + const shouldDisableForm = submitState === "submitting"; return ( @@ -452,6 +485,51 @@ const OwnProfilePage = ({ initialUser }: { initialUser: Meteor.User }) => { + +
+

Advanced

+ + API key + + + + + + + + + + Authorization credential used to make API calls. Keep this secret! + + {APIKeyError ? ( + + Key generation failed: {APIKeyError} + + ) : null} + +
); }; diff --git a/imports/client/components/ProfilePage.tsx b/imports/client/components/ProfilePage.tsx index d207989bf..895966198 100644 --- a/imports/client/components/ProfilePage.tsx +++ b/imports/client/components/ProfilePage.tsx @@ -2,8 +2,11 @@ import { Meteor } from "meteor/meteor"; import { useSubscribe, useTracker } from "meteor/react-meteor-data"; import React from "react"; import { Navigate, useParams } from "react-router-dom"; +import APIKeys from "../../lib/models/APIKeys"; import MeteorUsers from "../../lib/models/MeteorUsers"; +import apiKeysForSelf from "../../lib/publications/apiKeysForSelf"; import { useBreadcrumb } from "../hooks/breadcrumb"; +import useTypedSubscribe from "../hooks/useTypedSubscribe"; import OthersProfilePage from "./OthersProfilePage"; import OwnProfilePage from "./OwnProfilePage"; @@ -17,7 +20,14 @@ const ResolvedProfilePage = ({ const huntId = useParams<"huntId">().huntId; const profileLoading = useSubscribe("profile", userId); - const loading = profileLoading(); + + const apiKeyLoading = useTypedSubscribe(apiKeysForSelf); + + const apiKey = useTracker(() => { + return isSelf ? APIKeys.findOne()?.key : undefined; + }, [isSelf]); + + const loading = profileLoading() || apiKeyLoading(); const user = useTracker(() => { return loading ? undefined : MeteorUsers.findOne(userId); @@ -33,7 +43,7 @@ const ResolvedProfilePage = ({ } else if (!user) { return
{`No user ${userId} found.`}
; } else if (isSelf) { - return ; + return ; } return ; diff --git a/imports/server/models/APIKeys.ts b/imports/lib/models/APIKeys.ts similarity index 57% rename from imports/server/models/APIKeys.ts rename to imports/lib/models/APIKeys.ts index a200f9239..7d74cdc4b 100644 --- a/imports/server/models/APIKeys.ts +++ b/imports/lib/models/APIKeys.ts @@ -1,8 +1,8 @@ import { z } from "zod"; -import type { ModelType } from "../../lib/models/Model"; -import SoftDeletedModel from "../../lib/models/SoftDeletedModel"; -import { foreignKey } from "../../lib/models/customTypes"; -import withCommon from "../../lib/models/withCommon"; +import type { ModelType } from "./Model"; +import SoftDeletedModel from "./SoftDeletedModel"; +import { foreignKey } from "./customTypes"; +import withCommon from "./withCommon"; const APIKey = withCommon( z.object({ diff --git a/imports/lib/publications/apiKeysForSelf.ts b/imports/lib/publications/apiKeysForSelf.ts new file mode 100644 index 000000000..a62aede95 --- /dev/null +++ b/imports/lib/publications/apiKeysForSelf.ts @@ -0,0 +1,3 @@ +import TypedPublication from "./TypedPublication"; + +export default new TypedPublication("APIKeys.publications.forSelf"); diff --git a/imports/server/api/authenticator.ts b/imports/server/api/authenticator.ts index 69f79b214..bf0f15f98 100644 --- a/imports/server/api/authenticator.ts +++ b/imports/server/api/authenticator.ts @@ -1,6 +1,6 @@ import type express from "express"; +import APIKeys from "../../lib/models/APIKeys"; import expressAsyncWrapper from "../expressAsyncWrapper"; -import APIKeys from "../models/APIKeys"; const authenticator: express.Handler = expressAsyncWrapper( async (req, res, next) => { diff --git a/imports/server/ensureAPIKey.ts b/imports/server/ensureAPIKey.ts index afd399231..674441231 100644 --- a/imports/server/ensureAPIKey.ts +++ b/imports/server/ensureAPIKey.ts @@ -1,6 +1,6 @@ import { Random } from "meteor/random"; import Logger from "../Logger"; -import APIKeys from "./models/APIKeys"; +import APIKeys from "../lib/models/APIKeys"; import userForKeyOperation from "./userForKeyOperation"; import withLock from "./withLock"; diff --git a/imports/server/methods/rollAPIKey.ts b/imports/server/methods/rollAPIKey.ts index 9493b4123..9e9e259e0 100644 --- a/imports/server/methods/rollAPIKey.ts +++ b/imports/server/methods/rollAPIKey.ts @@ -1,8 +1,8 @@ import { check, Match } from "meteor/check"; import Logger from "../../Logger"; +import APIKeys from "../../lib/models/APIKeys"; import rollAPIKey from "../../methods/rollAPIKey"; import ensureAPIKey from "../ensureAPIKey"; -import APIKeys from "../models/APIKeys"; import userForKeyOperation from "../userForKeyOperation"; import defineMethod from "./defineMethod"; diff --git a/imports/server/migrations/51-backfill-updated-at.ts b/imports/server/migrations/51-backfill-updated-at.ts index de9281a4b..a4ca3f3d1 100644 --- a/imports/server/migrations/51-backfill-updated-at.ts +++ b/imports/server/migrations/51-backfill-updated-at.ts @@ -1,4 +1,5 @@ import type { z } from "zod"; +import APIKeys from "../../lib/models/APIKeys"; import Announcements from "../../lib/models/Announcements"; import ChatMessages from "../../lib/models/ChatMessages"; import ChatNotifications from "../../lib/models/ChatNotifications"; @@ -31,7 +32,6 @@ import Routers from "../../lib/models/mediasoup/Routers"; import TransportRequests from "../../lib/models/mediasoup/TransportRequests"; import TransportStates from "../../lib/models/mediasoup/TransportStates"; import Transports from "../../lib/models/mediasoup/Transports"; -import APIKeys from "../models/APIKeys"; import LatestDeploymentTimestamps from "../models/LatestDeploymentTimestamps"; import Subscribers from "../models/Subscribers"; import UploadTokens from "../models/UploadTokens"; diff --git a/imports/server/publications/apiKeysForSelf.ts b/imports/server/publications/apiKeysForSelf.ts new file mode 100644 index 000000000..b6da3b69f --- /dev/null +++ b/imports/server/publications/apiKeysForSelf.ts @@ -0,0 +1,13 @@ +import APIKeys from "../../lib/models/APIKeys"; +import apiKeysForSelf from "../../lib/publications/apiKeysForSelf"; +import definePublication from "./definePublication"; + +definePublication(apiKeysForSelf, { + run() { + if (!this.userId) { + return []; + } + + return APIKeys.find({ user: this.userId }); + }, +}); diff --git a/imports/server/publications/index.ts b/imports/server/publications/index.ts index 64e3aaa65..7a9aacd96 100644 --- a/imports/server/publications/index.ts +++ b/imports/server/publications/index.ts @@ -1,4 +1,5 @@ import "./announcementsForAnnouncementsPage"; +import "./apiKeysForSelf"; import "./blobMappingsAll"; import "./bookmarkNotificationsForSelf"; import "./chatMessagesForFirehose"; From c811856b7f0b826ba6c6666b6b8c84c5f9a1496b Mon Sep 17 00:00:00 2001 From: Jeff Davidson Date: Wed, 27 Nov 2024 19:56:55 -0800 Subject: [PATCH 4/4] Initial implementation of a browser extension to add puzzles. Supports (at least) Chrome and Firefox. Includes a settings page to input the Jolly Roger instance and API key, and a popup menu which reads the URL and title from the current page and provides a form to add it to a particular hunt on that instance. The form is modeled off of PuzzleModalForm, but condensed vertically to try to avoid scrolling in the height-limited popup window. We also surface recently-used tags to make it easier to add multiple puzzles in the same round/group. Built as a mostly-independent module in the extension/ folder using npm; could be split into a separate repository if desired. There is a small bit of duplication with Jolly Roger proper, but this seems reasonable to avoid unduly tight coupling and allow using more modern dependencies than Meteor currently permits. Uses React Bootstrap and consistent UI components with Jolly Roger, but is powered by the REST API rather than Meteor, which seems like it would introduce significant complexity for limited value. Also covered by the root project's eslint and prettier configs; Typescript checking is covered by the Webpack compilation. Some checks are disabled because they require "npm install" to be run from the extension directory. In general, we probably need to align on an organizational structure before devoting too much time to making the linters work. --- .eslintignore | 1 + .gitignore | 2 + .prettierignore | 2 +- extension/.eslintrc.json | 26 + extension/README.md | 31 + extension/package-lock.json | 3699 ++++++++++++++++++++++++++ extension/package.json | 35 + extension/public/icon.png | Bin 0 -> 1324 bytes extension/public/icon128.png | Bin 0 -> 10119 bytes extension/public/icon48.png | Bin 0 -> 2692 bytes extension/public/manifest.json | 17 + extension/public/options.html | 12 + extension/public/popup.html | 12 + extension/src/api.ts | 81 + extension/src/options.tsx | 127 + extension/src/popup.tsx | 529 ++++ extension/src/storage.ts | 71 + extension/tsconfig.json | 14 + extension/webpack/webpack.common.mjs | 72 + extension/webpack/webpack.dev.mjs | 9 + extension/webpack/webpack.prod.mjs | 8 + tsconfig.json | 3 +- 22 files changed, 4749 insertions(+), 2 deletions(-) create mode 100644 extension/.eslintrc.json create mode 100644 extension/README.md create mode 100644 extension/package-lock.json create mode 100644 extension/package.json create mode 100644 extension/public/icon.png create mode 100644 extension/public/icon128.png create mode 100644 extension/public/icon48.png create mode 100644 extension/public/manifest.json create mode 100644 extension/public/options.html create mode 100644 extension/public/popup.html create mode 100644 extension/src/api.ts create mode 100644 extension/src/options.tsx create mode 100644 extension/src/popup.tsx create mode 100644 extension/src/storage.ts create mode 100644 extension/tsconfig.json create mode 100644 extension/webpack/webpack.common.mjs create mode 100644 extension/webpack/webpack.dev.mjs create mode 100644 extension/webpack/webpack.prod.mjs diff --git a/.eslintignore b/.eslintignore index c4c545eb2..b46309c26 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ eslint/dist +extension/dist node_modules tests/node_modules diff --git a/.gitignore b/.gitignore index a922ef715..5b76ce106 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ node_modules npm-debug.log jsconfig.json .vscode/launch.json +extension/dist +extension/tmp \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index 86ff2c110..36f20bb93 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,2 @@ .meteor -eslint/dist +eslint/dist \ No newline at end of file diff --git a/extension/.eslintrc.json b/extension/.eslintrc.json new file mode 100644 index 000000000..e0ad2a47d --- /dev/null +++ b/extension/.eslintrc.json @@ -0,0 +1,26 @@ +{ + "overrides": [ + { + "files": ["**"], + "parserOptions": { + "tsconfigRootDir": "extension", + "project": "./tsconfig.json" + }, + "rules": { + "import/no-extraneous-dependencies": [ + "error", + { + "devDependencies": [ + // All webpack config dependencies are dev dependencies, even when imported. + "**/webpack.*.mjs" + ] + } + ], + // Disable rules which require "npm install" to be run. + // TODO: Enable these if possible once we align on project organization. + "@typescript-eslint/restrict-plus-operands": ["off", {}], + "import/no-unresolved": ["off"] + } + } + ] +} diff --git a/extension/README.md b/extension/README.md new file mode 100644 index 000000000..d0d093774 --- /dev/null +++ b/extension/README.md @@ -0,0 +1,31 @@ +# Jolly Roger Browser Extension + +## Project Structure + +- src: TypeScript source files +- public: static files +- dist/chrome: output Chrome Extension directory +- dist/firefox: output Firefox Extension directory +- dist/{browser}/js: Generated JavaScript files + +## Setup + +``` +npm install +``` + +## Build for production + +``` +npm run build +``` + +## Build for development (in watch mode)x + +``` +npm run watch +``` + +## Load extension to browser + +Load manifest.json from the appropriate `dist` subdirectory (`chrome` or `firefox`) diff --git a/extension/package-lock.json b/extension/package-lock.json new file mode 100644 index 000000000..b202d0bde --- /dev/null +++ b/extension/package-lock.json @@ -0,0 +1,3699 @@ +{ + "name": "jolly-roger-browser-extension", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "jolly-roger-browser-extension", + "version": "1.0.0", + "dependencies": { + "bootstrap": "^5.3.3", + "react": "^18.3.1", + "react-bootstrap": "^2.10.6", + "react-dom": "^18.3.1", + "react-select": "^5.9.0", + "styled-components": "^6.1.13" + }, + "devDependencies": { + "@types/chrome": "0.0.287", + "@types/node": "^22.10.2", + "@types/react": "^18.2.7", + "@types/react-dom": "^18.3.1", + "copy-webpack-plugin": "^12.0.2", + "css-loader": "^7.1.2", + "glob": "^11.0.0", + "prettier": "^3.4.1", + "rimraf": "^6.0.1", + "style-loader": "^4.0.0", + "ts-loader": "^9.5.1", + "typescript": "^5.1.0", + "webpack": "^5.96.1", + "webpack-cli": "^5.1.4", + "webpack-merge": "^6.0.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/is-prop-valid/node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "license": "MIT" + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@floating-ui/core": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", + "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.8" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.12", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz", + "integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.8" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", + "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui": { + "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", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz", + "integrity": "sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.1.tgz", + "integrity": "sha512-qghR21ynHiUrpcIkKCoKYB+3rJtezY5Y7ikrwradCL+7hZHdQ2Ozc5ffxtpmpahoAGgc31gyXaSx2sXXaThmqA==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@popperjs/core": "^2.11.8", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.5.0", + "@types/warning": "^3.0.3", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.4", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "node_modules/@restart/ui/node_modules/@restart/hooks": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.0.tgz", + "integrity": "sha512-wS+h6IusJCPjTkmOOrRZxIPICD/mtFA3PRZviutoM23/b7akyDGfZF/WS+nIFk27u7JDhPE2+0GBdZxjSqHZkg==", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui/node_modules/uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "peerDependencies": { + "react": ">=16.14.0" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/chrome": { + "version": "0.0.287", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.287.tgz", + "integrity": "sha512-wWhBNPNXZHwycHKNYnexUcpSbrihVZu++0rdp6GEk5ZgAglenLx+RwdEouh6FrHS0XQiOxSd62yaujM1OoQlZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/filesystem": "*", + "@types/har-format": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/filesystem": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.32.tgz", + "integrity": "sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==", + "dev": true, + "dependencies": { + "@types/filewriter": "*" + } + }, + "node_modules/@types/filewriter": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.29.tgz", + "integrity": "sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ==", + "dev": true + }, + "node_modules/@types/har-format": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.10.tgz", + "integrity": "sha512-o0J30wqycjF5miWDKYKKzzOU1ZTLuA42HZ4HE7/zqTOc/jTLdQ5NhYWvsRQo45Nfi1KHoRdNhteSI4BAxTF1Pg==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "node_modules/@types/react": { + "version": "18.3.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", + "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", + "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", + "license": "MIT" + }, + "node_modules/@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-regex": { + "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" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/balanced-match": { + "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/bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/brace-expansion": { + "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" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001684", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz", + "integrity": "sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/copy-webpack-plugin": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.1", + "globby": "^14.0.0", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/copy-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/copy-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-webpack-plugin/node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/eastasianwidth": { + "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/electron-to-chromium": { + "version": "1.5.66", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.66.tgz", + "integrity": "sha512-pI2QF6+i+zjPbqRzJwkMvtvkdI7MjVbSh2g8dlMguDJIXEPw+kwasS1Jl+YGPEBfGVxsVgGUratAKymPdPo2vQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "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/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", + "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", + "dev": true + }, + "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-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/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/foreground-child": { + "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", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "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", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "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" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jackspeak": { + "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" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", + "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "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" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "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/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "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": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "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", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.1.0.tgz", + "integrity": "sha512-rm0bdSv4jC3BDma3s9H19ZddW0aHX6EoqwDYU2IfZhRN+53QrufTRo2IdkAbRqLx4R2IYbZnbjKKxg4VN5oU9Q==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", + "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "dependencies": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "peerDependencies": { + "react": ">=0.14.0" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-bootstrap": { + "version": "2.10.6", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.6.tgz", + "integrity": "sha512-fNvKytSp0nHts1WRnRBJeBEt+I9/ZdrnhIjWOucEduRNvFRU1IXjZueDdWnBiqsTSJ7MckQJi9i/hxGolaRq+g==", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.9.0", + "@types/react-transition-group": "^4.4.6", + "classnames": "^2.3.2", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.8.1", + "prop-types-extra": "^1.1.0", + "react-transition-group": "^4.4.5", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "@types/react": ">=16.14.8", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-select": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.9.0.tgz", + "integrity": "sha512-nwRKGanVHGjdccsnzhFte/PULziueZxGD8LL2WojON78Mvnq7LdAMEtu2frrwld1fr3geixg3iiMBIc/LLAZpw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@floating-ui/dom": "^1.0.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^6.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0", + "use-isomorphic-layout-effect": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "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": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "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, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "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" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "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", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "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", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "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" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "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" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "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" + } + }, + "node_modules/style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "dev": true, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.27.0" + } + }, + "node_modules/styled-components": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.13.tgz", + "integrity": "sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.38", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", + "license": "MIT" + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "license": "0BSD" + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.31.6", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", + "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-loader": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/typescript": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.0.tgz", + "integrity": "sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.96.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz", + "integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-cli/node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/wrap-ansi": { + "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", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "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", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "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" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "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", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "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" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + } + } +} diff --git a/extension/package.json b/extension/package.json new file mode 100644 index 000000000..4ad917af5 --- /dev/null +++ b/extension/package.json @@ -0,0 +1,35 @@ +{ + "name": "jolly-roger-browser-extension", + "version": "1.0.0", + "description": "Browser extension for use with Jolly Roger", + "scripts": { + "watch": "webpack --config webpack/webpack.dev.mjs --watch", + "build": "webpack --config webpack/webpack.prod.mjs", + "clean": "rimraf dist" + }, + "dependencies": { + "bootstrap": "^5.3.3", + "react": "^18.3.1", + "react-bootstrap": "^2.10.6", + "react-dom": "^18.3.1", + "react-select": "^5.9.0", + "styled-components": "^6.1.13" + }, + "devDependencies": { + "@types/chrome": "0.0.287", + "@types/node": "^22.10.2", + "@types/react": "^18.2.7", + "@types/react-dom": "^18.3.1", + "copy-webpack-plugin": "^12.0.2", + "css-loader": "^7.1.2", + "glob": "^11.0.0", + "prettier": "^3.4.1", + "rimraf": "^6.0.1", + "style-loader": "^4.0.0", + "ts-loader": "^9.5.1", + "typescript": "^5.1.0", + "webpack": "^5.96.1", + "webpack-cli": "^5.1.4", + "webpack-merge": "^6.0.1" + } +} diff --git a/extension/public/icon.png b/extension/public/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..dfd2c2745b7574fad8e39b54b05ae8e57b5faf53 GIT binary patch literal 1324 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 literal 0 HcmV?d00001 diff --git a/extension/public/icon48.png b/extension/public/icon48.png new file mode 100644 index 0000000000000000000000000000000000000000..769e741c57e302da3c8a4119d964c8b7a978f1d5 GIT binary patch literal 2692 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 + + + Jolly Roger Browser Extension Options + + + + +
+ + + diff --git a/extension/public/popup.html b/extension/public/popup.html new file mode 100644 index 000000000..0edc5086a --- /dev/null +++ b/extension/public/popup.html @@ -0,0 +1,12 @@ + + + + + Jolly Roger Browser Extension + + + +
+ + + diff --git a/extension/src/api.ts b/extension/src/api.ts new file mode 100644 index 000000000..85f10030f --- /dev/null +++ b/extension/src/api.ts @@ -0,0 +1,81 @@ +export class HttpError extends Error { + private status: number | undefined; + + constructor(msg: string, status?: number) { + super(msg); + Object.setPrototypeOf(this, HttpError.prototype); + this.status = status; + } + + getStatus(): number | undefined { + return this.status; + } +} + +export interface Hunt { + _id: string; + name: string; +} + +export type GdriveMimeTypesType = "spreadsheet" | "document"; + +export interface Puzzle { + title: string; + url: string; + tags: string[]; + expectedAnswerCount: number; + docType: GdriveMimeTypesType; + allowDuplicateUrls: boolean; +} + +export class JollyRogerClient { + private instance: string; + + private apiKey: string; + + constructor(instance: string, apiKey: string) { + this.instance = instance; + this.apiKey = apiKey; + } + + getHunts = async () => { + return (await this.callApi("/api/hunts")).hunts as Hunt[]; + }; + + getTagsForHunt = async (hunt: string) => { + return (await this.callApi(`/api/tags/${hunt}`)).tags as string[]; + }; + + addPuzzle = async (hunt: string, puzzle: Puzzle) => { + return (await this.callApi(`/api/createPuzzle/${hunt}`, puzzle)) + .id as string; + }; + + private callApi = (endpoint: string, requestBody?: any): Promise => { + return new Promise((resolve, reject) => { + const url = new URL(endpoint, this.instance); + const xhr = new XMLHttpRequest(); + xhr.open(requestBody ? "POST" : "GET", url); + xhr.setRequestHeader("Authorization", `Bearer ${this.apiKey}`); + if (requestBody) { + xhr.setRequestHeader("Content-Type", "application/json"); + } + xhr.onload = function () { + if (xhr.status === 200) { + resolve(JSON.parse(xhr.response)); + } else { + reject( + new HttpError( + `HTTP error ${xhr.status} calling ${url}`, + xhr.status, + ), + ); + } + }; + xhr.onerror = function () { + reject(new HttpError(`HTTP error calling ${url}`)); + }; + xhr.send(requestBody ? JSON.stringify(requestBody) : null); + }); + }; +} diff --git a/extension/src/options.tsx b/extension/src/options.tsx new file mode 100644 index 000000000..bfb8438b6 --- /dev/null +++ b/extension/src/options.tsx @@ -0,0 +1,127 @@ +import React, { useCallback, useEffect, useState } from "react"; +import Alert from "react-bootstrap/Alert"; +import Button from "react-bootstrap/Button"; +import Form from "react-bootstrap/Form"; +import { createRoot } from "react-dom/client"; +import { storedOptions } from "./storage"; + +import "bootstrap/dist/css/bootstrap.min.css"; + +const Options = () => { + const [status, setStatus] = useState(""); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + + const [jollyRogerInstance, setJollyRogerInstance] = useState(""); + const [apiKey, setApiKey] = useState(""); + + useEffect(() => { + void (async () => { + const options = await storedOptions.get(); + setJollyRogerInstance(options.jollyRogerInstance); + setApiKey(options.apiKey); + setLoading(false); + })(); + }, []); + + const saveOptions: React.FormEventHandler = useCallback( + (e: React.FormEvent) => { + e.preventDefault(); + setStatus(""); + setSaving(true); + let hostUrl: URL; + try { + hostUrl = new URL(jollyRogerInstance); + } catch (error) { + setStatus("Invalid instance URL."); + setSaving(false); + return; + } + + const hostPermission = { origins: [`*://${hostUrl.hostname}/*`] }; + + void (async () => { + // NOTE: There must not be any "await" calls above this one. Otherwise, Firefox rejects the + // request because it does not appear to take place in response to a user action (submitting + // the form), since the original action is lost in the ensuing stack trace. + if (!(await chrome.permissions.request(hostPermission))) { + setStatus("Permission not granted to Jolly Roger instance."); + setSaving(false); + return; + } + await storedOptions.put({ + jollyRogerInstance, + apiKey, + }); + setStatus("Settings saved."); + setSaving(false); + })(); + }, + [apiKey, jollyRogerInstance], + ); + + const onJollyRogerInstanceChange: React.ChangeEventHandler = + useCallback((event) => { + setJollyRogerInstance(event.target.value); + }, []); + + const onApiKeyChange: React.ChangeEventHandler = + useCallback((event) => { + setApiKey(event.target.value); + }, []); + + const form = ( +
+ + Jolly Roger instance + + + URL of the Jolly Roger instance to connect to. + + + + API key + + + API key to use for authentication. Generate one on your Jolly Roger + "My Profile" page, under "Advanced". + + + + + + + {status} + +
+ ); + + return ( +
+

Jolly Roger Browser Extension

+
+ {loading ?
loading...
: form} +
+ ); +}; + +const root = createRoot(document.getElementById("root")!); + +root.render( + + + , +); diff --git a/extension/src/popup.tsx b/extension/src/popup.tsx new file mode 100644 index 000000000..d141dc55c --- /dev/null +++ b/extension/src/popup.tsx @@ -0,0 +1,529 @@ +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import Button from "react-bootstrap/Button"; +import Col from "react-bootstrap/Col"; +import Form from "react-bootstrap/Form"; +import Modal from "react-bootstrap/Modal"; +import OverlayTrigger from "react-bootstrap/OverlayTrigger"; +import Row from "react-bootstrap/Row"; +import Tooltip from "react-bootstrap/Tooltip"; +import { createRoot } from "react-dom/client"; +import type { ActionMeta } from "react-select"; +import Creatable from "react-select/creatable"; +import type { GdriveMimeTypesType, Hunt, Puzzle } from "./api"; +import { HttpError, JollyRogerClient } from "./api"; +import { + storedOptions, + storedRecentTags, + storedSelectedHuntId, +} from "./storage"; + +import "bootstrap/dist/css/bootstrap.min.css"; + +type TagSelectOption = { value: string; label: string }; + +/* Either the ID of the created puzzle, or an error for any failures. */ +type Status = string | Error | undefined; + +interface RecentTagButtonProps { + tag: string; + onTagClicked: (tag: string) => void; +} + +const RecentTagButton = ({ tag, onTagClicked }: RecentTagButtonProps) => { + const onClick = useCallback(() => { + onTagClicked(tag); + }, [tag, onTagClicked]); + + return ( + + ); +}; + +const Popup = () => { + const [jollyRogerInstance, setJollyRogerInstance] = useState(""); + const [apiKey, setApiKey] = useState(""); + const [recentTags, setRecentTags] = useState([]); + const [loadingOptions, setLoadingOptions] = useState(true); + + // Load configuration / saved state from extension storage. + useEffect(() => { + void (async () => { + const options = await storedOptions.get(); + setApiKey(options.apiKey); + setJollyRogerInstance(options.jollyRogerInstance); + setRecentTags(await storedRecentTags.get()); + setLoadingOptions(false); + })(); + }, []); + + const [hunts, setHunts] = useState([]); + const [loadingHunts, setLoadingHunts] = useState(false); + const [hunt, setHunt] = useState(""); + + const [availableTags, setAvailableTags] = useState([]); + const [loadingTags, setLoadingTags] = useState(false); + const [tags, setTags] = useState([]); + + const [loadingPageDetails, setLoadingPageDetails] = useState(true); + const [title, setTitle] = useState(""); + const [currentURL, setCurrentURL] = useState(""); + + const [allowDuplicateUrls, setAllowDuplicateUrls] = useState(false); + const [docType, setDocType] = useState("spreadsheet"); + const [expectedAnswerCount, setExpectedAnswerCount] = useState(1); + + const [status, setStatus] = useState(); + const [saving, setSaving] = useState(false); + + const loading = loadingOptions || loadingHunts || loadingPageDetails; + const disableForm = loadingTags || saving; + + const jollyRogerClient = useMemo(() => { + if (!jollyRogerInstance || !apiKey) { + return undefined; + } + return new JollyRogerClient(jollyRogerInstance, apiKey); + }, [jollyRogerInstance, apiKey]); + + // Load the list of hunts from Jolly Roger, preselecting the last used hunt (if any). + useEffect(() => { + if (!jollyRogerClient) { + setLoadingHunts(false); + return; + } + setLoadingHunts(true); + void (async () => { + let huntList: Hunt[]; + try { + huntList = await jollyRogerClient.getHunts(); + } catch (error) { + let errorMsg: string; + if (error instanceof HttpError) { + errorMsg = `Unable to load hunts from Jolly Roger: ${error.message}.`; + } else { + errorMsg = `Unable to load hunts from Jolly Roger.`; + } + setStatus(new Error(errorMsg)); + setLoadingHunts(false); + return; + } + const selectedHuntId = await storedSelectedHuntId.get(); + setHunts(huntList); + if (huntList.find((huntElement) => huntElement._id === selectedHuntId)) { + setHunt(selectedHuntId); + } else if (huntList.length > 0) { + setHunt(huntList[0]._id); + } else { + setStatus(new Error("No hunts are available")); + } + setLoadingHunts(false); + })(); + }, [jollyRogerClient]); + + // Whenever a new hunt is selected, fetch the tags for that hunt for autocomplete. + useEffect(() => { + if (!jollyRogerClient || !hunt) { + return; + } + setLoadingTags(true); + void (async () => { + let huntTags: string[]; + try { + huntTags = await jollyRogerClient.getTagsForHunt(hunt); + } catch (error) { + let errorMsg: string; + if (error instanceof HttpError) { + errorMsg = `Unable to load tags from Jolly Roger: ${error.message}.`; + } else { + errorMsg = `Unable to load tags from Jolly Roger.`; + } + setStatus(new Error(errorMsg)); + setLoadingTags(false); + return; + } + + setAvailableTags(huntTags); + setLoadingTags(false); + })(); + }, [jollyRogerClient, hunt]); + + const selectOptions: TagSelectOption[] = availableTags + .filter(Boolean) + .map((t) => { + return { value: t, label: t }; + }); + + // Read the URL and title from the current page. + useEffect(() => { + setLoadingPageDetails(true); + void (async () => { + const tabs = await chrome.tabs.query({ + active: true, + currentWindow: true, + }); + const tabTitle = tabs[0].title ?? ""; + const tabUrl = tabs[0].url ?? ""; + + setCurrentURL(tabUrl); + + // TODO: Provide a way to customize title extraction per hunt. + if ( + tabUrl.includes("pandamagazine.com/island") && + tabTitle.includes(" | ") + ) { + // Puzzle Boats + setTitle(tabTitle.substring(tabTitle.indexOf(" | ") + 3)); + } else if ( + tabUrl.includes("puzzlehunt.azurewebsites.net") && + tabTitle.includes(" - ") + ) { + // Microsoft Puzzle Server + setTitle(tabTitle.substring(0, tabTitle.lastIndexOf(" - "))); + } else { + setTitle(tabTitle); + } + + setLoadingPageDetails(false); + })(); + }, []); + + const onHuntChange: React.ChangeEventHandler = useCallback( + (event) => { + void (async () => { + // Save the most recently selected hunt so it can be selected by default next time. + await storedSelectedHuntId.put(event.target.value); + })(); + setHunt(event.target.value); + }, + [], + ); + + const onTitleChange: React.ChangeEventHandler = useCallback( + (event) => { + setTitle(event.target.value); + }, + [], + ); + + const onAllowDuplicateUrlsChange: React.ChangeEventHandler = + useCallback((event) => { + setAllowDuplicateUrls(event.target.checked); + }, []); + + const onTagsChange = useCallback( + ( + value: readonly TagSelectOption[], + action: ActionMeta, + ) => { + let newTags = []; + switch (action.action) { + case "clear": + case "create-option": + case "deselect-option": + case "pop-value": + case "remove-value": + case "select-option": + newTags = value.map((v) => v.value); + break; + default: + return; + } + + setTags(newTags); + }, + [], + ); + + const addTag = useCallback( + (tag: string) => { + const newTags = [...tags]; + newTags.push(tag); + setTags(newTags); + }, + [tags], + ); + + const onDocTypeChange: React.ChangeEventHandler = + useCallback((event) => { + setDocType(event.currentTarget.value as GdriveMimeTypesType); + }, []); + + const onExpectedAnswerCountChange: React.ChangeEventHandler = + useCallback((event) => { + const string = event.currentTarget.value; + const value = Number(string); + setExpectedAnswerCount(value); + }, []); + + const addPuzzle: React.FormEventHandler = useCallback( + (e: React.FormEvent) => { + e.preventDefault(); + if (!jollyRogerClient) { + // Should be impossible, as we hide the form until configuration is complete. + setStatus("Jolly Roger is not configured"); + return; + } + setSaving(true); + const puzzle: Puzzle = { + title, + url: currentURL, + tags, + expectedAnswerCount, + docType, + allowDuplicateUrls, + }; + void (async () => { + let puzzleId: string; + try { + puzzleId = await jollyRogerClient.addPuzzle(hunt, puzzle); + } catch (error) { + let errorMsg: string; + if (error instanceof HttpError) { + if (error.getStatus() === 409) { + errorMsg = + "A puzzle already exists with this URL - did someone else already add this " + + 'puzzle? To force creation anyway, check the "Allow puzzles with identical URLs" ' + + "box and try again."; + } else { + errorMsg = `Unable to create puzzle: ${error.message}.`; + } + } else { + errorMsg = `Unable to create puzzle.`; + } + setStatus(new Error(errorMsg)); + setSaving(false); + return; + } + + const newTags = await storedRecentTags.put(tags); + setRecentTags(newTags); + setStatus(puzzleId); + setSaving(false); + })(); + }, + [ + jollyRogerClient, + hunt, + title, + currentURL, + tags, + expectedAnswerCount, + docType, + allowDuplicateUrls, + ], + ); + + const onCloseStatusDialog = useCallback(() => { + // If the creation was successful, close the extension. + if (typeof status === "string") { + window.close(); + } + setStatus(undefined); + }, [status]); + + const statusMessage = useMemo(() => { + if (!status) { + return <>Unknown error; + } else if (status instanceof Error) { + return ( + <> + Error: {status.message} + + ); + } else { + // status is the created puzzle ID + const puzzleUrl = new URL( + `/hunts/${hunt}/puzzles/${status}`, + jollyRogerInstance, + ); + return ( + <> + Puzzle successfully created:{" "} + + {title} + + + ); + } + }, [jollyRogerInstance, status, hunt, title]); + + const onConfigureLinkClicked = useCallback(() => { + void (async () => { + await chrome.runtime.openOptionsPage(); + })(); + }, []); + + const form = ( +
+ + + Hunt + + + + {hunts.map((huntItem) => { + return ( + + ); + })} + + + + + + Title + + + + + + + + URL + + + + + + + + + + Tags + + + { + return { label: tag, value: tag }; + })} + /> + {recentTags.map((recentTag) => { + return ( + + ); + })} + + + + + + Document type{" "} + + This can't be changed once a puzzle has been created. + Unless you're absolutely sure, use a spreadsheet. We only + expect to use documents for administrivia. + + } + > + (?) + + + + + + + + + + + + + Expected # of answers + + + + + + + + + +
+ ); + + const contents = + jollyRogerInstance !== "" && apiKey !== "" ? ( + form + ) : ( + + ); + + return ( +
+ {loading ?
loading...
: contents} + + {statusMessage} + + + + +
+ ); +}; + +const root = createRoot(document.getElementById("root")!); + +root.render( + + + , +); diff --git a/extension/src/storage.ts b/extension/src/storage.ts new file mode 100644 index 000000000..89df5ea43 --- /dev/null +++ b/extension/src/storage.ts @@ -0,0 +1,71 @@ +export interface OptionsData { + jollyRogerInstance: string; + apiKey: string; +} + +/** Stores extension configuration options. */ +class StoredOptions { + get: () => Promise = async () => { + const items = await chrome.storage.sync.get({ + jollyRogerInstance: "", + apiKey: "", + }); + return { + jollyRogerInstance: items.jollyRogerInstance as string, + apiKey: items.apiKey as string, + }; + }; + + put = async (options: OptionsData) => { + await chrome.storage.sync.set({ + jollyRogerInstance: options.jollyRogerInstance, + apiKey: options.apiKey, + }); + }; +} + +/** Stores a small list of recently used tags. */ +class StoredRecentTags { + static readonly TAG_COUNT = 5; + + get: () => Promise = async () => { + const items = await chrome.storage.sync.get({ + recentTags: [], + }); + return items.recentTags as string[]; + }; + + put: (tags: string[]) => Promise = async (tags) => { + // Put the newest tags to front, followed by previous tags (filtering out newest ones), then + // truncate to the TAG_COUNT limit. + const newTags = tags + .concat((await this.get()).filter((tag) => !tags.includes(tag))) + .slice(0, StoredRecentTags.TAG_COUNT); + await chrome.storage.sync.set({ + recentTags: newTags, + }); + return newTags; + }; +} + +/** Store the most recently selected hunt. */ +class StoredSelectedHuntId { + get: () => Promise = async () => { + const items = await chrome.storage.sync.get({ + selectedHuntId: "", + }); + return items.selectedHuntId as string; + }; + + put = async (huntId: string) => { + await chrome.storage.sync.set({ + selectedHuntId: huntId, + }); + }; +} + +const storedOptions = new StoredOptions(); +const storedRecentTags = new StoredRecentTags(); +const storedSelectedHuntId = new StoredSelectedHuntId(); + +export { storedOptions, storedRecentTags, storedSelectedHuntId }; diff --git a/extension/tsconfig.json b/extension/tsconfig.json new file mode 100644 index 000000000..078c64614 --- /dev/null +++ b/extension/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "strict": true, + "target": "es6", + "moduleResolution": "bundler", + "module": "ES6", + "esModuleInterop": true, + "sourceMap": false, + "rootDir": "src", + "noEmitOnError": true, + "jsx": "react", + "typeRoots": ["node_modules/@types"] + } +} diff --git a/extension/webpack/webpack.common.mjs b/extension/webpack/webpack.common.mjs new file mode 100644 index 000000000..1970b5547 --- /dev/null +++ b/extension/webpack/webpack.common.mjs @@ -0,0 +1,72 @@ +import { join } from "path"; +import CopyPlugin from "copy-webpack-plugin"; + +const srcDir = join(import.meta.dirname, "..", "src"); + +export default ["chrome", "firefox"].map((browser) => { + return { + entry: { + popup: join(srcDir, "popup.tsx"), + options: join(srcDir, "options.tsx"), + }, + output: { + path: join(import.meta.dirname, `../dist/${browser}/js`), + filename: "[name].js", + }, + optimization: { + splitChunks: { + name: "vendor", + chunks(chunk) { + return chunk.name !== "background"; + }, + }, + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: "ts-loader", + exclude: /node_modules/, + }, + { + test: /\.css$/, + use: ["style-loader", "css-loader"], + }, + ], + }, + resolve: { + extensions: [".ts", ".tsx", ".js"], + }, + plugins: [ + new CopyPlugin({ + patterns: [ + { + from: ".", + to: "../", + context: "public", + transform: { + transformer(input, absoluteFilename) { + if (!absoluteFilename.endsWith("manifest.json")) { + return input; + } + const manifest = JSON.parse(input.toString()); + if (browser === "firefox") { + manifest.browser_specific_settings = { + gecko: { + id: "{2bf7bc48-4f7d-4c16-9a88-58bb9f1c6ff5}", + strict_min_version: "128.0", + }, + }; + } else { + manifest.minimum_chrome_version = "88"; + } + return JSON.stringify(manifest); + }, + }, + }, + ], + options: {}, + }), + ], + }; +}); diff --git a/extension/webpack/webpack.dev.mjs b/extension/webpack/webpack.dev.mjs new file mode 100644 index 000000000..83d201d03 --- /dev/null +++ b/extension/webpack/webpack.dev.mjs @@ -0,0 +1,9 @@ +import { merge } from "webpack-merge"; +import common from "./webpack.common.mjs"; + +export default common.map((browserConfig) => { + return merge(browserConfig, { + devtool: "inline-source-map", + mode: "development", + }); +}); diff --git a/extension/webpack/webpack.prod.mjs b/extension/webpack/webpack.prod.mjs new file mode 100644 index 000000000..84f0c06cc --- /dev/null +++ b/extension/webpack/webpack.prod.mjs @@ -0,0 +1,8 @@ +import { merge } from "webpack-merge"; +import common from "./webpack.common.mjs"; + +export default common.map((browserConfig) => { + return merge(browserConfig, { + mode: "production", + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 60ddf3853..5ee844546 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -47,6 +47,7 @@ "./.meteor/local/build/**", "./.meteor/local/bundler-cache/**", "./packages/**", - "./eslint/**" + "./eslint/**", + "./extension/**" ] }