From 696d56a4867e74c813e4f11c22d3a17243f0498d Mon Sep 17 00:00:00 2001 From: Igor Sala Date: Mon, 16 Jun 2025 20:43:54 +0200 Subject: [PATCH 01/47] Layout for application created, documentation added. The most important changes: - header with changing label using context API - sidebar for whole app - navbar element for different pages. --- Tokenization/webapp/app/app.css | 9 + Tokenization/webapp/app/root.tsx | 10 +- Tokenization/webapp/app/routes/home.tsx | 6 + .../webapp/app/routes/tokens/overview.tsx | 63 +- Tokenization/webapp/app/ui/header/header.tsx | 46 ++ .../webapp/app/ui/header/headerContext.tsx | 39 + Tokenization/webapp/app/ui/layout.css | 19 + Tokenization/webapp/app/ui/layout.tsx | 49 ++ Tokenization/webapp/app/ui/navbar.tsx | 50 +- Tokenization/webapp/app/ui/sidebar.tsx | 62 ++ Tokenization/webapp/package-lock.json | 745 +++++++++++++++++- Tokenization/webapp/package.json | 3 + 12 files changed, 1036 insertions(+), 65 deletions(-) create mode 100644 Tokenization/webapp/app/ui/header/header.tsx create mode 100644 Tokenization/webapp/app/ui/header/headerContext.tsx create mode 100644 Tokenization/webapp/app/ui/layout.css create mode 100644 Tokenization/webapp/app/ui/layout.tsx create mode 100644 Tokenization/webapp/app/ui/sidebar.tsx diff --git a/Tokenization/webapp/app/app.css b/Tokenization/webapp/app/app.css index 310fdee23..ed2794845 100644 --- a/Tokenization/webapp/app/app.css +++ b/Tokenization/webapp/app/app.css @@ -11,3 +11,12 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ + +.justify-end { + justify-content: flex-end; +} + +.ml1 { margin-left: var(--space-xs); } +.ml2 { margin-left: var(--space-s); } +.ml3 { margin-left: var(--space-m); } +.ml4 { margin-left: var(--space-l); } \ No newline at end of file diff --git a/Tokenization/webapp/app/root.tsx b/Tokenization/webapp/app/root.tsx index 06a82d3ad..a50e70ed4 100644 --- a/Tokenization/webapp/app/root.tsx +++ b/Tokenization/webapp/app/root.tsx @@ -21,11 +21,12 @@ import { ScrollRestoration, useNavigation, } from 'react-router'; +import {useState} from 'react'; import type { Route } from './+types/root'; import './app.css'; import '@aliceo2/web-ui/Frontend/css/src/bootstrap.css' -import {Navbar} from '~/ui/navbar'; import {Spinner} from '~/ui/spinner'; +import AppLayout from '~/ui/layout' export function Layout({ children }: { children: React.ReactNode }) { const { state } = useNavigation(); @@ -39,10 +40,9 @@ export function Layout({ children }: { children: React.ReactNode }) { - -
- {state === 'loading' ? : children} -
+ + {children} + diff --git a/Tokenization/webapp/app/routes/home.tsx b/Tokenization/webapp/app/routes/home.tsx index 91d18d622..736517114 100644 --- a/Tokenization/webapp/app/routes/home.tsx +++ b/Tokenization/webapp/app/routes/home.tsx @@ -13,10 +13,16 @@ */ import { Link } from 'react-router'; +import { useSetHeader } from '~/ui/header/headerContext'; export default function Home() { + + const { setHeaderContent } = useSetHeader(); + setHeaderContent('Tokenization Admin Interface'); + return <>

Welcome to (dummy) Tokenization GUI!

Tokens overview + ; } diff --git a/Tokenization/webapp/app/routes/tokens/overview.tsx b/Tokenization/webapp/app/routes/tokens/overview.tsx index f0eea6302..3ba6c1039 100644 --- a/Tokenization/webapp/app/routes/tokens/overview.tsx +++ b/Tokenization/webapp/app/routes/tokens/overview.tsx @@ -14,7 +14,14 @@ import type { Token } from '../../components/tokens/token'; import type { Route } from './+types/overview'; + import {Link} from 'react-router'; +import { useState } from 'react'; +import {Box, Tabs, Tab} from '@mui/material'; + +import { useSetHeader } from '~/ui/header/headerContext'; + +import { TabsNavbar } from '~/ui/navbar' export const clientLoader = async (): Promise => { const response = await fetch('http://localhost:8080/api/tokens'); @@ -24,22 +31,44 @@ export const clientLoader = async (): Promise => { return response.json(); } +// Will be changed in next PR +function TokenTable({tokens}: {tokens: Token[]}) { + return + + + + + + + + {tokens.map((token: Token) => + + + )} + +
IDvalidity
{token.tokenId}{token.validity}
+} + export default function Overview({loaderData: tokens}: Route.ComponentProps) { - return <> -

Tokens

- - - - - - - - - {tokens.map((token: Token) => - - - )} - -
IDvalidity
{token.tokenId}{token.validity}
- + + const { setHeaderContent } = useSetHeader() + setHeaderContent('Tokens') + + const [ tabIndex, setTabIndex ] = useState(0) + + return
+ + + + + + { + tabIndex == 0 ? + : +
+

Create Token

+

Form to create a new token will go here.

+
+ } +
} diff --git a/Tokenization/webapp/app/ui/header/header.tsx b/Tokenization/webapp/app/ui/header/header.tsx new file mode 100644 index 000000000..2b9bd8d1d --- /dev/null +++ b/Tokenization/webapp/app/ui/header/header.tsx @@ -0,0 +1,46 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ + +import {IconHome, IconCog} from '../icon' +import { Link } from 'react-router' + +/** + * AppHeader + * + * Displays the application header with navigation icons (home, settings) and a customizable title. + * + * @param headerContent Optional string to display as the header title. + */ +export function AppHeader({headerContent}: {headerContent?: string}) { + return ( +
+ + +
+ +
+ + +
+ +
+ + +
+

{headerContent ? headerContent: "Tokenization Admin Interface"}

+
+ +
+ ) +} \ No newline at end of file diff --git a/Tokenization/webapp/app/ui/header/headerContext.tsx b/Tokenization/webapp/app/ui/header/headerContext.tsx new file mode 100644 index 000000000..4f2505c8e --- /dev/null +++ b/Tokenization/webapp/app/ui/header/headerContext.tsx @@ -0,0 +1,39 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ + + + + +import { createContext, useContext } from 'react'; + + +/** + * HeaderContext + * + * React context providing the setHeaderContent function for updating the header content + * from any component within the provider tree. + */ +export const HeaderContext = createContext({ + setHeaderContent: (_: string) => {} +}); + +/** + * useSetHeader + * + * Custom React hook to access the HeaderContext. + * Allows components to call setHeaderContent to update the header text. + * + * @returns The context value containing setHeaderContent. + */ +export const useSetHeader = () => useContext(HeaderContext); diff --git a/Tokenization/webapp/app/ui/layout.css b/Tokenization/webapp/app/ui/layout.css new file mode 100644 index 000000000..524b9701c --- /dev/null +++ b/Tokenization/webapp/app/ui/layout.css @@ -0,0 +1,19 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ + +.container { + display: grid; + grid-template-rows: 1fr 3fr 1.3fr; + grid-template-columns: 0.575fr 3fr; +} \ No newline at end of file diff --git a/Tokenization/webapp/app/ui/layout.tsx b/Tokenization/webapp/app/ui/layout.tsx new file mode 100644 index 000000000..c9f07ee55 --- /dev/null +++ b/Tokenization/webapp/app/ui/layout.tsx @@ -0,0 +1,49 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ + +/** + * Layout + * + * Main layout component for the application. + * Provides the header, sidebar, and main content area. + * Also sets up the HeaderContext to allow child components to update the header content. + * + * @param state The current loading state of the application (e.g., 'loading' or ready). + * @param children The main content to be rendered inside the layout. + * + * @returns The application layout with header, sidebar, and content area. + */ + +import {useState} from 'react' +import { AppHeader } from './header/header' +import { HeaderContext } from './header/headerContext' +import { AppSidebar } from './sidebar' +import { Spinner } from './spinner' +import './layout.css' + +export default function({state, children}: any) { + + const [headerContent, setHeaderContent] = useState('Tokenization Admin Interface'); + + return +
+ + +
+ {state === 'loading' ? : children} +
+
+
+ +} \ No newline at end of file diff --git a/Tokenization/webapp/app/ui/navbar.tsx b/Tokenization/webapp/app/ui/navbar.tsx index 7b0d82ae9..858de2734 100644 --- a/Tokenization/webapp/app/ui/navbar.tsx +++ b/Tokenization/webapp/app/ui/navbar.tsx @@ -12,24 +12,34 @@ * or submit itself to any jurisdiction. */ -import { NavLink } from 'react-router'; -import type {NavLinkProps} from 'react-router'; +import { Box, Tabs } from '@mui/material'; +import React from 'react'; -const StyledNavLink = ({children, className: _, ...props}: NavLinkProps) => { - return `btn btn-tab ${isActive ? 'selected' : ''}`} - >{children} -} - -export const Navbar = () => { - return -} +/** + * TabsNavbar + * + * A reusable component that renders a Material-UI Tabs navigation bar. + * Allows switching between different sections using tab buttons. + * + * @param tabIndex The currently selected tab index. + * @param setTabIndex Function to update the selected tab index. + * @param children Tab (@mui/material) components (usually ) to be rendered inside the navigation bar. + */ +export function TabsNavbar({tabIndex, setTabIndex, children}: {tabIndex: number, setTabIndex: (index: number) => void, children?: React.ReactNode}) { + + const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { + setTabIndex(newValue) + } + + return + + {children} + + +} \ No newline at end of file diff --git a/Tokenization/webapp/app/ui/sidebar.tsx b/Tokenization/webapp/app/ui/sidebar.tsx new file mode 100644 index 000000000..2d2e75d8b --- /dev/null +++ b/Tokenization/webapp/app/ui/sidebar.tsx @@ -0,0 +1,62 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ + +import { NavLink } from 'react-router'; +import Button from '@mui/material/Button'; +import type {NavLinkProps} from 'react-router'; + + + + +/** + * StyledNavLink + * + * A wrapper component that renders a Material-UI Button styled as a navigation link. + * It uses NavLink from react-router to determine if the link is active and applies + * the 'contained' variant for the active route and 'outlined' for inactive routes. + * + * @param children The content to display inside the button. + * @param to The target route path. + */ +const StyledNavLink = ({children, to}: NavLinkProps) => { + return + {({isActive}) => ( + + )} + +} + +/** + * AppSidebar + * + * The sidebar navigation component for the application. + * Displays navigation buttons for different sections using StyledNavLink. + * Styled with a light gray background and rounded left corners. + * + * @returns The sidebar navigation JSX element. + */ +export const AppSidebar = () => { + return +} diff --git a/Tokenization/webapp/package-lock.json b/Tokenization/webapp/package-lock.json index c2e3a8b2f..1de631491 100644 --- a/Tokenization/webapp/package-lock.json +++ b/Tokenization/webapp/package-lock.json @@ -10,6 +10,9 @@ "license": "GPL-3.0", "dependencies": { "@aliceo2/web-ui": "^2.8.4", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/material": "^7.1.1", "@react-router/node": "^7.5.0", "isbot": "^5", "react": "^19.0.0", @@ -65,7 +68,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", @@ -156,7 +158,6 @@ "version": "7.27.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz", "integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.27.3", @@ -276,7 +277,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -363,7 +363,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -373,7 +372,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -407,7 +405,6 @@ "version": "7.27.3", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.3.tgz", "integrity": "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.3" @@ -524,11 +521,19 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -543,7 +548,6 @@ "version": "7.27.3", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.3.tgz", "integrity": "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -562,7 +566,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -580,14 +583,12 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/@babel/types": { "version": "7.27.3", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -617,6 +618,167 @@ "kuler": "^2.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/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/@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.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "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/styled": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", + "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "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/@esbuild/aix-ppc64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", @@ -1064,7 +1226,6 @@ "version": "0.3.8", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -1079,7 +1240,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1089,7 +1249,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1099,14 +1258,12 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, "license": "MIT" }, "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==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1119,6 +1276,213 @@ "integrity": "sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng==", "license": "MIT" }, + "node_modules/@mui/core-downloads-tracker": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.1.tgz", + "integrity": "sha512-yBckQs4aQ8mqukLnPC6ivIRv6guhaXi8snVl00VtyojBbm+l6VbVhyTSZ68Abcx7Ah8B+GZhrB7BOli+e+9LkQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/material": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.1.1.tgz", + "integrity": "sha512-mTpdmdZCaHCGOH3SrYM41+XKvNL0iQfM9KlYgpSjgadXx/fEKhhvOktxm8++Xw6FFeOHoOiV+lzOI8X1rsv71A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1", + "@mui/core-downloads-tracker": "^7.1.1", + "@mui/system": "^7.1.1", + "@mui/types": "^7.4.3", + "@mui/utils": "^7.1.1", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.1.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^7.1.1", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.1.tgz", + "integrity": "sha512-M8NbLUx+armk2ZuaxBkkMk11ultnWmrPlN0Xe3jUEaBChg/mcxa5HWIWS1EE4DF36WRACaAHVAvyekWlDQf0PQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1", + "@mui/utils": "^7.1.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.1.tgz", + "integrity": "sha512-R2wpzmSN127j26HrCPYVQ53vvMcT5DaKLoWkrfwUYq3cYytL6TQrCH8JBH3z79B6g4nMZZVoaXrxO757AlShaw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.1.1.tgz", + "integrity": "sha512-Kj1uhiqnj4Zo7PDjAOghtXJtNABunWvhcRU0O7RQJ7WOxeynoH6wXPcilphV8QTFtkKaip8EiNJRiCD+B3eROA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1", + "@mui/private-theming": "^7.1.1", + "@mui/styled-engine": "^7.1.1", + "@mui/types": "^7.4.3", + "@mui/utils": "^7.1.1", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.3.tgz", + "integrity": "sha512-2UCEiK29vtiZTeLdS2d4GndBKacVyxGvReznGXGr+CzW/YhjIX+OHUdCIczZjzcRAgKBGmE9zCIgoV9FleuyRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.1.tgz", + "integrity": "sha512-BkOt2q7MBYl7pweY2JWwfrlahhp+uGLR8S+EhiyRaofeRYUWL2YKbSGQvN4hgSN1i8poN0PaUiii1kEMrchvzg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1", + "@mui/types": "^7.4.3", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@npmcli/git": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz", @@ -1192,6 +1556,16 @@ "node": ">=14" } }, + "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==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -1636,11 +2010,22 @@ "undici-types": "~6.19.2" } }, + "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.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.1.6", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz", "integrity": "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -1656,6 +2041,15 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", @@ -1733,6 +2127,21 @@ "@babel/types": "^7.23.6" } }, + "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", @@ -1876,6 +2285,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/caniuse-lite": { "version": "1.0.30001718", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", @@ -1913,6 +2331,15 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", @@ -2007,6 +2434,31 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, + "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/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" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2042,7 +2494,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/debug": { @@ -2088,6 +2539,16 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "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==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2160,6 +2621,21 @@ "dev": true, "license": "MIT" }, + "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/error-ex/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/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2254,6 +2730,18 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -2361,6 +2849,12 @@ "node": ">= 0.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/fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", @@ -2513,7 +3007,6 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -2578,6 +3071,21 @@ "node": ">=18.0.0" } }, + "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/hoist-non-react-statics/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==", + "license": "MIT" + }, "node_modules/hosted-git-info": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.3.tgz", @@ -2629,6 +3137,22 @@ "node": ">=0.10.0" } }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "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/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -2654,7 +3178,6 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -2739,14 +3262,12 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/jsesc": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -2855,6 +3376,12 @@ "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", "license": "MIT" }, + "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/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -2933,6 +3460,18 @@ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, + "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==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -3183,6 +3722,15 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-hash": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", @@ -3256,6 +3804,42 @@ "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/parse-json/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==", + "license": "MIT" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3275,6 +3859,12 @@ "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": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -3305,6 +3895,15 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, + "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/pathe": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", @@ -3316,7 +3915,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -3414,6 +4012,23 @@ "node": ">=10" } }, + "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==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/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==", + "license": "MIT" + }, "node_modules/protobufjs": { "version": "7.5.2", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.2.tgz", @@ -3511,6 +4126,12 @@ "react": "^19.1.0" } }, + "node_modules/react-is": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz", + "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==", + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -3552,6 +4173,22 @@ "node": ">=18" } }, + "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==", + "license": "BSD-3-Clause", + "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/readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -3587,6 +4224,35 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -4090,6 +4756,24 @@ "node": ">=8" } }, + "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-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/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -4696,6 +5380,21 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } } } } diff --git a/Tokenization/webapp/package.json b/Tokenization/webapp/package.json index bd8c85419..13f419f83 100644 --- a/Tokenization/webapp/package.json +++ b/Tokenization/webapp/package.json @@ -23,6 +23,9 @@ "homepage": "https://github.com/AliceO2Group/WebUi#readme", "dependencies": { "@aliceo2/web-ui": "^2.8.4", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/material": "^7.1.1", "@react-router/node": "^7.5.0", "isbot": "^5", "react": "^19.0.0", From 1a45249b4b4672921515546d22e9d3b9295f6b86 Mon Sep 17 00:00:00 2001 From: Igor Sala Date: Wed, 18 Jun 2025 00:08:30 +0200 Subject: [PATCH 02/47] First idea for running test container. Created image with dockerfile - downloading google-chrome with all dependencies. Successful run for first test. --- Tokenization/docker-compose.test.yml | 15 + Tokenization/tests/ui/Dockerfile | 19 + Tokenization/tests/ui/package-lock.json | 2111 +++++++++++++++++++++++ Tokenization/tests/ui/package.json | 10 + Tokenization/tests/ui/test/main.test.js | 23 + 5 files changed, 2178 insertions(+) create mode 100644 Tokenization/docker-compose.test.yml create mode 100644 Tokenization/tests/ui/Dockerfile create mode 100644 Tokenization/tests/ui/package-lock.json create mode 100644 Tokenization/tests/ui/package.json create mode 100644 Tokenization/tests/ui/test/main.test.js diff --git a/Tokenization/docker-compose.test.yml b/Tokenization/docker-compose.test.yml new file mode 100644 index 000000000..bde82aa38 --- /dev/null +++ b/Tokenization/docker-compose.test.yml @@ -0,0 +1,15 @@ +services: + ui-tests: + build: + context: ./tests/ui + dockerfile: Dockerfile + working_dir: /tests/ui + volumes: + - ./tests/ui:/tests/ui + depends_on: + reverse-proxy: + condition: service_started + backend: + condition: service_healthy + webapp: + condition: service_healthy \ No newline at end of file diff --git a/Tokenization/tests/ui/Dockerfile b/Tokenization/tests/ui/Dockerfile new file mode 100644 index 000000000..00d3e51dd --- /dev/null +++ b/Tokenization/tests/ui/Dockerfile @@ -0,0 +1,19 @@ +FROM node:22-slim + +WORKDIR /tests/ui + +# system dependencies for running Chrome +RUN apt-get update && \ + apt-get install -y curl gnupg ca-certificates fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libcups2 libdbus-1-3 libdrm2 libgbm1 libgtk-3-0 libnspr4 libnss3 libx11-xcb1 libxcomposite1 libxdamage1 libxrandr2 xdg-utils libu2f-udev libvulkan1 libxshmfence1 libxss1 --no-install-recommends && \ + curl --location --silent https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \ + sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' && \ + apt-get update && \ + apt-get install -y google-chrome-stable --no-install-recommends && \ + rm -rf /var/lib/apt/lists/* + +COPY package*.json ./ +RUN npm install --silent + +COPY . . + +CMD ["sh", "-c", "npx wait-on http://reverse-proxy:8080 && npm run test"] \ No newline at end of file diff --git a/Tokenization/tests/ui/package-lock.json b/Tokenization/tests/ui/package-lock.json new file mode 100644 index 000000000..5beabaafb --- /dev/null +++ b/Tokenization/tests/ui/package-lock.json @@ -0,0 +1,2111 @@ +{ + "name": "ui", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "mocha": "^11.6.0", + "puppeteer": "^24.10.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "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/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.10.5", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.5.tgz", + "integrity": "sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.1", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.2", + "tar-fs": "^3.0.8", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz", + "integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "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, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "dev": true, + "license": "Apache-2.0" + }, + "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/bare-events": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", + "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.5.tgz", + "integrity": "sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", + "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", + "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT", + "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/chalk/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, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chromium-bidi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-5.1.0.tgz", + "integrity": "sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/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/cliui/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/cliui/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/cliui/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/cliui/node_modules/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/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, + "license": "MIT", + "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, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "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, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1452169", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1452169.tgz", + "integrity": "sha512-FOFDVMGrAUNp0dDKsAU1TorWJUx2JOU1k9xdgBKKJF3IBh/Uhl2yswG5r3TEAOrCiGY2QRp1e6LVDQrCsTKO4g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "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/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/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", + "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "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/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "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-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "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/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/mocha": { + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.6.0.tgz", + "integrity": "sha512-i0JVb+OUBqw63X/1pC3jCyJsqYisgxySBbsQa8TKvefpA1oEnw7JXxXnftfMHRsw7bEEVGRtVlHcDYXBa7FzVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.10.1.tgz", + "integrity": "sha512-7T3rfSaaPt5A31VITV5YKQ4wPCCv4aPn8byDaV+9lhDU9v7BWYY4Ncwerw3ZR5mIolrh/PvzGdIDK7yiBth75g==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.10.5", + "chromium-bidi": "5.1.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1452169", + "puppeteer-core": "24.10.1", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.10.1.tgz", + "integrity": "sha512-AE6doA9znmEEps/pC5lc9p0zejCdNLR6UBp3EZ49/15Nbvh+uklXxGox7Qh8/lFGqGVwxInl0TXmsOmIuIMwiQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.10.5", + "chromium-bidi": "5.1.0", + "debug": "^4.4.1", + "devtools-protocol": "0.0.1452169", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "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/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, + "license": "MIT", + "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, + "license": "MIT", + "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/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.5.tgz", + "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.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/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tar-fs": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.10.tgz", + "integrity": "sha512-C1SwlQGNLe/jPNqapK8epDsXME7CAJR5RL3GcE6KWx1d9OUByzoHVcbu1VPI8tevg9H8Alae0AApHHFGzrD5zA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "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==", + "dev": true, + "license": "0BSD" + }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "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, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workerpool": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.2.tgz", + "integrity": "sha512-Xz4Nm9c+LiBHhDR5bDLnNzmj6+5F+cyEAWPMkbs2awq/dYazR/efelZzUAjB/y3kNHL+uzkHvxVVpaOfGCPV7A==", + "dev": true, + "license": "Apache-2.0" + }, + "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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/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/yargs/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/yargs/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/yargs/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/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.65", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.65.tgz", + "integrity": "sha512-kMyE2qsXK1p+TAvO7zsf5wMFiCejU3obrUDs9bR1q5CBKykfvp7QhhXrycUylMoOow0iEUSyjLlZZdCsHwSldQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/Tokenization/tests/ui/package.json b/Tokenization/tests/ui/package.json new file mode 100644 index 000000000..e6fc2368e --- /dev/null +++ b/Tokenization/tests/ui/package.json @@ -0,0 +1,10 @@ +{ + "scripts": { + "test": "mocha test/**/*.js" + }, + + "devDependencies": { + "mocha": "^11.6.0", + "puppeteer": "^24.10.1" + } +} diff --git a/Tokenization/tests/ui/test/main.test.js b/Tokenization/tests/ui/test/main.test.js new file mode 100644 index 000000000..9811a9440 --- /dev/null +++ b/Tokenization/tests/ui/test/main.test.js @@ -0,0 +1,23 @@ +const assert = require('assert'); +const pupeteer = require('puppeteer'); + +describe('Puppeteer test', () => { + it('Pupeteer launch', async () => { + const browser = await pupeteer.launch({ + executablePath: '/usr/bin/google-chrome', + headless: true, + args: [ + '--no-sandbox', + ] + }) + + const page = await browser.newPage(); + + await page.goto('http://reverse-proxy:8080/') + + const headerContent = await page.$eval('header', el => el.textContent) + assert.ok(headerContent.includes('Tokenization')) + + await browser.close() + }); +}) \ No newline at end of file From 38f90edba95d48ee27788efc6ebac8f24d250021 Mon Sep 17 00:00:00 2001 From: Igor Sala Date: Wed, 18 Jun 2025 15:11:14 +0200 Subject: [PATCH 03/47] Session data taken correctly but it rerenders page again which throws Error --- Tokenization/webapp/app/hooks/useAuth.tsx | 20 ++++++++++++++++++++ Tokenization/webapp/app/root.tsx | 22 +++++++++++++++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 Tokenization/webapp/app/hooks/useAuth.tsx diff --git a/Tokenization/webapp/app/hooks/useAuth.tsx b/Tokenization/webapp/app/hooks/useAuth.tsx new file mode 100644 index 000000000..33b0e29aa --- /dev/null +++ b/Tokenization/webapp/app/hooks/useAuth.tsx @@ -0,0 +1,20 @@ +import {useState, useEffect} from 'react'; +import sessionService from '@aliceo2/web-ui/Frontend/js/src/sessionService' + +export function useAuth(role: string): boolean { + const [hasAccess, setHasAccess] = useState(false) + + useEffect( () => { + try { + if(!sessionService.session) { + setHasAccess(false) + } + setHasAccess(sessionService.hasAccess(role)) + }catch(e) { + setHasAccess(false) + } + }, [role]) + + return hasAccess + +} diff --git a/Tokenization/webapp/app/root.tsx b/Tokenization/webapp/app/root.tsx index a50e70ed4..627432ef0 100644 --- a/Tokenization/webapp/app/root.tsx +++ b/Tokenization/webapp/app/root.tsx @@ -21,16 +21,21 @@ import { ScrollRestoration, useNavigation, } from 'react-router'; -import {useState} from 'react'; +import {useState, useEffect, use} from 'react'; import type { Route } from './+types/root'; import './app.css'; import '@aliceo2/web-ui/Frontend/css/src/bootstrap.css' import {Spinner} from '~/ui/spinner'; import AppLayout from '~/ui/layout' +import sessionService from '@aliceo2/web-ui/Frontend/js/src/sessionService'; +import { useAuth } from './hooks/useAuth'; + + export function Layout({ children }: { children: React.ReactNode }) { const { state } = useNavigation(); + return ( @@ -44,14 +49,25 @@ export function Layout({ children }: { children: React.ReactNode }) { {children} - + ); } export default function App() { - return ; + useEffect(() => { + try{ + sessionService.loadAndHideParameters() + console.log(sessionService.session) + }catch(e) { + console.error(e) + } + }, []) + + const hasAccess = useAuth('admin') + + return hasAccess ? : ; } export function HydrateFallback() { From 6faffd18555ec558ddbcfce825593e2ebe639de5 Mon Sep 17 00:00:00 2001 From: Igor Sala Date: Thu, 21 Aug 2025 23:04:40 +0200 Subject: [PATCH 04/47] - Added eslint configuration with established assumptions - Added needed packages with eslint for js and ts - move style to css files - kept in style directory - changes in structure of code without affecting any appearance --- Tokenization/webapp/app/app.css | 6 +- .../webapp/app/components/logs/log.ts | 8 +- .../app/components/tokens/token-logs.tsx | 48 +- .../webapp/app/components/tokens/token.ts | 6 +- Tokenization/webapp/app/root.tsx | 18 +- Tokenization/webapp/app/routes.ts | 12 +- Tokenization/webapp/app/routes/home.tsx | 13 +- .../webapp/app/routes/tokens/details.tsx | 103 +++-- .../webapp/app/routes/tokens/overview.tsx | 109 +++-- .../components-styles.css} | 9 +- Tokenization/webapp/app/styles/ui-styles.css | 30 ++ Tokenization/webapp/app/ui/header/header.tsx | 49 +- .../webapp/app/ui/header/headerContext.tsx | 18 +- Tokenization/webapp/app/ui/icon.tsx | 436 +++++++++--------- Tokenization/webapp/app/ui/layout.tsx | 55 ++- Tokenization/webapp/app/ui/navbar.tsx | 49 +- Tokenization/webapp/app/ui/sidebar.tsx | 52 +-- Tokenization/webapp/app/ui/spinner.tsx | 50 +- Tokenization/webapp/eslint.config.js | 223 +++++++++ Tokenization/webapp/package.json | 15 +- 20 files changed, 819 insertions(+), 490 deletions(-) rename Tokenization/webapp/app/{ui/layout.css => styles/components-styles.css} (82%) create mode 100644 Tokenization/webapp/app/styles/ui-styles.css create mode 100644 Tokenization/webapp/eslint.config.js diff --git a/Tokenization/webapp/app/app.css b/Tokenization/webapp/app/app.css index ed2794845..f59fbf8aa 100644 --- a/Tokenization/webapp/app/app.css +++ b/Tokenization/webapp/app/app.css @@ -19,4 +19,8 @@ .ml1 { margin-left: var(--space-xs); } .ml2 { margin-left: var(--space-s); } .ml3 { margin-left: var(--space-m); } -.ml4 { margin-left: var(--space-l); } \ No newline at end of file +.ml4 { margin-left: var(--space-l); } + +.scale25 { + transform: scale(2.5); +} \ No newline at end of file diff --git a/Tokenization/webapp/app/components/logs/log.ts b/Tokenization/webapp/app/components/logs/log.ts index d762b186d..90cafaf95 100644 --- a/Tokenization/webapp/app/components/logs/log.ts +++ b/Tokenization/webapp/app/components/logs/log.ts @@ -10,10 +10,10 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ export interface Log { - id: number; - title: string; - content: string; + id: number; + title: string; + content: string; } diff --git a/Tokenization/webapp/app/components/tokens/token-logs.tsx b/Tokenization/webapp/app/components/tokens/token-logs.tsx index 767188bf0..7b896362e 100644 --- a/Tokenization/webapp/app/components/tokens/token-logs.tsx +++ b/Tokenization/webapp/app/components/tokens/token-logs.tsx @@ -10,30 +10,36 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ import type { Log } from '~/components/logs/log'; interface TokenLogsProps { - logs: Log[]; + logs: Log[]; } -export const TokenLogs = ({logs}: TokenLogsProps) => { - if (logs.length === 0) { - return

No logs linked to this token

- } - return - - - - - - - - {logs.map(({id, title, content}) => - - - )} - -
TitleContent
{title}{content}
-} +/** + * Displays log entries associated with a token in a table format. + * Shows a "no logs" message when the logs array is empty. + * + * @param logs - Array of log entries to display + */ +export const TokenLogs = ({ logs }: TokenLogsProps) => { + if (logs.length === 0) { + return

No logs linked to this token

; + } + return + + + + + + + + {logs.map(({ id, title, content }) => + + + )} + +
TitleContent
{title}{content}
; +}; diff --git a/Tokenization/webapp/app/components/tokens/token.ts b/Tokenization/webapp/app/components/tokens/token.ts index f345afabe..d6a098cf8 100644 --- a/Tokenization/webapp/app/components/tokens/token.ts +++ b/Tokenization/webapp/app/components/tokens/token.ts @@ -10,9 +10,9 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ export interface Token { - tokenId: number; - validity: 'good'|'bad'; + tokenId: number; + validity: 'good' | 'bad'; } diff --git a/Tokenization/webapp/app/root.tsx b/Tokenization/webapp/app/root.tsx index a50e70ed4..db2946894 100644 --- a/Tokenization/webapp/app/root.tsx +++ b/Tokenization/webapp/app/root.tsx @@ -10,7 +10,8 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ +import type { Route } from './+types/root'; import { isRouteErrorResponse, @@ -21,12 +22,13 @@ import { ScrollRestoration, useNavigation, } from 'react-router'; -import {useState} from 'react'; -import type { Route } from './+types/root'; +import { Spinner } from '~/ui/spinner'; +import AppLayout from '~/ui/layout'; + +import '@aliceo2/web-ui/Frontend/css/src/bootstrap.css'; import './app.css'; -import '@aliceo2/web-ui/Frontend/css/src/bootstrap.css' -import {Spinner} from '~/ui/spinner'; -import AppLayout from '~/ui/layout' +import './styles/components-styles.css' +import './styles/ui-styles.css' export function Layout({ children }: { children: React.ReactNode }) { const { state } = useNavigation(); @@ -41,7 +43,7 @@ export function Layout({ children }: { children: React.ReactNode }) { - {children} + {children} @@ -55,7 +57,7 @@ export default function App() { } export function HydrateFallback() { - return + return ; } export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { diff --git a/Tokenization/webapp/app/routes.ts b/Tokenization/webapp/app/routes.ts index 3fa6ef612..fdfcc2f6f 100644 --- a/Tokenization/webapp/app/routes.ts +++ b/Tokenization/webapp/app/routes.ts @@ -10,14 +10,14 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ import { type RouteConfig, index, route, prefix } from '@react-router/dev/routes'; export default [ - index('routes/home.tsx'), - ...prefix('tokens', [ - index('routes/tokens/overview.tsx'), - route(':tokenId', 'routes/tokens/details.tsx'), - ]), + index('routes/home.tsx'), + ...prefix('tokens', [ + index('routes/tokens/overview.tsx'), + route(':tokenId', 'routes/tokens/details.tsx'), + ]), ] satisfies RouteConfig; diff --git a/Tokenization/webapp/app/routes/home.tsx b/Tokenization/webapp/app/routes/home.tsx index 736517114..bfc99ffb1 100644 --- a/Tokenization/webapp/app/routes/home.tsx +++ b/Tokenization/webapp/app/routes/home.tsx @@ -10,19 +10,24 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ import { Link } from 'react-router'; + import { useSetHeader } from '~/ui/header/headerContext'; +/** + * Home page component for the Tokenization Admin Interface. + * Sets the page header and provides navigation to the tokens overview. + */ export default function Home() { const { setHeaderContent } = useSetHeader(); setHeaderContent('Tokenization Admin Interface'); - + return <> -

Welcome to (dummy) Tokenization GUI!

- Tokens overview +

Welcome to (dummy) Tokenization GUI!

+ Tokens overview ; } diff --git a/Tokenization/webapp/app/routes/tokens/details.tsx b/Tokenization/webapp/app/routes/tokens/details.tsx index 6ca75cf18..8be896322 100644 --- a/Tokenization/webapp/app/routes/tokens/details.tsx +++ b/Tokenization/webapp/app/routes/tokens/details.tsx @@ -10,52 +10,77 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ -import type {Route} from './+types/details'; -import type {Token} from '../../components/tokens/token'; -import {Await, data, Link} from 'react-router'; -import {Suspense} from 'react'; -import type {Log} from '~/components/logs/log'; -import {TokenLogs} from '~/components/tokens/token-logs'; -import {Spinner} from '~/ui/spinner'; +import type { Route } from './+types/details'; +import { Await, data, Link } from 'react-router'; +import { Suspense } from 'react'; +import type { Token } from '../../components/tokens/token'; +import type { Log } from '~/components/logs/log'; +import { TokenLogs } from '~/components/tokens/token-logs'; +import { Spinner } from '~/ui/spinner'; + +/** + * Fetches a token by its ID from the API. + * + * @param tokenId - The ID of the token to fetch + * @returns Promise that resolves to the token data + */ const getToken = async (tokenId: number): Promise => { - const response = await fetch(`http://localhost:8080/api/tokens/${tokenId}`); - if (!response.ok) { - throw data(`Failed to fetch token with token id ${tokenId}`, {status: response.status}); - } - return response.json(); -} + const response = await fetch(`/api/tokens/${tokenId}`); + if (!response.ok) { + throw data(`Failed to fetch token with token id ${tokenId}`, { status: response.status }); + } + return response.json(); +}; +/** + * Fetches logs associated with a token by its ID from the API. + * + * @param tokenId - The ID of the token to fetch logs for + * @returns Promise that resolves to an array of log entries + */ const getLogs = async (tokenId: number): Promise => { - const response = await fetch(`http://localhost:8080/api/tokens/${tokenId}/logs`); - if (!response.ok) { - throw data(`Failed to fetch logs related to token with token id ${tokenId}`, {status: response.status}); - } - return response.json(); -} + const response = await fetch(`/api/tokens/${tokenId}/logs`); + if (!response.ok) { + throw data(`Failed to fetch logs related to token with token id ${tokenId}`, { status: response.status }); + } + return response.json(); +}; -export const clientLoader = async ({params}: Route.ClientLoaderArgs): Promise<{ token: Token, logs: Promise }> => { - // Normally we would check that run number is a number... - const tokenId = parseInt(params.tokenId, 10); +/** + * Client loader that fetches token and logs data for the details page. + * + * @param params - Route parameters containing the token ID + * @returns Object with token data and logs promise + */ +export const clientLoader = async ({ params }: Route.ClientLoaderArgs): Promise<{ token: Token; logs: Promise }> => { + // Normally we would check that run number is a number... + const tokenId = parseInt(params.tokenId, 10); - const logs = getLogs(tokenId); - const token = await getToken(tokenId); + const logs = getLogs(tokenId); + const token = await getToken(tokenId); - return {token, logs}; -} + return { token, logs }; +}; -export default function Details({loaderData: {token, logs}}: Route.ComponentProps) { - return <> -

Token {token.tokenId} details

-

The token has a {token.validity} validity.

-

Logs

- }> - Failed to load the logs}> - {(logs) => } - - - Back to overview - ; +/** + * Token details page component that displays token information and associated logs. + * + * @param props.loaderData.token - The token data + * @param props.loaderData.logs - Promise that resolves to the logs array + */ +export default function Details({ loaderData: { token, logs } }: Route.ComponentProps) { + return <> +

Token {token.tokenId} details

+

The token has a {token.validity} validity.

+

Logs

+ }> + Failed to load the logs}> + {(logs) => } + + + Back to overview + ; } diff --git a/Tokenization/webapp/app/routes/tokens/overview.tsx b/Tokenization/webapp/app/routes/tokens/overview.tsx index 3ba6c1039..fe0d4268d 100644 --- a/Tokenization/webapp/app/routes/tokens/overview.tsx +++ b/Tokenization/webapp/app/routes/tokens/overview.tsx @@ -10,65 +10,80 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ - -import type { Token } from '../../components/tokens/token'; + */ import type { Route } from './+types/overview'; +import type { Token } from '../../components/tokens/token'; -import {Link} from 'react-router'; +import { Link } from 'react-router'; import { useState } from 'react'; -import {Box, Tabs, Tab} from '@mui/material'; +import { Tab } from '@mui/material'; import { useSetHeader } from '~/ui/header/headerContext'; +import { TabsNavbar } from '~/ui/navbar'; -import { TabsNavbar } from '~/ui/navbar' - +/** + * Client loader that fetches all tokens from the API. + * + * @returns Promise that resolves to an array of tokens + */ export const clientLoader = async (): Promise => { - const response = await fetch('http://localhost:8080/api/tokens'); - if (!response.ok) { - throw new Error('An error occurred!'); - } - return response.json(); -} + const response = await fetch('/api/tokens'); + if (!response.ok) { + throw new Error('An error occurred!'); + } + return response.json(); +}; // Will be changed in next PR -function TokenTable({tokens}: {tokens: Token[]}) { - return - - - - - - - - {tokens.map((token: Token) => - - - )} - -
IDvalidity
{token.tokenId}{token.validity}
+/** + * Table component that displays a list of tokens with their ID and validity. + * Token IDs are clickable links that navigate to the token details page. + * + * @param tokens - Array of tokens to display + */ +function TokenTable({ tokens }: { tokens: Token[] }) { + return + + + + + + + + {tokens.map((token: Token) => + + + )} + +
IDvalidity
{token.tokenId}{token.validity}
; } -export default function Overview({loaderData: tokens}: Route.ComponentProps) { - - const { setHeaderContent } = useSetHeader() - setHeaderContent('Tokens') +/** + * Tokens overview page component with tabbed interface. + * Displays a list of tokens and provides a placeholder for token creation. + * + * @param loaderData - Array of tokens loaded by the client loader + */ +export default function Overview({ loaderData: tokens }: Route.ComponentProps) { + + const { setHeaderContent } = useSetHeader(); + setHeaderContent('Tokens'); - const [ tabIndex, setTabIndex ] = useState(0) + const [tabIndex, setTabIndex] = useState(0); - return
- - - - + return
+ + + + - { - tabIndex == 0 ? - : -
-

Create Token

-

Form to create a new token will go here.

-
- } -
+ { + tabIndex == 0 ? + : +
+

Create Token

+

Form to create a new token will go here.

+
+ } +
; } diff --git a/Tokenization/webapp/app/ui/layout.css b/Tokenization/webapp/app/styles/components-styles.css similarity index 82% rename from Tokenization/webapp/app/ui/layout.css rename to Tokenization/webapp/app/styles/components-styles.css index 524b9701c..e318f84e6 100644 --- a/Tokenization/webapp/app/ui/layout.css +++ b/Tokenization/webapp/app/styles/components-styles.css @@ -10,10 +10,5 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ - -.container { - display: grid; - grid-template-rows: 1fr 3fr 1.3fr; - grid-template-columns: 0.575fr 3fr; -} \ No newline at end of file + */ + \ No newline at end of file diff --git a/Tokenization/webapp/app/styles/ui-styles.css b/Tokenization/webapp/app/styles/ui-styles.css new file mode 100644 index 000000000..ed368fe14 --- /dev/null +++ b/Tokenization/webapp/app/styles/ui-styles.css @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + + .container { + display: grid; + grid-template-rows: 1fr 3fr 1.3fr; + grid-template-columns: 0.575fr 3fr; +} + +.header-1 { + justify-self: stretch; + grid-column: span 2; +} + +.sidebar-1 { + border-radius: 2% 0 0 2% ; + grid-row: span 2; +} + diff --git a/Tokenization/webapp/app/ui/header/header.tsx b/Tokenization/webapp/app/ui/header/header.tsx index 2b9bd8d1d..651cca851 100644 --- a/Tokenization/webapp/app/ui/header/header.tsx +++ b/Tokenization/webapp/app/ui/header/header.tsx @@ -10,37 +10,38 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ + +import { Link } from 'react-router'; -import {IconHome, IconCog} from '../icon' -import { Link } from 'react-router' +import { IconHome, IconCog } from '../icon'; /** * AppHeader * * Displays the application header with navigation icons (home, settings) and a customizable title. - * + * @param headerContent.headerContent * @param headerContent Optional string to display as the header title. */ -export function AppHeader({headerContent}: {headerContent?: string}) { - return ( -
- - -
- -
- - -
- -
- - -
-

{headerContent ? headerContent: "Tokenization Admin Interface"}

-
+export function AppHeader({ headerContent }: { headerContent?: string }) { + return ( +
+ +
+
- ) -} \ No newline at end of file + + +
+ +
+ + +
+

{headerContent ?? 'Tokenization Admin Interface'}

+
+ +
+ ); +} diff --git a/Tokenization/webapp/app/ui/header/headerContext.tsx b/Tokenization/webapp/app/ui/header/headerContext.tsx index 4f2505c8e..467b8a352 100644 --- a/Tokenization/webapp/app/ui/header/headerContext.tsx +++ b/Tokenization/webapp/app/ui/header/headerContext.tsx @@ -10,30 +10,28 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ - - - + */ import { createContext, useContext } from 'react'; - /** * HeaderContext * * React context providing the setHeaderContent function for updating the header content * from any component within the provider tree. */ -export const HeaderContext = createContext({ - setHeaderContent: (_: string) => {} +export const HeaderContext = createContext({ + /** + * Updates headers content + */ + setHeaderContent: (_: string) => {}, }); /** - * useSetHeader + * UseSetHeader * * Custom React hook to access the HeaderContext. * Allows components to call setHeaderContent to update the header text. - * - * @returns The context value containing setHeaderContent. + * @return The context value containing setHeaderContent. */ export const useSetHeader = () => useContext(HeaderContext); diff --git a/Tokenization/webapp/app/ui/icon.tsx b/Tokenization/webapp/app/ui/icon.tsx index 4bb1914a3..b82ed443f 100644 --- a/Tokenization/webapp/app/ui/icon.tsx +++ b/Tokenization/webapp/app/ui/icon.tsx @@ -10,229 +10,227 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ import type { PropsWithChildren } from 'react'; interface IconContainerProps { - className?: string; + className?: string; } -export const IconContainer = ({children, className}: PropsWithChildren) => { - return - {children} - -} +export const IconContainer = ({ children, className }: PropsWithChildren) => + {children} +; -export const IconAccountLogin = () => -export const IconAccountLogout = () => -export const IconActionRedo = () => -export const IconActionUndo = () => -export const IconAlignCenter = () => -export const IconAlignLeft = () => -export const IconAlignRight = () => -export const IconArrowCircleBottom = () => -export const IconArrowCircleLeft = () => -export const IconArrowCircleRight = () => -export const IconArrowCircleTop = () => -export const IconArrowBottom = () => -export const IconArrowLeft = () => -export const IconArrowRight = () => -export const IconArrowTop = () => -export const IconArrowThickBottom = () => -export const IconArrowThickLeft = () => -export const IconArrowThickRight = () => -export const IconArrowThickTop = () => -export const IconAudioSpectrum = () => -export const IconAudio = () => -export const IconBadge = () => -export const IconBan = () => -export const IconBarChart = () => -export const IconBasket = () => -export const IconBatteryEmpty = () => -export const IconBatteryFull = () => -export const IconBeaker = () => -export const IconBell = () => -export const IconBold = () => -export const IconBolt = () => -export const IconBook = () => -export const IconBookmark = () => -export const IconBox = () => -export const IconBriefcase = () => -export const IconBrowser = () => -export const IconBrush = () => -export const IconBug = () => -export const IconBullhorn = () => -export const IconCalculator = () => -export const IconCalendar = () => -export const IconCameraSlr = () => -export const IconCaretBottom = () => -export const IconCaretLeft = () => -export const IconCaretRight = () => -export const IconCaretTop = () => -export const IconChat = () => -export const IconCheck = () => -export const IconChevronBottom = () => -export const IconChevronLeft = () => -export const IconChevronRight = () => -export const IconChevronTop = () => -export const IconCircleCheck = () => -export const IconCircleX = () => -export const IconClipboard = () => -export const IconClock = () => -export const IconCloudDownload = () => -export const IconCloudUpload = () => -export const IconCloud = () => -export const IconCode = () => -export const IconCog = () => -export const IconCollapseDown = () => -export const IconCollapseLeft = () => -export const IconCollapseRight = () => -export const IconCollapseUp = () => -export const IconCommand = () => -export const IconCommentSquare = () => -export const IconCompass = () => -export const IconContrast = () => -export const IconCopywriting = () => -export const IconCreditCard = () => -export const IconCrop = () => -export const IconDashboard = () => -export const IconDataTransferDownload = () => -export const IconDataTransferUpload = () => -export const IconDelete = () => -export const IconDial = () => -export const IconDocument = () => -export const IconDoubleQuoteSansLeft = () => -export const IconDoubleQuoteSansRight = () => -export const IconDoubleQuoteSerifLeft = () => -export const IconDoubleQuoteSerifRight = () => -export const IconDroplet = () => -export const IconEject = () => -export const IconElevator = () => -export const IconEllipses = () => -export const IconEnvelopeClosed = () => -export const IconEnvelopeOpen = () => -export const IconExcerpt = () => -export const IconExpandDown = () => -export const IconExpandLeft = () => -export const IconExpandRight = () => -export const IconExpandUp = () => -export const IconExternalLink = () => -export const IconEye = () => -export const IconEyedropper = () => -export const IconFile = () => -export const IconFire = () => -export const IconFlag = () => -export const IconFlash = () => -export const IconFolder = () => -export const IconFork = () => -export const IconFullscreenEnter = () => -export const IconFullscreenExit = () => -export const IconGlobe = () => -export const IconGraph = () => -export const IconGridFourUp = () => -export const IconGridThreeUp = () => -export const IconGridTwoUp = () => -export const IconHardDrive = () => -export const IconHeader = () => -export const IconHeadphones = () => -export const IconHeart = () => -export const IconHome = () => -export const ImagE = () => -export const InboX = () => -export const InfiNity = () => -export const Info = () => -export const ItalIc = () => -export const IconJustifyCenter = () => -export const IconJustifyLeft = () => -export const IconJustifyRight = () => -export const IconKey = () => -export const IconLaptop = () => -export const IconLayers = () => -export const IconLinkBroken = () => -export const IconLinkIntact = () => -export const IconListRich = () => -export const IconList = () => -export const IconLocation = () => -export const IconLockLocked = () => -export const IconLockUnlocked = () => -export const IconLoopCircular = () => -export const IconLoopSquare = () => -export const IconLoop = () => -export const IconMagnifyingGlass = () => -export const IconMapMarker = () => -export const IconMap = () => -export const IconMediaPause = () => -export const IconMediaPlay = () => -export const IconMediaRecord = () => -export const IconMediaSkipBackward = () => -export const IconMediaSkipForward = () => -export const IconMediaStepBackward = () => -export const IconMediaStepForward = () => -export const IconMediaStop = () => -export const IconMedicalCross = () => -export const IconMenu = () => -export const IconMicrophone = () => -export const IconMinus = () => -export const IconMonitor = () => -export const IconMoon = () => -export const IconMove = () => -export const IconMusicalNote = () => -export const IconPaperclip = () => -export const IconPencil = () => -export const IconPeople = () => -export const IconPerson = () => -export const IconPhone = () => -export const IconPieChart = () => -export const IconPin = () => -export const IconPlayCircle = () => -export const IconPlus = () => -export const IconPowerStandby = () => -export const IconPrint = () => -export const IconProject = () => -export const IconPulse = () => -export const IconPuzzlePiece = () => -export const IconRandom = () => -export const IconReload = () => -export const IconResizeBoth = () => -export const IconResizeHeight = () => -export const IconResizeWidth = () => -export const IconRssAlt = () => -export const IconRss = () => -export const IconScript = () => -export const IconShareBoxed = () => -export const IconShare = () => -export const IconShield = () => -export const IconSignal = () => -export const IconSignpost = () => -export const IconSortAscending = () => -export const IconSortDescending = () => -export const IconSpreadsheet = () => -export const IconStar = () => -export const IconSun = () => -export const IconTablet = () => -export const IconTag = () => -export const IconTags = () => -export const IconTarget = () => -export const IconTask = () => -export const IconTerminal = () => -export const IconText = () => -export const IconThumbDown = () => -export const IconThumbUp = () => -export const IconTimer = () => -export const IconTransfer = () => -export const IconTrash = () => -export const IconUnderline = () => -export const IconVerticalAlignBottom = () => -export const IconVerticalAlignCenter = () => -export const IconVerticalAlignTop = () => -export const IconVideo = () => -export const IconVolumeHigh = () => -export const IconVolumeLow = () => -export const IconVolumeOff = () => -export const IconWarning = () => -export const IconWifi = () => -export const IconWrench = () => -export const IconX = () => -export const IconZoomIn = () => -export const IconZoomOut = () => +export const IconAccountLogin = () => ; +export const IconAccountLogout = () => ; +export const IconActionRedo = () => ; +export const IconActionUndo = () => ; +export const IconAlignCenter = () => ; +export const IconAlignLeft = () => ; +export const IconAlignRight = () => ; +export const IconArrowCircleBottom = () => ; +export const IconArrowCircleLeft = () => ; +export const IconArrowCircleRight = () => ; +export const IconArrowCircleTop = () => ; +export const IconArrowBottom = () => ; +export const IconArrowLeft = () => ; +export const IconArrowRight = () => ; +export const IconArrowTop = () => ; +export const IconArrowThickBottom = () => ; +export const IconArrowThickLeft = () => ; +export const IconArrowThickRight = () => ; +export const IconArrowThickTop = () => ; +export const IconAudioSpectrum = () => ; +export const IconAudio = () => ; +export const IconBadge = () => ; +export const IconBan = () => ; +export const IconBarChart = () => ; +export const IconBasket = () => ; +export const IconBatteryEmpty = () => ; +export const IconBatteryFull = () => ; +export const IconBeaker = () => ; +export const IconBell = () => ; +export const IconBold = () => ; +export const IconBolt = () => ; +export const IconBook = () => ; +export const IconBookmark = () => ; +export const IconBox = () => ; +export const IconBriefcase = () => ; +export const IconBrowser = () => ; +export const IconBrush = () => ; +export const IconBug = () => ; +export const IconBullhorn = () => ; +export const IconCalculator = () => ; +export const IconCalendar = () => ; +export const IconCameraSlr = () => ; +export const IconCaretBottom = () => ; +export const IconCaretLeft = () => ; +export const IconCaretRight = () => ; +export const IconCaretTop = () => ; +export const IconChat = () => ; +export const IconCheck = () => ; +export const IconChevronBottom = () => ; +export const IconChevronLeft = () => ; +export const IconChevronRight = () => ; +export const IconChevronTop = () => ; +export const IconCircleCheck = () => ; +export const IconCircleX = () => ; +export const IconClipboard = () => ; +export const IconClock = () => ; +export const IconCloudDownload = () => ; +export const IconCloudUpload = () => ; +export const IconCloud = () => ; +export const IconCode = () => ; +export const IconCog = () => ; +export const IconCollapseDown = () => ; +export const IconCollapseLeft = () => ; +export const IconCollapseRight = () => ; +export const IconCollapseUp = () => ; +export const IconCommand = () => ; +export const IconCommentSquare = () => ; +export const IconCompass = () => ; +export const IconContrast = () => ; +export const IconCopywriting = () => ; +export const IconCreditCard = () => ; +export const IconCrop = () => ; +export const IconDashboard = () => ; +export const IconDataTransferDownload = () => ; +export const IconDataTransferUpload = () => ; +export const IconDelete = () => ; +export const IconDial = () => ; +export const IconDocument = () => ; +export const IconDoubleQuoteSansLeft = () => ; +export const IconDoubleQuoteSansRight = () => ; +export const IconDoubleQuoteSerifLeft = () => ; +export const IconDoubleQuoteSerifRight = () => ; +export const IconDroplet = () => ; +export const IconEject = () => ; +export const IconElevator = () => ; +export const IconEllipses = () => ; +export const IconEnvelopeClosed = () => ; +export const IconEnvelopeOpen = () => ; +export const IconExcerpt = () => ; +export const IconExpandDown = () => ; +export const IconExpandLeft = () => ; +export const IconExpandRight = () => ; +export const IconExpandUp = () => ; +export const IconExternalLink = () => ; +export const IconEye = () => ; +export const IconEyedropper = () => ; +export const IconFile = () => ; +export const IconFire = () => ; +export const IconFlag = () => ; +export const IconFlash = () => ; +export const IconFolder = () => ; +export const IconFork = () => ; +export const IconFullscreenEnter = () => ; +export const IconFullscreenExit = () => ; +export const IconGlobe = () => ; +export const IconGraph = () => ; +export const IconGridFourUp = () => ; +export const IconGridThreeUp = () => ; +export const IconGridTwoUp = () => ; +export const IconHardDrive = () => ; +export const IconHeader = () => ; +export const IconHeadphones = () => ; +export const IconHeart = () => ; +export const IconHome = () => ; +export const ImagE = () => ; +export const InboX = () => ; +export const InfiNity = () => ; +export const Info = () => ; +export const ItalIc = () => ; +export const IconJustifyCenter = () => ; +export const IconJustifyLeft = () => ; +export const IconJustifyRight = () => ; +export const IconKey = () => ; +export const IconLaptop = () => ; +export const IconLayers = () => ; +export const IconLinkBroken = () => ; +export const IconLinkIntact = () => ; +export const IconListRich = () => ; +export const IconList = () => ; +export const IconLocation = () => ; +export const IconLockLocked = () => ; +export const IconLockUnlocked = () => ; +export const IconLoopCircular = () => ; +export const IconLoopSquare = () => ; +export const IconLoop = () => ; +export const IconMagnifyingGlass = () => ; +export const IconMapMarker = () => ; +export const IconMap = () => ; +export const IconMediaPause = () => ; +export const IconMediaPlay = () => ; +export const IconMediaRecord = () => ; +export const IconMediaSkipBackward = () => ; +export const IconMediaSkipForward = () => ; +export const IconMediaStepBackward = () => ; +export const IconMediaStepForward = () => ; +export const IconMediaStop = () => ; +export const IconMedicalCross = () => ; +export const IconMenu = () => ; +export const IconMicrophone = () => ; +export const IconMinus = () => ; +export const IconMonitor = () => ; +export const IconMoon = () => ; +export const IconMove = () => ; +export const IconMusicalNote = () => ; +export const IconPaperclip = () => ; +export const IconPencil = () => ; +export const IconPeople = () => ; +export const IconPerson = () => ; +export const IconPhone = () => ; +export const IconPieChart = () => ; +export const IconPin = () => ; +export const IconPlayCircle = () => ; +export const IconPlus = () => ; +export const IconPowerStandby = () => ; +export const IconPrint = () => ; +export const IconProject = () => ; +export const IconPulse = () => ; +export const IconPuzzlePiece = () => ; +export const IconRandom = () => ; +export const IconReload = () => ; +export const IconResizeBoth = () => ; +export const IconResizeHeight = () => ; +export const IconResizeWidth = () => ; +export const IconRssAlt = () => ; +export const IconRss = () => ; +export const IconScript = () => ; +export const IconShareBoxed = () => ; +export const IconShare = () => ; +export const IconShield = () => ; +export const IconSignal = () => ; +export const IconSignpost = () => ; +export const IconSortAscending = () => ; +export const IconSortDescending = () => ; +export const IconSpreadsheet = () => ; +export const IconStar = () => ; +export const IconSun = () => ; +export const IconTablet = () => ; +export const IconTag = () => ; +export const IconTags = () => ; +export const IconTarget = () => ; +export const IconTask = () => ; +export const IconTerminal = () => ; +export const IconText = () => ; +export const IconThumbDown = () => ; +export const IconThumbUp = () => ; +export const IconTimer = () => ; +export const IconTransfer = () => ; +export const IconTrash = () => ; +export const IconUnderline = () => ; +export const IconVerticalAlignBottom = () => ; +export const IconVerticalAlignCenter = () => ; +export const IconVerticalAlignTop = () => ; +export const IconVideo = () => ; +export const IconVolumeHigh = () => ; +export const IconVolumeLow = () => ; +export const IconVolumeOff = () => ; +export const IconWarning = () => ; +export const IconWifi = () => ; +export const IconWrench = () => ; +export const IconX = () => ; +export const IconZoomIn = () => ; +export const IconZoomOut = () => ; diff --git a/Tokenization/webapp/app/ui/layout.tsx b/Tokenization/webapp/app/ui/layout.tsx index c9f07ee55..58eca9faf 100644 --- a/Tokenization/webapp/app/ui/layout.tsx +++ b/Tokenization/webapp/app/ui/layout.tsx @@ -10,7 +10,7 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ /** * Layout @@ -18,32 +18,43 @@ * Main layout component for the application. * Provides the header, sidebar, and main content area. * Also sets up the HeaderContext to allow child components to update the header content. - * * @param state The current loading state of the application (e.g., 'loading' or ready). * @param children The main content to be rendered inside the layout. - * - * @returns The application layout with header, sidebar, and content area. + * @return The application layout with header, sidebar, and content area. */ -import {useState} from 'react' -import { AppHeader } from './header/header' -import { HeaderContext } from './header/headerContext' -import { AppSidebar } from './sidebar' -import { Spinner } from './spinner' -import './layout.css' +import { useState } from 'react'; + +import { AppHeader } from './header/header'; +import { HeaderContext } from './header/headerContext'; +import { AppSidebar } from './sidebar'; +import { Spinner } from './spinner'; -export default function({state, children}: any) { +interface LayoutArgs { + state: string; + children: React.ReactNode; +} + +/** + * Component provides layout for the applicatio + * + * @param state - refers to state of the whole website if data are loaded its - loading + * @param children - elements to render inside layout component + */ +export default function Layout({ state, children }: LayoutArgs) { - const [headerContent, setHeaderContent] = useState('Tokenization Admin Interface'); + const [headerContent, setHeaderContent] = useState('Tokenization Admin Interface'); - return -
- - -
- {state === 'loading' ? : children} -
-
-
+ return ( + +
+ + +
+ {state === 'loading' ? : children} +
+
+
+ ); -} \ No newline at end of file +} diff --git a/Tokenization/webapp/app/ui/navbar.tsx b/Tokenization/webapp/app/ui/navbar.tsx index 858de2734..d996641a2 100644 --- a/Tokenization/webapp/app/ui/navbar.tsx +++ b/Tokenization/webapp/app/ui/navbar.tsx @@ -10,36 +10,43 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ -import { Box, Tabs } from '@mui/material'; import React from 'react'; +import { Box, Tabs } from '@mui/material'; + +interface TabsNavbarArguments { + tabIndex: number; + setTabIndex: (index: number) => void; + children?: React.ReactNode; +} /** * TabsNavbar * * A reusable component that renders a Material-UI Tabs navigation bar. * Allows switching between different sections using tab buttons. - * * @param tabIndex The currently selected tab index. * @param setTabIndex Function to update the selected tab index. * @param children Tab (@mui/material) components (usually ) to be rendered inside the navigation bar. */ -export function TabsNavbar({tabIndex, setTabIndex, children}: {tabIndex: number, setTabIndex: (index: number) => void, children?: React.ReactNode}) { - - const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { - setTabIndex(newValue) - } - - return - - {children} - - -} \ No newline at end of file +export function TabsNavbar({ tabIndex, setTabIndex, children }: TabsNavbarArguments) { + + const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { + setTabIndex(newValue); + }; + + return ( + + + {children} + + + ); +} diff --git a/Tokenization/webapp/app/ui/sidebar.tsx b/Tokenization/webapp/app/ui/sidebar.tsx index 2d2e75d8b..08510ed92 100644 --- a/Tokenization/webapp/app/ui/sidebar.tsx +++ b/Tokenization/webapp/app/ui/sidebar.tsx @@ -10,14 +10,11 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ +import type { NavLinkProps } from 'react-router'; import { NavLink } from 'react-router'; import Button from '@mui/material/Button'; -import type {NavLinkProps} from 'react-router'; - - - /** * StyledNavLink @@ -25,23 +22,22 @@ import type {NavLinkProps} from 'react-router'; * A wrapper component that renders a Material-UI Button styled as a navigation link. * It uses NavLink from react-router to determine if the link is active and applies * the 'contained' variant for the active route and 'outlined' for inactive routes. - * + * @param children.children * @param children The content to display inside the button. * @param to The target route path. + * @param children.to */ -const StyledNavLink = ({children, to}: NavLinkProps) => { - return - {({isActive}) => ( - - )} - -} +const StyledNavLink = ({ children, to }: NavLinkProps) => + {({ isActive }) => ( + + )} +; /** * AppSidebar @@ -49,14 +45,12 @@ const StyledNavLink = ({children, to}: NavLinkProps) => { * The sidebar navigation component for the application. * Displays navigation buttons for different sections using StyledNavLink. * Styled with a light gray background and rounded left corners. - * - * @returns The sidebar navigation JSX element. + * @return The sidebar navigation JSX element. */ -export const AppSidebar = () => { - return -} +export const AppSidebar = () => + ; diff --git a/Tokenization/webapp/app/ui/spinner.tsx b/Tokenization/webapp/app/ui/spinner.tsx index 30c30194b..0d8f87ab6 100644 --- a/Tokenization/webapp/app/ui/spinner.tsx +++ b/Tokenization/webapp/app/ui/spinner.tsx @@ -10,33 +10,37 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ interface SpinnerProps { - /** - * Size of the spinner in rem, default 10 - */ - size?: number; + /** + * Size of the spinner in rem, default 10 + */ + size?: number; - /** - * Horizontal alignment of the spinner, default 'center' - */ - align?: 'left' | 'center' | 'right'; + /** + * Horizontal alignment of the spinner, default 'center' + */ + align?: 'left' | 'center' | 'right'; } -export const Spinner = ({size = 10, align = 'center'}: SpinnerProps) => { - return
-
-
-
-
-
-
-
-
-
-
+/** + * Renders animated spinner + * + * @param size + * @param align + */ +export const Spinner = ({ size = 10, align = 'center' }: SpinnerProps) =>
+
+
+
+
+
+
+
+
+
-
-} +
+
; diff --git a/Tokenization/webapp/eslint.config.js b/Tokenization/webapp/eslint.config.js new file mode 100644 index 000000000..d0b762f00 --- /dev/null +++ b/Tokenization/webapp/eslint.config.js @@ -0,0 +1,223 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import globals from 'globals'; +import pluginJs from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import pluginReact from 'eslint-plugin-react'; +import pluginReactHooks from 'eslint-plugin-react-hooks'; +import jsdoc from 'eslint-plugin-jsdoc'; +import stylisticTs from '@stylistic/eslint-plugin-ts'; +import stylisticJs from '@stylistic/eslint-plugin-js'; + +const licenseHeader = `/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */`; + +const licenseHeaderRule = { + meta: { + type: 'layout', + docs: { + description: 'Require license header at the top of files', + category: 'Stylistic Issues', + }, + fixable: 'code', + schema: [], + }, + create(context) { + return { + Program(node) { + const sourceCode = context.getSourceCode(); + const text = sourceCode.getText(); + + if (text.includes('@license')) { + return; + } + + context.report({ + node, + message: 'Missing license header', + fix(fixer) { + return fixer.insertTextBefore(node, licenseHeader + '\n\n'); + }, + }); + }, + }; + }, +}; + +export default [ + { + ignores: [ + 'test/', + 'tests/', + 'node_modules/', + 'build/', + 'dist/', + '.react-router/', + 'database/data/', + 'lib/public/assets/', + 'cpp-api-client/', + 'tmp/', + '.nyc_output/', + 'app/ui/icon.tsx', + 'app/root.tsx', + ], + }, + + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + pluginReact.configs.flat.recommended, + + { + files: ['**/*.{js,mjs,cjs,ts,tsx,jsx}'], + + plugins: { + '@typescript-eslint': tseslint.plugin, + 'react': pluginReact, + 'react-hooks': pluginReactHooks, + 'jsdoc': jsdoc, + '@stylistic/ts': stylisticTs, + '@stylistic/js': stylisticJs, + 'custom': { + rules: { + 'license-header': licenseHeaderRule, + }, + }, + }, + + languageOptions: { + parser: tseslint.parser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + project: './tsconfig.json', + }, + globals: { + ...globals.browser, + ...globals.node, + ...globals.es2021, + React: 'readonly', + }, + }, + + settings: { + react: { + version: 'detect', + }, + jsdoc: { + mode: 'typescript', + tagNamePreference: { + returns: 'return', + }, + }, + }, + + rules: { + // === CUSTOM RULES === + 'custom/license-header': 'error', + + // === TYPESCRIPT SPECIFIC RULES === + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + ignoreRestSiblings: true, + }, + ], + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/prefer-nullish-coalescing': 'error', + '@typescript-eslint/prefer-optional-chain': 'error', + '@typescript-eslint/no-non-null-assertion': 'warn', + '@typescript-eslint/consistent-type-imports': 'error', + + // === REACT SPECIFIC RULES === + 'react/react-in-jsx-scope': 'off', + 'react/prop-types': 'off', + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + + // === GENERAL CODE QUALITY === + 'arrow-body-style': ['error', 'as-needed'], + 'curly': 'error', + 'no-console': 'error', + 'no-implicit-coercion': 'error', + 'no-return-assign': 'error', + 'no-var': 'error', + 'one-var': ['error', 'never'], + 'prefer-const': 'error', + 'prefer-destructuring': 'warn', + 'prefer-template': 'error', + 'radix': 'error', + + // === COMMENTS AND DOCUMENTATION === + 'capitalized-comments': ['error', 'always'], + 'jsdoc/require-description': 'error', + 'jsdoc/require-returns': 'off', + 'jsdoc/require-jsdoc': [ + 'error', + { + require: { + FunctionDeclaration: true, + MethodDefinition: true, + ClassDeclaration: true, + ArrowFunctionExpression: false, + FunctionExpression: true, + }, + }, + ], + + // === TYPESCRIPT STYLISTIC RULES (only ones that exist) === + '@stylistic/ts/comma-dangle': ['error', 'always-multiline'], + '@stylistic/ts/comma-spacing': ['error', { before: false, after: true }], + '@stylistic/ts/indent': ['error', 2], + '@stylistic/ts/quotes': ['error', 'single', { avoidEscape: true }], + '@stylistic/ts/semi': 'error', + '@stylistic/ts/space-before-blocks': 'error', + '@stylistic/ts/space-infix-ops': 'error', + '@stylistic/ts/object-curly-spacing': ['error', 'always'], + '@stylistic/ts/keyword-spacing': 'error', + '@stylistic/ts/type-annotation-spacing': 'error', + '@stylistic/ts/member-delimiter-style': 'error', + + // === JAVASCRIPT STYLISTIC RULES === + '@stylistic/js/array-bracket-spacing': ['error', 'never'], + '@stylistic/js/brace-style': ['error', '1tbs'], + '@stylistic/js/no-trailing-spaces': 'error', + '@stylistic/js/eol-last': ['error', 'always'], + '@stylistic/js/max-len': ['error', { code: 145 }], + "@stylistic/js/no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 0 }], + + // === DISABLED RULES === + 'no-magic-numbers': 'off', + 'sort-keys': 'off', + 'sort-imports': 'off', + 'sort-vars': 'off', + }, + }, +]; \ No newline at end of file diff --git a/Tokenization/webapp/package.json b/Tokenization/webapp/package.json index 13f419f83..c15a8433a 100644 --- a/Tokenization/webapp/package.json +++ b/Tokenization/webapp/package.json @@ -9,7 +9,9 @@ "dev": "react-router dev", "start": "react-router-serve ./build/server/index.js", "typecheck": "react-router typegen && tsc", - "docker:typecheck": "docker compose exec webapp npm run typecheck" + "docker:typecheck": "docker compose exec webapp npm run typecheck", + "lint": "eslint app --ignore-pattern 'app/styles' --no-warn-ignored", + "lint:fix": "eslint --fix app --ignore-pattern 'app/styles' --no-warn-ignored" }, "repository": { "type": "git", @@ -39,6 +41,15 @@ "@types/react-dom": "^19.0.1", "typescript": "^5.7.2", "vite": "^6.0.11", - "vite-tsconfig-paths": "^5.1.4" + "vite-tsconfig-paths": "^5.1.4", + "@eslint/js": "^9.28.0", + "@stylistic/eslint-plugin-ts": "^2.10.1", + "@stylistic/eslint-plugin-js": "^2.10.1", + "typescript-eslint": "^8.34.0", + "eslint": "^9.28.0", + "eslint-plugin-jsdoc": "^50.7.1", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.0.0", + "globals": "^16.2.0" } } From 3374b5bf9bd156121afeb49507ac6e68d42820a7 Mon Sep 17 00:00:00 2001 From: Igor Sala Date: Fri, 22 Aug 2025 12:24:33 +0200 Subject: [PATCH 05/47] - Moved test files for UI to webapp folder - Changed a bit configuration for docker-compose - now it copies configuration file and doesnt use volumes - Multi-staged build for creating containers for testing ( downloading dev-dependencies etc.) - commands in package.json that run tests commands in containers --- Tokenization/.dockerignore | 1 + Tokenization/Dockerfile | 53 + Tokenization/docker-compose.test.yml | 49 +- Tokenization/docker-compose.yml | 5 +- .../provisioning/nginx/conf.d/production.conf | 26 + Tokenization/tests/ui/Dockerfile | 19 - Tokenization/tests/ui/package-lock.json | 2111 ---------------- Tokenization/tests/ui/package.json | 10 - Tokenization/tests/ui/test/main.test.js | 23 - Tokenization/webapp/package-lock.json | 2156 ++++++++++++++++- Tokenization/webapp/package.json | 10 +- Tokenization/webapp/tests/main.test.js | 22 + 12 files changed, 2226 insertions(+), 2259 deletions(-) create mode 100644 Tokenization/.dockerignore create mode 100644 Tokenization/Dockerfile create mode 100644 Tokenization/docker/provisioning/nginx/conf.d/production.conf delete mode 100644 Tokenization/tests/ui/Dockerfile delete mode 100644 Tokenization/tests/ui/package-lock.json delete mode 100644 Tokenization/tests/ui/package.json delete mode 100644 Tokenization/tests/ui/test/main.test.js create mode 100644 Tokenization/webapp/tests/main.test.js diff --git a/Tokenization/.dockerignore b/Tokenization/.dockerignore new file mode 100644 index 000000000..40b878db5 --- /dev/null +++ b/Tokenization/.dockerignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/Tokenization/Dockerfile b/Tokenization/Dockerfile new file mode 100644 index 000000000..d311e66a1 --- /dev/null +++ b/Tokenization/Dockerfile @@ -0,0 +1,53 @@ +# ---- Base ---- +FROM node:22-alpine AS base +WORKDIR /var/workspace + +# ---- Dependencies (for production) ---- +FROM base AS dependencies +COPY webapp/package*.json ./ +RUN npm ci --only=production && npm cache clean --force + +# ---- Dev Dependencies (for tests) ---- +FROM base AS dev-dependencies + +# Installs packages required for Puppeteer +RUN apk add --no-cache \ + chromium \ + freetype \ + freetype-dev \ + harfbuzz \ + ca-certificates + +ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true +ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser + +COPY webapp . + +RUN npm --silent install + +# ---- Build ---- +FROM base AS build +COPY webapp/package*.json ./ +RUN npm ci --only=production && npm cache clean --force +COPY webapp . +RUN npm run build + +# ---- Test ---- +FROM dev-dependencies AS test +CMD ["npm", "run", "test"] + +# ---- Coverage ---- +FROM dev-dependencies AS coverage +CMD ["npm", "run", "coverage"] + +# ---- Production ---- +FROM nginx:alpine AS production +COPY --from=build /var/workspace/build/client /usr/share/nginx/html +COPY docker/provisioning/nginx/conf.d/production.conf /etc/nginx/nginx.conf +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] + +FROM nginx:1.27 AS reverse-proxy +COPY ./docker/provisioning/nginx/conf.d/default.conf /etc/nginx/conf.d +EXPOSE 8080 + diff --git a/Tokenization/docker-compose.test.yml b/Tokenization/docker-compose.test.yml index bde82aa38..77d4665d5 100644 --- a/Tokenization/docker-compose.test.yml +++ b/Tokenization/docker-compose.test.yml @@ -1,15 +1,48 @@ services: + install-backend: + image: node:22-alpine + working_dir: /var/workspace + volumes: + - ./backend:/var/workspace + command: ["npm", "install", "--no-save", "--silent"] + + backend: + image: node:22-alpine + working_dir: /var/workspace + volumes: + - ./backend:/var/workspace + command: ["npm", "run", "dev"] + healthcheck: + interval: 1s + test: ["CMD-SHELL", + "node", "-c", + "node --input-type=module -e \"process.exit((await fetch('http://backend:8080/api/healthcheck')).ok === true ? 0 : 1)\""] + depends_on: + install-backend: + condition: service_completed_successfully + + + prod-container: + build: + context: . + dockerfile: Dockerfile + target: production + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/"] + interval: 5s + timeout: 3s + retries: 5 + depends_on: + backend: + condition: service_healthy + ui-tests: build: - context: ./tests/ui + context: . dockerfile: Dockerfile - working_dir: /tests/ui + target: test volumes: - - ./tests/ui:/tests/ui + - ./webapp/tests:/var/workspace/tests depends_on: - reverse-proxy: - condition: service_started - backend: + prod-container: condition: service_healthy - webapp: - condition: service_healthy \ No newline at end of file diff --git a/Tokenization/docker-compose.yml b/Tokenization/docker-compose.yml index b61d5a991..9160c247d 100644 --- a/Tokenization/docker-compose.yml +++ b/Tokenization/docker-compose.yml @@ -44,9 +44,8 @@ services: condition: service_completed_successfully reverse-proxy: - image: nginx:1.27 - volumes: - - ./docker/provisioning/nginx/conf.d/:/etc/nginx/conf.d/ + build: + target: reverse-proxy ports: - "8080:8080" depends_on: diff --git a/Tokenization/docker/provisioning/nginx/conf.d/production.conf b/Tokenization/docker/provisioning/nginx/conf.d/production.conf new file mode 100644 index 000000000..c851013a0 --- /dev/null +++ b/Tokenization/docker/provisioning/nginx/conf.d/production.conf @@ -0,0 +1,26 @@ +events {} + +http { + include mime.types; + default_type application/octet-stream; + sendfile on; + + server { + listen 80; + listen [::]:80; + + server_name localhost; + + root /usr/share/nginx/html; + index index.html; + + location /api { + proxy_pass http://backend:8080; + } + + location / { + try_files $uri /index.html; + } + + } +} diff --git a/Tokenization/tests/ui/Dockerfile b/Tokenization/tests/ui/Dockerfile deleted file mode 100644 index 00d3e51dd..000000000 --- a/Tokenization/tests/ui/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM node:22-slim - -WORKDIR /tests/ui - -# system dependencies for running Chrome -RUN apt-get update && \ - apt-get install -y curl gnupg ca-certificates fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libcups2 libdbus-1-3 libdrm2 libgbm1 libgtk-3-0 libnspr4 libnss3 libx11-xcb1 libxcomposite1 libxdamage1 libxrandr2 xdg-utils libu2f-udev libvulkan1 libxshmfence1 libxss1 --no-install-recommends && \ - curl --location --silent https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \ - sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' && \ - apt-get update && \ - apt-get install -y google-chrome-stable --no-install-recommends && \ - rm -rf /var/lib/apt/lists/* - -COPY package*.json ./ -RUN npm install --silent - -COPY . . - -CMD ["sh", "-c", "npx wait-on http://reverse-proxy:8080 && npm run test"] \ No newline at end of file diff --git a/Tokenization/tests/ui/package-lock.json b/Tokenization/tests/ui/package-lock.json deleted file mode 100644 index 5beabaafb..000000000 --- a/Tokenization/tests/ui/package-lock.json +++ /dev/null @@ -1,2111 +0,0 @@ -{ - "name": "ui", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "devDependencies": { - "mocha": "^11.6.0", - "puppeteer": "^24.10.1" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "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/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@puppeteer/browsers": { - "version": "2.10.5", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.5.tgz", - "integrity": "sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "debug": "^4.4.1", - "extract-zip": "^2.0.1", - "progress": "^2.0.3", - "proxy-agent": "^6.5.0", - "semver": "^7.7.2", - "tar-fs": "^3.0.8", - "yargs": "^17.7.2" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz", - "integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "undici-types": "~7.8.0" - } - }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "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, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/b4a": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", - "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", - "dev": true, - "license": "Apache-2.0" - }, - "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/bare-events": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", - "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", - "dev": true, - "license": "Apache-2.0", - "optional": true - }, - "node_modules/bare-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.5.tgz", - "integrity": "sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4" - }, - "engines": { - "bare": ">=1.16.0" - }, - "peerDependencies": { - "bare-buffer": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } - } - }, - "node_modules/bare-os": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", - "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "engines": { - "bare": ">=1.14.0" - } - }, - "node_modules/bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-os": "^3.0.1" - } - }, - "node_modules/bare-stream": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", - "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "streamx": "^2.21.0" - }, - "peerDependencies": { - "bare-buffer": "*", - "bare-events": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } - } - }, - "node_modules/basic-ftp": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", - "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true, - "license": "ISC" - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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, - "license": "MIT", - "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/chalk/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, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/chromium-bidi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-5.1.0.tgz", - "integrity": "sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "mitt": "^3.0.1", - "zod": "^3.24.1" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/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/cliui/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/cliui/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/cliui/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/cliui/node_modules/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/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, - "license": "MIT", - "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, - "license": "MIT" - }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "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, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/devtools-protocol": { - "version": "0.0.1452169", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1452169.tgz", - "integrity": "sha512-FOFDVMGrAUNp0dDKsAU1TorWJUx2JOU1k9xdgBKKJF3IBh/Uhl2yswG5r3TEAOrCiGY2QRp1e6LVDQrCsTKO4g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "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/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/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-uri": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", - "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "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/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, - "engines": { - "node": ">= 12" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "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-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "dev": true, - "license": "MIT" - }, - "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==", - "dev": true, - "license": "MIT" - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "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/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.6.0.tgz", - "integrity": "sha512-i0JVb+OUBqw63X/1pC3jCyJsqYisgxySBbsQa8TKvefpA1oEnw7JXxXnftfMHRsw7bEEVGRtVlHcDYXBa7FzVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "browser-stdout": "^1.3.1", - "chokidar": "^4.0.1", - "debug": "^4.3.5", - "diff": "^7.0.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^10.4.5", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^9.0.5", - "ms": "^2.1.3", - "picocolors": "^1.1.1", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^9.2.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "dev": true, - "license": "MIT", - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, - "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==", - "dev": true, - "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==", - "dev": true, - "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, - "license": "MIT", - "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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/puppeteer": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.10.1.tgz", - "integrity": "sha512-7T3rfSaaPt5A31VITV5YKQ4wPCCv4aPn8byDaV+9lhDU9v7BWYY4Ncwerw3ZR5mIolrh/PvzGdIDK7yiBth75g==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "2.10.5", - "chromium-bidi": "5.1.0", - "cosmiconfig": "^9.0.0", - "devtools-protocol": "0.0.1452169", - "puppeteer-core": "24.10.1", - "typed-query-selector": "^2.12.0" - }, - "bin": { - "puppeteer": "lib/cjs/puppeteer/node/cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/puppeteer-core": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.10.1.tgz", - "integrity": "sha512-AE6doA9znmEEps/pC5lc9p0zejCdNLR6UBp3EZ49/15Nbvh+uklXxGox7Qh8/lFGqGVwxInl0TXmsOmIuIMwiQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "2.10.5", - "chromium-bidi": "5.1.0", - "debug": "^4.4.1", - "devtools-protocol": "0.0.1452169", - "typed-query-selector": "^2.12.0", - "ws": "^8.18.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "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/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, - "license": "MIT", - "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, - "license": "MIT", - "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/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.5.tgz", - "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^9.0.5", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/streamx": { - "version": "2.22.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", - "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-fifo": "^1.3.2", - "text-decoder": "^1.1.0" - }, - "optionalDependencies": { - "bare-events": "^2.2.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/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/tar-fs": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.10.tgz", - "integrity": "sha512-C1SwlQGNLe/jPNqapK8epDsXME7CAJR5RL3GcE6KWx1d9OUByzoHVcbu1VPI8tevg9H8Alae0AApHHFGzrD5zA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" - } - }, - "node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, - "node_modules/text-decoder": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", - "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "b4a": "^1.6.4" - } - }, - "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==", - "dev": true, - "license": "0BSD" - }, - "node_modules/typed-query-selector": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", - "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", - "dev": true, - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", - "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", - "dev": true, - "license": "MIT", - "optional": true - }, - "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, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/workerpool": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.2.tgz", - "integrity": "sha512-Xz4Nm9c+LiBHhDR5bDLnNzmj6+5F+cyEAWPMkbs2awq/dYazR/efelZzUAjB/y3kNHL+uzkHvxVVpaOfGCPV7A==", - "dev": true, - "license": "Apache-2.0" - }, - "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/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs/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/yargs/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/yargs/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/yargs/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/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "3.25.65", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.65.tgz", - "integrity": "sha512-kMyE2qsXK1p+TAvO7zsf5wMFiCejU3obrUDs9bR1q5CBKykfvp7QhhXrycUylMoOow0iEUSyjLlZZdCsHwSldQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/Tokenization/tests/ui/package.json b/Tokenization/tests/ui/package.json deleted file mode 100644 index e6fc2368e..000000000 --- a/Tokenization/tests/ui/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "scripts": { - "test": "mocha test/**/*.js" - }, - - "devDependencies": { - "mocha": "^11.6.0", - "puppeteer": "^24.10.1" - } -} diff --git a/Tokenization/tests/ui/test/main.test.js b/Tokenization/tests/ui/test/main.test.js deleted file mode 100644 index 9811a9440..000000000 --- a/Tokenization/tests/ui/test/main.test.js +++ /dev/null @@ -1,23 +0,0 @@ -const assert = require('assert'); -const pupeteer = require('puppeteer'); - -describe('Puppeteer test', () => { - it('Pupeteer launch', async () => { - const browser = await pupeteer.launch({ - executablePath: '/usr/bin/google-chrome', - headless: true, - args: [ - '--no-sandbox', - ] - }) - - const page = await browser.newPage(); - - await page.goto('http://reverse-proxy:8080/') - - const headerContent = await page.$eval('header', el => el.textContent) - assert.ok(headerContent.includes('Tokenization')) - - await browser.close() - }); -}) \ No newline at end of file diff --git a/Tokenization/webapp/package-lock.json b/Tokenization/webapp/package-lock.json index 1de631491..6e8eff78f 100644 --- a/Tokenization/webapp/package-lock.json +++ b/Tokenization/webapp/package-lock.json @@ -24,6 +24,8 @@ "@types/node": "^20", "@types/react": "^19.0.1", "@types/react-dom": "^19.0.1", + "mocha": "^10.2.0", + "puppeteer": "^24.9.0", "typescript": "^5.7.2", "vite": "^6.0.11", "vite-tsconfig-paths": "^5.1.4" @@ -1630,6 +1632,196 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "license": "BSD-3-Clause" }, + "node_modules/@puppeteer/browsers": { + "version": "2.10.7", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.7.tgz", + "integrity": "sha512-wHWLkQWBjHtajZeqCB74nsa/X70KheyOhySYBRmVQDJiNj0zjZR/naPCvdWjMhcG1LmjaMV/9WtTo5mpe8qWLw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.1", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.2", + "tar-fs": "^3.1.0", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/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/@puppeteer/browsers/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, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@puppeteer/browsers/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@puppeteer/browsers/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, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@puppeteer/browsers/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, + "license": "MIT" + }, + "node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@puppeteer/browsers/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/@puppeteer/browsers/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@puppeteer/browsers/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/@puppeteer/browsers/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/@puppeteer/browsers/node_modules/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/@puppeteer/browsers/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@puppeteer/browsers/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/@react-router/dev": { "version": "7.6.1", "resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-7.6.1.tgz", @@ -1994,6 +2186,13 @@ "win32" ] }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -2056,6 +2255,17 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "license": "MIT" }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -2069,6 +2279,26 @@ "node": ">= 0.6" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -2095,6 +2325,33 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -2102,18 +2359,45 @@ "dev": true, "license": "MIT" }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "license": "MIT" }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/babel-dead-code-elimination": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.10.tgz", @@ -2149,58 +2433,178 @@ "dev": true, "license": "MIT" }, - "node_modules/bignumber.js": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", - "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", - "license": "MIT", - "engines": { - "node": "*" - } + "node_modules/bare-events": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.6.1.tgz", + "integrity": "sha512-AuTJkq9XmE6Vk0FJVNq5QxETrSA/vKHarWVBG5l/JbdCL1prJemiyJqUS0jrlXO0MftuPq4m3YVYhoNc5+aE/g==", + "dev": true, + "license": "Apache-2.0", + "optional": true }, - "node_modules/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==", - "license": "MIT", + "node_modules/bare-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.2.1.tgz", + "integrity": "sha512-mELROzV0IhqilFgsl1gyp48pnZsaV9xhQapHLDsvn4d4ZTfbFhcghQezl7FTEDNBcGqLUnNI3lUlm6ecrLWdFA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, "dependencies": { - "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" + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } } }, - "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==", + "node_modules/bare-os": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", + "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" } }, - "node_modules/browserslist": { - "version": "4.24.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", - "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", + "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "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" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "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, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/browserslist": { + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" }, { "type": "tidelift", @@ -2225,6 +2629,16 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -2294,6 +2708,19 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001718", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", @@ -2315,6 +2742,72 @@ ], "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, + "license": "MIT", + "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/chalk/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, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk/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, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/chalk/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, + "license": "MIT" + }, + "node_modules/chalk/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, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -2331,6 +2824,131 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/chromium-bidi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-8.0.0.tgz", + "integrity": "sha512-d1VmE0FD7lxZQHzcDUCKZSNRtRwISXDsdg4HjdTR5+Ll5nQ/vzU12JeNmupD6VWffrPSlrnGhEWlLESKH3VO+g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/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/cliui/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, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/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, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cliui/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, + "license": "MIT" + }, + "node_modules/cliui/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/cliui/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/cliui/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/cliui/node_modules/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/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2496,6 +3114,16 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2505,6 +3133,19 @@ "ms": "2.0.0" } }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dedent": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", @@ -2520,6 +3161,21 @@ } } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2539,6 +3195,23 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/devtools-protocol": { + "version": "0.0.1475386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1475386.tgz", + "integrity": "sha512-RQ809ykTfJ+dgj9bftdeL2vRVxASAuGU+I9LEx9Ij5TXU5HrgAQVmzi72VA+mkzscE12uzlRv5/tWWv9R9J1SA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -2614,6 +3287,26 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", @@ -2742,6 +3435,62 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -2810,6 +3559,69 @@ "url": "https://opencollective.com/express" } }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/extract-zip/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/fdir": { "version": "6.4.4", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", @@ -2831,6 +3643,19 @@ "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", "license": "MIT" }, + "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, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -2855,6 +3680,33 @@ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", "license": "MIT" }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", @@ -2911,6 +3763,13 @@ "node": ">=12" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2945,6 +3804,16 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -2979,9 +3848,65 @@ "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/get-uri/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==", + "dev": true, + "license": "MIT" + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -3003,6 +3928,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "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/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -3038,6 +3976,16 @@ "dev": true, "license": "ISC" }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -3062,6 +4010,16 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/helmet": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", @@ -3125,6 +4083,84 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/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==", + "dev": true, + "license": "MIT" + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -3153,12 +4189,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -3174,6 +4232,19 @@ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", "license": "MIT" }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -3189,6 +4260,16 @@ "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, + "license": "MIT", + "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", @@ -3199,6 +4280,39 @@ "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -3211,6 +4325,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -3264,6 +4391,19 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsesc": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", @@ -3382,6 +4522,22 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -3431,6 +4587,23 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/logform": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", @@ -3532,61 +4705,214 @@ "node": ">=4" } }, - "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==", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "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/mithril": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/mithril/-/mithril-1.1.7.tgz", + "integrity": "sha512-1SAkGeVrIVvkUHlPHvR3pXdWzNfTzmS/fBAe+rC2ApEBfZFFc+idi8Qg/M5JoW/sZkIDXSfQYVgvENMIhBIVAg==", + "license": "MIT", + "bin": { + "bundle": "bundler/bin/bundle", + "ospec": "ospec/bin/ospec" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, "engines": { - "node": ">= 0.6" + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "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==", + "node_modules/mocha/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "ms": "^2.1.3" }, "engines": { - "node": ">= 0.6" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=12" }, "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==", + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=10" } }, - "node_modules/mithril": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/mithril/-/mithril-1.1.7.tgz", - "integrity": "sha512-1SAkGeVrIVvkUHlPHvR3pXdWzNfTzmS/fBAe+rC2ApEBfZFFc+idi8Qg/M5JoW/sZkIDXSfQYVgvENMIhBIVAg==", + "node_modules/mocha/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/mocha/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", - "bin": { - "bundle": "bundler/bin/bundle", - "ospec": "ospec/bin/ospec" + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mocha/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" } }, "node_modules/ms": { @@ -3644,6 +4970,16 @@ "node": ">= 0.6" } }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -3667,6 +5003,16 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/npm-install-checks": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", @@ -3773,6 +5119,16 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/one-time": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", @@ -3797,6 +5153,97 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/pac-proxy-agent/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, "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", @@ -3849,6 +5296,16 @@ "node": ">= 0.8" } }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -3911,6 +5368,13 @@ "dev": true, "license": "MIT" }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3991,6 +5455,16 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -4050,20 +5524,185 @@ "long": "^5.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=12.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-agent/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer": { + "version": "24.17.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.17.0.tgz", + "integrity": "sha512-CGrmJ8WgilK3nyE73k+pbxHggETPpEvL6AQ9H5JSK1RgZRGMQVJ+iO3MocGm9yBQXQJ9U5xijyLvkYXFeb0/+g==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.10.7", + "chromium-bidi": "8.0.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1475386", + "puppeteer-core": "24.17.0", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "24.17.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.17.0.tgz", + "integrity": "sha512-RYOBKFiF+3RdwIZTEacqNpD567gaFcBAOKTT7742FdB1icXudrPI7BlZbYTYWK2wgGQUXt9Zi1Yn+D5PmCs4CA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.10.7", + "chromium-bidi": "8.0.0", + "debug": "^4.4.1", + "devtools-protocol": "0.0.1475386", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "node_modules/puppeteer-core/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/puppeteer/node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, "license": "MIT", "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" }, "engines": { - "node": ">= 0.10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/qs": { @@ -4081,6 +5720,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -4224,6 +5873,16 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -4395,6 +6054,16 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "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/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", @@ -4539,6 +6208,72 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socks-proxy-agent/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==", + "dev": true, + "license": "MIT" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -4637,6 +6372,20 @@ "integrity": "sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==", "license": "MIT" }, + "node_modules/streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -4756,12 +6505,41 @@ "node": ">=8" } }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "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": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "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", @@ -4774,6 +6552,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar-fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz", + "integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -4797,6 +6612,19 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "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, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -4836,6 +6664,13 @@ } } }, + "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==", + "dev": true, + "license": "0BSD" + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -4849,6 +6684,13 @@ "node": ">= 0.6" } }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true, + "license": "MIT" + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -5236,6 +7078,13 @@ "node": ">= 6" } }, + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -5354,10 +7203,17 @@ "node": ">=8" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, "node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -5375,6 +7231,16 @@ } } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -5395,6 +7261,130 @@ "engines": { "node": ">= 14.6" } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/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/yargs/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/yargs/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/yargs/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/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/Tokenization/webapp/package.json b/Tokenization/webapp/package.json index 13f419f83..75a135054 100644 --- a/Tokenization/webapp/package.json +++ b/Tokenization/webapp/package.json @@ -8,8 +8,12 @@ "build": "react-router build", "dev": "react-router dev", "start": "react-router-serve ./build/server/index.js", + "test": "mocha --exit tests/**/*.js", "typecheck": "react-router typegen && tsc", - "docker:typecheck": "docker compose exec webapp npm run typecheck" + "docker:typecheck": "docker compose exec webapp npm run typecheck", + "docker:test:build": "cd .. && docker compose -f docker-compose.test.yml up --build --abort-on-container-exit ui-tests && docker compose -f docker-compose.test.yml stop && cd ./webapp", + "docker:test": "cd .. && docker compose -f docker-compose.test.yml up --abort-on-container-exit ui-tests && docker compose -f docker-compose.test.yml stop && cd ./webapp" + }, "repository": { "type": "git", @@ -39,6 +43,8 @@ "@types/react-dom": "^19.0.1", "typescript": "^5.7.2", "vite": "^6.0.11", - "vite-tsconfig-paths": "^5.1.4" + "vite-tsconfig-paths": "^5.1.4", + "mocha": "^10.2.0", + "puppeteer": "^24.9.0" } } diff --git a/Tokenization/webapp/tests/main.test.js b/Tokenization/webapp/tests/main.test.js new file mode 100644 index 000000000..47646e096 --- /dev/null +++ b/Tokenization/webapp/tests/main.test.js @@ -0,0 +1,22 @@ +import assert from 'assert' +import puppeteer from 'puppeteer' + +describe('Puppeteer test', () => { + it('Pupeteer launch', async () => { + const browser = await puppeteer.launch({ + headless: true, + executablePath: process.env.PUPPETEER_EXECUTABLE_PATH, + args: ['--no-sandbox'] + }) + + + const page = await browser.newPage(); + + await page.goto('http://prod-container:80/') + + const headerContent = await page.$eval('header', el => el.textContent) + assert.ok(headerContent.includes('Tokenization')) + + await browser.close() + }); +}) \ No newline at end of file From f0700daf501aad0cb8bf9f99b647bf3037966756 Mon Sep 17 00:00:00 2001 From: Igor Sala Date: Sun, 24 Aug 2025 12:10:23 +0200 Subject: [PATCH 06/47] - Proposed session checking logic with context api and hooks. --- .../webapp/app/contexts/sessionContext.tsx | 97 +++++++++++++++++++ Tokenization/webapp/app/hooks/session.tsx | 69 +++++++++++++ Tokenization/webapp/app/hooks/useAuth.tsx | 20 ---- Tokenization/webapp/app/root.tsx | 28 ++---- Tokenization/webapp/app/routes.ts | 12 ++- Tokenization/webapp/app/routes/404.tsx | 26 +++++ Tokenization/webapp/app/ui/layout.tsx | 28 ++---- 7 files changed, 214 insertions(+), 66 deletions(-) create mode 100644 Tokenization/webapp/app/contexts/sessionContext.tsx create mode 100644 Tokenization/webapp/app/hooks/session.tsx delete mode 100644 Tokenization/webapp/app/hooks/useAuth.tsx create mode 100644 Tokenization/webapp/app/routes/404.tsx diff --git a/Tokenization/webapp/app/contexts/sessionContext.tsx b/Tokenization/webapp/app/contexts/sessionContext.tsx new file mode 100644 index 000000000..e675c6211 --- /dev/null +++ b/Tokenization/webapp/app/contexts/sessionContext.tsx @@ -0,0 +1,97 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ +import React, { createContext, useState, useEffect, useMemo, useCallback } from 'react'; +import { useLocation, useNavigate } from 'react-router'; + +interface Session { + personid: string | null; + name: string | null; + token: string | null; + username: string | null; + access: string[] | null; +} + +type SessionKey = keyof Session; + +const defaultSession = { + personid: null, + name: null, + token: null, + username: null, + access: null, +}; + +// List ["personid", "name", "token", ...] +const sessionKeys = Object.keys(defaultSession) as SessionKey[]; + +interface SessionContextType { + session: Session; + hasAccess: (role: string) => boolean; +} + +/** + * React context for managing user session state. + * Provides session data and access control functionality. + */ +export const SessionContext = createContext({ + session: defaultSession, + hasAccess: () => false, +}); + +/** + * Session provider component that manages user session state. + * + * Automatically extracts session data from URL parameters on mount and + * provides session context to child components. + * + * @param children - React components that need access to session context + * + * @example + * ```tsx + * + * + * + * ``` + */ +export const SessionProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [session, setSession] = useState(defaultSession); + const location = useLocation(); + const navigate = useNavigate(); + + useEffect(() => { + let sessionLoad: Session = { ...defaultSession }; + const params = new URLSearchParams(location.search); + for (const sessionKey of sessionKeys) { + const value = params.get(sessionKey); + if (value && sessionKey === 'access') { + sessionLoad = { ...sessionLoad, [sessionKey]: value.split(',') }; + } else if (value) { + sessionLoad = { ...sessionLoad, [sessionKey]: value }; + } + } + setSession(sessionLoad); + navigate(location.pathname, { replace: true }); + // It should run only once when we start page + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const hasAccess = useCallback( + (role: string) => session.access?.includes(role) ?? false, + [session], + ); + + const value = useMemo(() => ({ session, hasAccess }), [session, hasAccess]); + + return {children}; +}; diff --git a/Tokenization/webapp/app/hooks/session.tsx b/Tokenization/webapp/app/hooks/session.tsx new file mode 100644 index 000000000..e5aab98f2 --- /dev/null +++ b/Tokenization/webapp/app/hooks/session.tsx @@ -0,0 +1,69 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { useContext } from 'react'; + +import { SessionContext } from '../contexts/sessionContext'; + +/** + * Custom hook to access the current user session data. + * + * @returns {Session} The current session object containing: + * - personid: User's person ID + * - name: User's display name + * - token: Authentication token + * - username: User's username + * - access: Array of user's access roles + * + * @throws {Error} If the hook is used outside of SessionProvider + * + * @example + * ```tsx + * const session = useSession(); + * console.log(`Welcome, ${session.name}!`); + * ``` + */ +export function useSession() { + const obj = useContext(SessionContext); + if (!obj) { + throw new Error('Session wasnt created'); + } + return obj.session; +} + +/** + * Custom hook to check if the current user has access to a specific role. + * + * @param {string} role - The role to check access for + * @returns {boolean} True if the user has the specified role, false otherwise + * + * @throws {Error} If the hook is used outside of SessionProvider + * + * @example + * ```tsx + * const hasAdminAccess = useAuth('admin'); + * const canEditTokens = useAuth('token-editor'); + * + * if (hasAdminAccess) { + * // Render admin-only content + * } + * ``` + */ +export function useAuth(role: string) { + const obj = useContext(SessionContext); + if (!obj) { + throw new Error('Session wasnt created'); + } + return obj.hasAccess(role); +} diff --git a/Tokenization/webapp/app/hooks/useAuth.tsx b/Tokenization/webapp/app/hooks/useAuth.tsx deleted file mode 100644 index 33b0e29aa..000000000 --- a/Tokenization/webapp/app/hooks/useAuth.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import {useState, useEffect} from 'react'; -import sessionService from '@aliceo2/web-ui/Frontend/js/src/sessionService' - -export function useAuth(role: string): boolean { - const [hasAccess, setHasAccess] = useState(false) - - useEffect( () => { - try { - if(!sessionService.session) { - setHasAccess(false) - } - setHasAccess(sessionService.hasAccess(role)) - }catch(e) { - setHasAccess(false) - } - }, [role]) - - return hasAccess - -} diff --git a/Tokenization/webapp/app/root.tsx b/Tokenization/webapp/app/root.tsx index fd7090148..f78641b77 100644 --- a/Tokenization/webapp/app/root.tsx +++ b/Tokenization/webapp/app/root.tsx @@ -19,11 +19,11 @@ import { Meta, Outlet, Scripts, - ScrollRestoration, useNavigation, + ScrollRestoration } from 'react-router'; +import { SessionProvider } from './contexts/sessionContext'; import { Spinner } from '~/ui/spinner'; -import AppLayout from '~/ui/layout'; import '@aliceo2/web-ui/Frontend/css/src/bootstrap.css'; import './app.css'; @@ -31,9 +31,6 @@ import './styles/components-styles.css' import './styles/ui-styles.css' export function Layout({ children }: { children: React.ReactNode }) { - const { state } = useNavigation(); - - return ( @@ -43,9 +40,7 @@ export function Layout({ children }: { children: React.ReactNode }) { - - {children} - + {children} @@ -54,18 +49,11 @@ export function Layout({ children }: { children: React.ReactNode }) { } export default function App() { - useEffect(() => { - try{ - sessionService.loadAndHideParameters() - console.log(sessionService.session) - }catch(e) { - console.error(e) - } - }, []) - - const hasAccess = useAuth('admin') - - return hasAccess ? : ; + + return ( + + + ); } export function HydrateFallback() { diff --git a/Tokenization/webapp/app/routes.ts b/Tokenization/webapp/app/routes.ts index fdfcc2f6f..e19aebdd7 100644 --- a/Tokenization/webapp/app/routes.ts +++ b/Tokenization/webapp/app/routes.ts @@ -15,9 +15,13 @@ import { type RouteConfig, index, route, prefix } from '@react-router/dev/routes'; export default [ - index('routes/home.tsx'), - ...prefix('tokens', [ - index('routes/tokens/overview.tsx'), - route(':tokenId', 'routes/tokens/details.tsx'), + route('', 'ui/layout.tsx', [ + index('routes/home.tsx'), + ...prefix('tokens', [ + index('routes/tokens/overview.tsx'), + route(':tokenId', 'routes/tokens/details.tsx'), + ]), + route('*', 'routes/404.tsx'), ]), + ] satisfies RouteConfig; diff --git a/Tokenization/webapp/app/routes/404.tsx b/Tokenization/webapp/app/routes/404.tsx new file mode 100644 index 000000000..81149c99e --- /dev/null +++ b/Tokenization/webapp/app/routes/404.tsx @@ -0,0 +1,26 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +/** + * 404 Not Found page component. + * Displays a user-friendly error message when a requested page doesn't exist. + */ +export default function NotFound() { + return ( +
+

404 - Page Not Found

+

The page you are looking for does not exist.

+
+ ); +} diff --git a/Tokenization/webapp/app/ui/layout.tsx b/Tokenization/webapp/app/ui/layout.tsx index 58eca9faf..774ed0c14 100644 --- a/Tokenization/webapp/app/ui/layout.tsx +++ b/Tokenization/webapp/app/ui/layout.tsx @@ -12,17 +12,7 @@ * or submit itself to any jurisdiction. */ -/** - * Layout - * - * Main layout component for the application. - * Provides the header, sidebar, and main content area. - * Also sets up the HeaderContext to allow child components to update the header content. - * @param state The current loading state of the application (e.g., 'loading' or ready). - * @param children The main content to be rendered inside the layout. - * @return The application layout with header, sidebar, and content area. - */ - +import { Outlet, useNavigation } from 'react-router'; import { useState } from 'react'; import { AppHeader } from './header/header'; @@ -30,18 +20,12 @@ import { HeaderContext } from './header/headerContext'; import { AppSidebar } from './sidebar'; import { Spinner } from './spinner'; -interface LayoutArgs { - state: string; - children: React.ReactNode; -} - /** - * Component provides layout for the applicatio - * - * @param state - refers to state of the whole website if data are loaded its - loading - * @param children - elements to render inside layout component + * Component provides main layout for the application + * Uses useNavigation state to check if page is loaded */ -export default function Layout({ state, children }: LayoutArgs) { +export default function Layout() { + const { state } = useNavigation(); const [headerContent, setHeaderContent] = useState('Tokenization Admin Interface'); @@ -51,7 +35,7 @@ export default function Layout({ state, children }: LayoutArgs) {
- {state === 'loading' ? : children} + {state === 'loading' ? : }
From 5311ebfaad9fcc3352a1a4ee6a5b46277a6ba86b Mon Sep 17 00:00:00 2001 From: Igor Sala Date: Mon, 25 Aug 2025 12:14:59 +0200 Subject: [PATCH 07/47] - Proposed Tokenization project github workflow for UI tests ( eslint configuration was created in different branch so if it needs to be as in different workflows I ask to accept this PR and I will and it OGUI-1711 ) --- .github/workflows/tokenization.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/tokenization.yml diff --git a/.github/workflows/tokenization.yml b/.github/workflows/tokenization.yml new file mode 100644 index 000000000..8cbf2c293 --- /dev/null +++ b/.github/workflows/tokenization.yml @@ -0,0 +1,23 @@ +name: Tokenization +on: + pull_request: + paths: + - 'Tokenization/**/*' + - '.github/workflows/tokenization.yml' + push: + branches: + - 'main' + - 'dev' + +jobs: + ui-test: + name: Check eslint rules on ubuntu-latest + runs-on: ubuntu-latest + timeout-minutes: 6 + steps: + - uses: actions/checkout@v4 + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: '22.x' + - run: (cd Tokenization/webapp; npm run docker:test) \ No newline at end of file From 494c62680d493d7ec53d2cdd94387ff0145bc114 Mon Sep 17 00:00:00 2001 From: Igor Sala Date: Wed, 3 Sep 2025 17:39:42 +0200 Subject: [PATCH 08/47] Fixing components and changing routes for better using of react-router 7 capabiilities such as defers, navigations etc. --- Tokenization/webapp/app/routes.ts | 5 +- Tokenization/webapp/app/routes/home.tsx | 4 +- .../webapp/app/routes/tokens/create.tsx | 29 +++++++++ .../webapp/app/routes/tokens/layout.tsx | 39 ++++++++++++ .../webapp/app/routes/tokens/overview.tsx | 60 +++++++------------ .../webapp/app/ui/header/headerContext.tsx | 15 +++-- Tokenization/webapp/app/ui/layout.tsx | 7 +-- Tokenization/webapp/app/ui/navbar.tsx | 41 +++++++------ Tokenization/webapp/app/ui/sidebar.tsx | 9 ++- 9 files changed, 136 insertions(+), 73 deletions(-) create mode 100644 Tokenization/webapp/app/routes/tokens/create.tsx create mode 100644 Tokenization/webapp/app/routes/tokens/layout.tsx diff --git a/Tokenization/webapp/app/routes.ts b/Tokenization/webapp/app/routes.ts index e19aebdd7..3dceb7777 100644 --- a/Tokenization/webapp/app/routes.ts +++ b/Tokenization/webapp/app/routes.ts @@ -12,13 +12,14 @@ * or submit itself to any jurisdiction. */ -import { type RouteConfig, index, route, prefix } from '@react-router/dev/routes'; +import { type RouteConfig, index, route } from '@react-router/dev/routes'; export default [ route('', 'ui/layout.tsx', [ index('routes/home.tsx'), - ...prefix('tokens', [ + route('tokens', 'routes/tokens/layout.tsx', [ index('routes/tokens/overview.tsx'), + route('new', 'routes/tokens/create.tsx'), route(':tokenId', 'routes/tokens/details.tsx'), ]), route('*', 'routes/404.tsx'), diff --git a/Tokenization/webapp/app/routes/home.tsx b/Tokenization/webapp/app/routes/home.tsx index bfc99ffb1..0b1b459eb 100644 --- a/Tokenization/webapp/app/routes/home.tsx +++ b/Tokenization/webapp/app/routes/home.tsx @@ -11,7 +11,6 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ - import { Link } from 'react-router'; import { useSetHeader } from '~/ui/header/headerContext'; @@ -22,8 +21,7 @@ import { useSetHeader } from '~/ui/header/headerContext'; */ export default function Home() { - const { setHeaderContent } = useSetHeader(); - setHeaderContent('Tokenization Admin Interface'); + useSetHeader('Tokenization Admin Interface'); return <>

Welcome to (dummy) Tokenization GUI!

diff --git a/Tokenization/webapp/app/routes/tokens/create.tsx b/Tokenization/webapp/app/routes/tokens/create.tsx new file mode 100644 index 000000000..39842e6f3 --- /dev/null +++ b/Tokenization/webapp/app/routes/tokens/create.tsx @@ -0,0 +1,29 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { useNavigate } from 'react-router'; + +/** + * First version of page for creating tokens + */ +export default function CreateToken() { + const navigate = useNavigate(); + + return ( +
+

Create New Token

+ +
+ ); +} diff --git a/Tokenization/webapp/app/routes/tokens/layout.tsx b/Tokenization/webapp/app/routes/tokens/layout.tsx new file mode 100644 index 000000000..3ac1e855d --- /dev/null +++ b/Tokenization/webapp/app/routes/tokens/layout.tsx @@ -0,0 +1,39 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { Outlet, useNavigation } from 'react-router'; + +import { Spinner } from '~/ui/spinner'; +import { useSetHeader } from '~/ui/header/headerContext'; +import { TabsNavbar, LinkTab } from '~/ui/navbar'; + +/** + * Layout for tokens subpage + */ +export default function TokenLayout() { + + const { state } = useNavigation(); + + useSetHeader('Tokens'); + + return ( +
+ + + + + + {state === 'loading' ? : } +
); +} diff --git a/Tokenization/webapp/app/routes/tokens/overview.tsx b/Tokenization/webapp/app/routes/tokens/overview.tsx index fe0d4268d..8af760cf6 100644 --- a/Tokenization/webapp/app/routes/tokens/overview.tsx +++ b/Tokenization/webapp/app/routes/tokens/overview.tsx @@ -14,27 +14,27 @@ import type { Route } from './+types/overview'; import type { Token } from '../../components/tokens/token'; -import { Link } from 'react-router'; -import { useState } from 'react'; -import { Tab } from '@mui/material'; - -import { useSetHeader } from '~/ui/header/headerContext'; -import { TabsNavbar } from '~/ui/navbar'; +import React from 'react'; +import { Link, Await } from 'react-router'; +import { Spinner } from '~/ui/spinner'; /** * Client loader that fetches all tokens from the API. * * @returns Promise that resolves to an array of tokens */ -export const clientLoader = async (): Promise => { - const response = await fetch('/api/tokens'); - if (!response.ok) { - throw new Error('An error occurred!'); - } - return response.json(); +export const clientLoader = async (): Promise<{ tokens: Promise }> => { + const tokensPromise = fetch('/api/tokens') + .then(r => r.json()) + .catch(_ => { + throw new Error('An error occurred'); + }); + + return { + tokens: tokensPromise, + }; }; -// Will be changed in next PR /** * Table component that displays a list of tokens with their ID and validity. * Token IDs are clickable links that navigate to the token details page. @@ -60,30 +60,16 @@ function TokenTable({ tokens }: { tokens: Token[] }) { /** * Tokens overview page component with tabbed interface. - * Displays a list of tokens and provides a placeholder for token creation. + * Displays a list of tokens * - * @param loaderData - Array of tokens loaded by the client loader + * @param loaderData - Object containing the deferred tokens promise */ -export default function Overview({ loaderData: tokens }: Route.ComponentProps) { - - const { setHeaderContent } = useSetHeader(); - setHeaderContent('Tokens'); - - const [tabIndex, setTabIndex] = useState(0); - - return
- - - - - - { - tabIndex == 0 ? - : -
-

Create Token

-

Form to create a new token will go here.

-
- } -
; +export default function Overview({ loaderData: { tokens } }: Route.ComponentProps) { + return ( + }> + + {(data) => } + + + ); } diff --git a/Tokenization/webapp/app/ui/header/headerContext.tsx b/Tokenization/webapp/app/ui/header/headerContext.tsx index 467b8a352..e47e8cca6 100644 --- a/Tokenization/webapp/app/ui/header/headerContext.tsx +++ b/Tokenization/webapp/app/ui/header/headerContext.tsx @@ -12,7 +12,7 @@ * or submit itself to any jurisdiction. */ -import { createContext, useContext } from 'react'; +import { createContext, useContext, useEffect } from 'react'; /** * HeaderContext @@ -30,8 +30,13 @@ export const HeaderContext = createContext({ /** * UseSetHeader * - * Custom React hook to access the HeaderContext. - * Allows components to call setHeaderContent to update the header text. - * @return The context value containing setHeaderContent. + * Changes content of header + * @param headerContent new value for context */ -export const useSetHeader = () => useContext(HeaderContext); +export const useSetHeader = (headerContent: string) => { + const { setHeaderContent } = useContext(HeaderContext); + + useEffect(() => { + setHeaderContent(headerContent); + }, [setHeaderContent, headerContent]); +}; diff --git a/Tokenization/webapp/app/ui/layout.tsx b/Tokenization/webapp/app/ui/layout.tsx index 774ed0c14..87b5ba65c 100644 --- a/Tokenization/webapp/app/ui/layout.tsx +++ b/Tokenization/webapp/app/ui/layout.tsx @@ -12,21 +12,18 @@ * or submit itself to any jurisdiction. */ -import { Outlet, useNavigation } from 'react-router'; +import { Outlet } from 'react-router'; import { useState } from 'react'; import { AppHeader } from './header/header'; import { HeaderContext } from './header/headerContext'; import { AppSidebar } from './sidebar'; -import { Spinner } from './spinner'; /** * Component provides main layout for the application * Uses useNavigation state to check if page is loaded */ export default function Layout() { - const { state } = useNavigation(); - const [headerContent, setHeaderContent] = useState('Tokenization Admin Interface'); return ( @@ -35,7 +32,7 @@ export default function Layout() {
- {state === 'loading' ? : } +
diff --git a/Tokenization/webapp/app/ui/navbar.tsx b/Tokenization/webapp/app/ui/navbar.tsx index d996641a2..70aa2712e 100644 --- a/Tokenization/webapp/app/ui/navbar.tsx +++ b/Tokenization/webapp/app/ui/navbar.tsx @@ -13,11 +13,10 @@ */ import React from 'react'; -import { Box, Tabs } from '@mui/material'; +import { NavLink } from 'react-router'; +import { Box, Tab } from '@mui/material'; interface TabsNavbarArguments { - tabIndex: number; - setTabIndex: (index: number) => void; children?: React.ReactNode; } @@ -30,23 +29,29 @@ interface TabsNavbarArguments { * @param setTabIndex Function to update the selected tab index. * @param children Tab (@mui/material) components (usually ) to be rendered inside the navigation bar. */ -export function TabsNavbar({ tabIndex, setTabIndex, children }: TabsNavbarArguments) { - - const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { - setTabIndex(newValue); - }; - +export function TabsNavbar({ children }: TabsNavbarArguments) { return ( - - {children} - + {children} ); } + +interface LinkTabProps { + label: string; + to: string; +} + +export const LinkTab = ({ label, to }: LinkTabProps) => ( + +); diff --git a/Tokenization/webapp/app/ui/sidebar.tsx b/Tokenization/webapp/app/ui/sidebar.tsx index 08510ed92..0e701317d 100644 --- a/Tokenization/webapp/app/ui/sidebar.tsx +++ b/Tokenization/webapp/app/ui/sidebar.tsx @@ -16,18 +16,21 @@ import type { NavLinkProps } from 'react-router'; import { NavLink } from 'react-router'; import Button from '@mui/material/Button'; +type StyledNavLinkProps = { + children: React.ReactNode; + to: NavLinkProps['to']; +}; + /** * StyledNavLink * * A wrapper component that renders a Material-UI Button styled as a navigation link. * It uses NavLink from react-router to determine if the link is active and applies * the 'contained' variant for the active route and 'outlined' for inactive routes. - * @param children.children * @param children The content to display inside the button. * @param to The target route path. - * @param children.to */ -const StyledNavLink = ({ children, to }: NavLinkProps) => +const StyledNavLink = ({ children, to }: StyledNavLinkProps) => {({ isActive }) => ( +
+
+ + + options={options} + value={firstSelectedOption} + onChange={(srv: SingleValue) => { + setFirstSelectedService(srv ? srv.value : ''); + }} + placeholder="Select first service..." + /> +
+
+ + + options={options} + value={secondSelectedOption} + onChange={(srv: SingleValue) => { + setSecondSelectedService(srv ? srv.value : ''); + }} + placeholder="Select second service..." + /> +
+ +
+ +
+ +
+ +
-
+
+ + setExpirationTime(e.target.value)} + placeholder="Enter expiration time" + slotProps={{ + input: { + endAdornment: hours, + inputProps: { min: 1 } + } + }} + label="" + /> +
+ +
-
From 71129cca9187e89b4dcf9dcea9ed2320953739ef Mon Sep 17 00:00:00 2001 From: Igor Sala Date: Fri, 3 Oct 2025 02:19:29 +0200 Subject: [PATCH 19/47] Token panel updated with overwiew and token details pages updated. Revoking token workflow implemented --- Tokenization/backend/index.js | 43 +++++++- .../app/components/tokens/action-block.tsx | 51 ++++++++++ .../webapp/app/components/tokens/token.ts | 10 +- .../webapp/app/routes/tokens/details.tsx | 7 +- .../webapp/app/routes/tokens/overview.tsx | 98 ++++++++++++++++--- 5 files changed, 189 insertions(+), 20 deletions(-) create mode 100644 Tokenization/webapp/app/components/tokens/action-block.tsx diff --git a/Tokenization/backend/index.js b/Tokenization/backend/index.js index d347df873..956e76267 100644 --- a/Tokenization/backend/index.js +++ b/Tokenization/backend/index.js @@ -17,10 +17,49 @@ import { HttpServer } from '@aliceo2/web-ui'; const http = new HttpServer({port: 8080, allow: '*'}); const fakeTokens = new Map([ - [1, {tokenId: 1, validity: 'good'}], - [2, {tokenId: 2, validity: 'bad'}], + [1, { + tokenId: 1, + last4chars: 'abcd', + serviceFrom: 'Service 1', + serviceTo: 'Service 2', + exp: '2026-01-12T11:31:12', + issuer: 'central-system', + iat: '2025-10-01T10:00:00', + permissions: ['GET', 'POST'] + }], + [2, { + tokenId: 2, + last4chars: 'wxyz', + serviceFrom: 'Service 3', + serviceTo: 'Service 4', + exp: '2025-11-15T08:45:30', + issuer: 'admin-portal', + iat: '2025-09-15T14:22:10', + permissions: ['GET'] + }], + [3, { + tokenId: 3, + last4chars: 'efgh', + serviceFrom: 'Service 2', + serviceTo: 'Service 1', + exp: '2026-03-20T16:30:00', + issuer: 'central-system', + iat: '2025-10-02T09:15:00', + permissions: ['GET', 'POST', 'PUT', 'DELETE'] + }], + [4, { + tokenId: 4, + last4chars: '1234', + serviceFrom: 'Service 1', + serviceTo: 'Service 3', + exp: '2026-02-05T12:00:00', + issuer: 'api-gateway', + iat: '2025-09-25T11:30:45', + permissions: ['GET', 'PUT'] + }] ]); + const fakeLogs = new Map([ [1, []], [2, [ diff --git a/Tokenization/webapp/app/components/tokens/action-block.tsx b/Tokenization/webapp/app/components/tokens/action-block.tsx new file mode 100644 index 000000000..12185e4fa --- /dev/null +++ b/Tokenization/webapp/app/components/tokens/action-block.tsx @@ -0,0 +1,51 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import React from 'react'; +import { IconDelete } from '~/ui/icon'; + +interface DeleteDialogState { + isOpen: boolean; + tokenId: string; +} + +interface ActionBlockProps { + tokenId: string; + setActionDeleteWindow: React.Dispatch>; +} + +/** + * Action block component that provides token actions such as delete + */ +export default function ActionBlock({ tokenId, setActionDeleteWindow }: ActionBlockProps) { + const handleDelete = () => { + setActionDeleteWindow({ + isOpen: true, + tokenId: tokenId, + }); + }; + + return ( +
+ +
+ ); +} diff --git a/Tokenization/webapp/app/components/tokens/token.ts b/Tokenization/webapp/app/components/tokens/token.ts index d6a098cf8..10dd55836 100644 --- a/Tokenization/webapp/app/components/tokens/token.ts +++ b/Tokenization/webapp/app/components/tokens/token.ts @@ -13,6 +13,12 @@ */ export interface Token { - tokenId: number; - validity: 'good' | 'bad'; + tokenId: string ; + serviceFrom: string; + serviceTo: string; + exp: string; + last4chars: string; + issuer: string; + iat: string; + permissions: string[]; } diff --git a/Tokenization/webapp/app/routes/tokens/details.tsx b/Tokenization/webapp/app/routes/tokens/details.tsx index 8be896322..9c1d7fee2 100644 --- a/Tokenization/webapp/app/routes/tokens/details.tsx +++ b/Tokenization/webapp/app/routes/tokens/details.tsx @@ -74,7 +74,12 @@ export const clientLoader = async ({ params }: Route.ClientLoaderArgs): Promise< export default function Details({ loaderData: { token, logs } }: Route.ComponentProps) { return <>

Token {token.tokenId} details

-

The token has a {token.validity} validity.

+

Last 4 token characters: {token.last4chars}

+

Issuer: {token.issuer}

+

Issued at: {token.iat}

+

Subject: {token.serviceFrom}

+

Audience: {token.serviceTo}

+

Expires at: {token.exp}

Logs

}> Failed to load the logs
}> diff --git a/Tokenization/webapp/app/routes/tokens/overview.tsx b/Tokenization/webapp/app/routes/tokens/overview.tsx index fe0d4268d..b317e7658 100644 --- a/Tokenization/webapp/app/routes/tokens/overview.tsx +++ b/Tokenization/webapp/app/routes/tokens/overview.tsx @@ -16,10 +16,12 @@ import type { Token } from '../../components/tokens/token'; import { Link } from 'react-router'; import { useState } from 'react'; -import { Tab } from '@mui/material'; +import { Tab, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Button } from '@mui/material'; +import ActionBlock from '~/components/tokens/action-block'; import { useSetHeader } from '~/ui/header/headerContext'; import { TabsNavbar } from '~/ui/navbar'; +import { useAuth } from '~/hooks/session'; /** * Client loader that fetches all tokens from the API. @@ -34,6 +36,12 @@ export const clientLoader = async (): Promise => { return response.json(); }; +// Interfejs stanu dla okna dialogowego usuwania tokena +interface DeleteDialogState { + isOpen: boolean; + tokenId: string; +} + // Will be changed in next PR /** * Table component that displays a list of tokens with their ID and validity. @@ -42,20 +50,80 @@ export const clientLoader = async (): Promise => { * @param tokens - Array of tokens to display */ function TokenTable({ tokens }: { tokens: Token[] }) { - return - - - - - - - - {tokens.map((token: Token) => - - - )} - -
IDvalidity
{token.tokenId}{token.validity}
; + const theaders = ['ID', 'Service From', 'Service To', 'Expires at', 'Actions']; + + // Zmodyfikowany stan dla okna dialogowego + const [deleteDialog, setDeleteDialog] = useState({ + isOpen: false, + tokenId: '', + }); + + const auth = useAuth('admin'); + + // Funkcja obsługująca potwierdzenie usunięcia + const handleConfirmDelete = async () => { + if (auth) { + console.log('Token Deleted'); + } + setDeleteDialog({ isOpen: false, tokenId: '' }); + }; + + const handleCloseDialog = () => { + setDeleteDialog({ isOpen: false, tokenId: '' }); + }; + + return ( + <> + + + + {theaders.map((content, index) => )} + + + + {tokens.map((token: Token) => ( + + + + + + + + ))} + +
{content}
{token.tokenId}{token.serviceFrom}{token.serviceTo}{token.exp} + +
+ + + + Confirm Token Deletion + + + + Are you sure you want to delete token with ID: {deleteDialog.tokenId}? + This action cannot be undone. + + + + + + + + + ); } /** From 15bf27145164ac5459f9facd0290292f15847eeb Mon Sep 17 00:00:00 2001 From: Igor Sala Date: Tue, 7 Oct 2025 13:16:05 +0200 Subject: [PATCH 20/47] Adjusted eslint configuration file for linting and fixing mocha tests files places in webapp/tests catalog. Changed tests structure --- Tokenization/webapp/eslint.config.js | 131 ++++++++---- Tokenization/webapp/package-lock.json | 232 ++++----------------- Tokenization/webapp/package.json | 25 +-- Tokenization/webapp/tests/config.cjs | 20 ++ Tokenization/webapp/tests/main.test.js | 23 -- Tokenization/webapp/tests/mocha-index.cjs | 77 +++++++ Tokenization/webapp/tests/public/basic.cjs | 38 ++++ 7 files changed, 272 insertions(+), 274 deletions(-) create mode 100644 Tokenization/webapp/tests/config.cjs delete mode 100644 Tokenization/webapp/tests/main.test.js create mode 100644 Tokenization/webapp/tests/mocha-index.cjs create mode 100644 Tokenization/webapp/tests/public/basic.cjs diff --git a/Tokenization/webapp/eslint.config.js b/Tokenization/webapp/eslint.config.js index d0b762f00..63f2c6984 100644 --- a/Tokenization/webapp/eslint.config.js +++ b/Tokenization/webapp/eslint.config.js @@ -20,6 +20,7 @@ import pluginReactHooks from 'eslint-plugin-react-hooks'; import jsdoc from 'eslint-plugin-jsdoc'; import stylisticTs from '@stylistic/eslint-plugin-ts'; import stylisticJs from '@stylistic/eslint-plugin-js'; +import mochaPlugin from 'eslint-plugin-mocha'; const licenseHeader = `/** * @license @@ -70,8 +71,6 @@ const licenseHeaderRule = { export default [ { ignores: [ - 'test/', - 'tests/', 'node_modules/', 'build/', 'dist/', @@ -86,13 +85,56 @@ export default [ ], }, - pluginJs.configs.recommended, - ...tseslint.configs.recommended, - pluginReact.configs.flat.recommended, - { - files: ['**/*.{js,mjs,cjs,ts,tsx,jsx}'], + files: ['**/*.{js,cjs,mjs}'], + ...pluginJs.configs['flat/recommended'], + }, + + { + files: ['**/*.{ts,tsx,jsx}'], + ...pluginJs.configs['flat/recommended'], + ...pluginReact.configs.flat.recommended, + ...tseslint.configs['flat/recommended'] + }, + { + files: ['**/*.{js,cjs,mjs,ts,tsx,jsx}'], + plugins: { + '@stylistic/js': stylisticJs, + custom: { + rules: { + 'license-header': licenseHeaderRule, + }, + }, + }, + rules: { + // JS-only stylistic rules (aplikowane globalnie) + '@stylistic/js/indent': ['error', 2], + '@stylistic/js/quotes': ['error', 'single', { avoidEscape: true }], + '@stylistic/js/semi': 'error', + '@stylistic/js/space-before-blocks': 'error', + '@stylistic/js/space-infix-ops': 'error', + '@stylistic/js/object-curly-spacing': ['error', 'always'], + '@stylistic/js/keyword-spacing': 'error', + '@stylistic/js/comma-dangle': ['error', 'always-multiline'], + '@stylistic/js/comma-spacing': ['error', { before: false, after: true }], + + // other JS stylistic helpers + '@stylistic/js/array-bracket-spacing': ['error', 'never'], + '@stylistic/js/brace-style': ['error', '1tbs'], + '@stylistic/js/no-trailing-spaces': 'error', + '@stylistic/js/eol-last': ['error', 'always'], + '@stylistic/js/max-len': ['error', { code: 145 }], + '@stylistic/js/no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }], + + // keep license rule active everywhere + 'custom/license-header': 'error', + } + }, + + { + files: ['**/*.{ts,tsx,jsx}'], + plugins: { '@typescript-eslint': tseslint.plugin, 'react': pluginReact, @@ -100,21 +142,14 @@ export default [ 'jsdoc': jsdoc, '@stylistic/ts': stylisticTs, '@stylistic/js': stylisticJs, - 'custom': { - rules: { - 'license-header': licenseHeaderRule, - }, - }, }, - + languageOptions: { parser: tseslint.parser, parserOptions: { ecmaVersion: 'latest', sourceType: 'module', - ecmaFeatures: { - jsx: true, - }, + ecmaFeatures: { jsx: true }, project: './tsconfig.json', }, globals: { @@ -124,44 +159,30 @@ export default [ React: 'readonly', }, }, - + settings: { - react: { - version: 'detect', - }, - jsdoc: { - mode: 'typescript', - tagNamePreference: { - returns: 'return', - }, - }, + react: { version: 'detect' }, + jsdoc: { mode: 'typescript', tagNamePreference: { returns: 'return' } }, }, - + rules: { - // === CUSTOM RULES === - 'custom/license-header': 'error', - // === TYPESCRIPT SPECIFIC RULES === '@typescript-eslint/no-unused-vars': [ 'error', - { - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', - ignoreRestSiblings: true, - }, + { argsIgnorePattern: '^_', varsIgnorePattern: '^_', ignoreRestSiblings: true }, ], '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/prefer-nullish-coalescing': 'error', '@typescript-eslint/prefer-optional-chain': 'error', '@typescript-eslint/no-non-null-assertion': 'warn', '@typescript-eslint/consistent-type-imports': 'error', - + // === REACT SPECIFIC RULES === 'react/react-in-jsx-scope': 'off', 'react/prop-types': 'off', 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn', - + // === GENERAL CODE QUALITY === 'arrow-body-style': ['error', 'as-needed'], 'curly': 'error', @@ -174,7 +195,7 @@ export default [ 'prefer-destructuring': 'warn', 'prefer-template': 'error', 'radix': 'error', - + // === COMMENTS AND DOCUMENTATION === 'capitalized-comments': ['error', 'always'], 'jsdoc/require-description': 'error', @@ -191,8 +212,8 @@ export default [ }, }, ], - - // === TYPESCRIPT STYLISTIC RULES (only ones that exist) === + + // === TYPESCRIPT STYLISTIC RULES (moved here) === '@stylistic/ts/comma-dangle': ['error', 'always-multiline'], '@stylistic/ts/comma-spacing': ['error', { before: false, after: true }], '@stylistic/ts/indent': ['error', 2], @@ -204,15 +225,15 @@ export default [ '@stylistic/ts/keyword-spacing': 'error', '@stylistic/ts/type-annotation-spacing': 'error', '@stylistic/ts/member-delimiter-style': 'error', - - // === JAVASCRIPT STYLISTIC RULES === + + // keep JS stylistic ones if you need them additionally in TS: '@stylistic/js/array-bracket-spacing': ['error', 'never'], '@stylistic/js/brace-style': ['error', '1tbs'], '@stylistic/js/no-trailing-spaces': 'error', '@stylistic/js/eol-last': ['error', 'always'], '@stylistic/js/max-len': ['error', { code: 145 }], - "@stylistic/js/no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 0 }], - + '@stylistic/js/no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }], + // === DISABLED RULES === 'no-magic-numbers': 'off', 'sort-keys': 'off', @@ -220,4 +241,26 @@ export default [ 'sort-vars': 'off', }, }, + { + files: ['app/test/**/*.cjs'], + extends: [ + pluginJs.configs.recommended, + mochaPlugin.configs.recommended, + ], + plugins: { + mocha: mochaPlugin, + }, + languageOptions: { + sourceType: 'script', + ecmaVersion: 'latest', + globals: { + ...globals.node, + ...globals.mocha, + window: 'readonly' + }, + }, + rules: { + 'mocha/no-setup-in-describe': 'off', + }, + } ]; \ No newline at end of file diff --git a/Tokenization/webapp/package-lock.json b/Tokenization/webapp/package-lock.json index 5d4e91fff..75a710ccd 100644 --- a/Tokenization/webapp/package-lock.json +++ b/Tokenization/webapp/package-lock.json @@ -13,31 +13,32 @@ "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@mui/material": "^7.1.1", + "@react-router/dev": "^7.5.0", "@react-router/node": "^7.5.0", + "@types/node": "^20", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.1", "isbot": "^5", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-router": "^7.5.0" + "react-router": "^7.5.0", + "typescript": "^5.7.2", + "vite": "^6.0.11", + "vite-tsconfig-paths": "^5.1.4" }, "devDependencies": { "@eslint/js": "^9.28.0", - "@react-router/dev": "^7.5.0", "@stylistic/eslint-plugin-js": "^2.10.1", "@stylistic/eslint-plugin-ts": "^2.10.1", - "@types/node": "^20", - "@types/react": "^19.0.1", - "@types/react-dom": "^19.0.1", "eslint": "^9.28.0", "eslint-plugin-jsdoc": "^50.7.1", + "eslint-plugin-mocha": "^11.2.0", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.0.0", "globals": "^16.2.0", "mocha": "^10.2.0", "puppeteer": "^24.9.0", - "typescript": "^5.7.2", - "typescript-eslint": "^8.34.0", - "vite": "^6.0.11", - "vite-tsconfig-paths": "^5.1.4" + "typescript-eslint": "^8.34.0" } }, "node_modules/@aliceo2/web-ui": { @@ -65,7 +66,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -93,7 +93,6 @@ "version": "7.27.3", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz", "integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -103,7 +102,6 @@ "version": "7.27.3", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.3.tgz", "integrity": "sha512-hyrN8ivxfvJ4i0fIJuV4EOlV0WDMz5Ui4StRTgVaAvWeiRCilXgwVvxJKtFQ3TKtHgJscB2YiXKGNJuVwhQMtA==", - "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -134,7 +132,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -152,14 +149,12 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -185,7 +180,6 @@ "version": "7.27.3", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.3" @@ -198,7 +192,6 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.27.2", @@ -215,7 +208,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -225,7 +217,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -235,14 +226,12 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, "license": "ISC" }, "node_modules/@babel/helper-create-class-features-plugin": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", @@ -264,7 +253,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -274,7 +262,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -301,7 +288,6 @@ "version": "7.27.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", @@ -319,7 +305,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.1" @@ -332,7 +317,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -342,7 +326,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", @@ -360,7 +343,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -392,7 +374,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -402,7 +383,6 @@ "version": "7.27.3", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.3.tgz", "integrity": "sha512-h/eKy9agOya1IGuLaZ9tEUgz+uIRXcbtOhRtUyyMf8JFmn1iT13vnl/IGVWSkdOCG/pC57U4S1jnAabAavTMwg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", @@ -431,7 +411,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -447,7 +426,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -463,7 +441,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -479,7 +456,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.27.1", @@ -496,7 +472,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", @@ -516,7 +491,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -823,7 +797,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -840,7 +813,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -857,7 +829,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -874,7 +845,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -891,7 +861,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -908,7 +877,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -925,7 +893,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -942,7 +909,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -959,7 +925,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -976,7 +941,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -993,7 +957,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1010,7 +973,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1027,7 +989,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1044,7 +1005,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1061,7 +1021,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1078,7 +1037,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1095,7 +1053,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1112,7 +1069,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1129,7 +1085,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1146,7 +1101,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1163,7 +1117,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1180,7 +1133,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1197,7 +1149,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1214,7 +1165,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1231,7 +1181,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1549,7 +1498,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -1866,7 +1814,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz", "integrity": "sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==", - "dev": true, "license": "ISC", "dependencies": { "@npmcli/promise-spawn": "^6.0.0", @@ -1886,7 +1833,6 @@ "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -1896,7 +1842,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-4.0.1.tgz", "integrity": "sha512-lRCEGdHZomFsURroh522YvA/2cVb9oPIJrjHanCJZkiasz1BzcnLr3tBJhlV7S86MBJBuAQ33is2D60YitZL2Q==", - "dev": true, "license": "ISC", "dependencies": { "@npmcli/git": "^4.1.0", @@ -1915,7 +1860,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", - "dev": true, "license": "ISC", "dependencies": { "which": "^3.0.0" @@ -1928,7 +1872,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -2203,7 +2146,6 @@ "version": "7.6.1", "resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-7.6.1.tgz", "integrity": "sha512-E4pzxViSQ1Z4EPUz1p47ldm+qIbzfFbJtbXvxi+KSidpftf/ttjr+DtLEiTEdIqZTYv8trBASRtV6C5hn9GZQQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.21.8", @@ -2290,7 +2232,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2304,7 +2245,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2318,7 +2258,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2332,7 +2271,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2346,7 +2284,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2360,7 +2297,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2374,7 +2310,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2388,7 +2323,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2402,7 +2336,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2416,7 +2349,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2430,7 +2362,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2444,7 +2375,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2458,7 +2388,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2472,7 +2401,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2486,7 +2414,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2500,7 +2427,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2514,7 +2440,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2528,7 +2453,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2542,7 +2466,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2556,7 +2479,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2609,7 +2531,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -2653,7 +2574,6 @@ "version": "19.1.5", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz", "integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==", - "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.0.0" @@ -3094,7 +3014,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -3107,7 +3026,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -3157,7 +3075,6 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true, "license": "MIT" }, "node_modules/argparse": { @@ -3367,7 +3284,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.10.tgz", "integrity": "sha512-DV5bdJZTzZ0zn0DC24v3jD7Mnidh6xhKa4GfKCbq3sfW8kaWhDdZjP3i81geA8T33tdYqWKw4D3fVv0CwEgKVA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.23.7", @@ -3395,7 +3311,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/bare-events": { @@ -3535,7 +3450,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -3565,7 +3479,6 @@ "version": "4.24.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", - "dev": true, "funding": [ { "type": "opencollective", @@ -3629,7 +3542,6 @@ "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3709,7 +3621,6 @@ "version": "1.0.30001718", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", - "dev": true, "funding": [ { "type": "opencollective", @@ -3796,7 +3707,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, "license": "MIT", "dependencies": { "readdirp": "^4.0.1" @@ -4029,7 +3939,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, "license": "MIT" }, "node_modules/cookie": { @@ -4082,7 +3991,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -4097,7 +4005,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -4205,7 +4112,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", - "dev": true, "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" @@ -4351,7 +4257,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, "license": "MIT" }, "node_modules/ecdsa-sig-formatter": { @@ -4373,14 +4278,12 @@ "version": "1.5.158", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.158.tgz", "integrity": "sha512-9vcp2xHhkvraY6AHw2WMi+GDSLPX42qe2xjYaVoZqFRJiOcilVQFq9mZmpuHEQpzlgGDelKlV7ZiGcmMsc8WxQ==", - "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/enabled": { @@ -4422,7 +4325,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true, "license": "MIT" }, "node_modules/error-ex": { @@ -4559,7 +4461,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, "license": "MIT" }, "node_modules/es-object-atoms": { @@ -4625,7 +4526,6 @@ "version": "0.25.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -4666,7 +4566,6 @@ "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" @@ -4834,6 +4733,33 @@ "spdx-license-ids": "^3.0.0" } }, + "node_modules/eslint-plugin-mocha": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-11.2.0.tgz", + "integrity": "sha512-nMdy3tEXZac8AH5Z/9hwUkSfWu8xHf4XqwB5UEQzyTQGKcNlgFeciRAjLjliIKC3dR1Ex/a2/5sqgQzvYRkkkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.1", + "globals": "^15.14.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0" + } + }, + "node_modules/eslint-plugin-mocha/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint-plugin-react": { "version": "7.37.5", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", @@ -5115,7 +5041,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5285,7 +5210,6 @@ "version": "6.4.4", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", - "dev": true, "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" @@ -5426,7 +5350,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -5461,7 +5384,6 @@ "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -5483,7 +5405,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -5538,7 +5459,6 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -5669,7 +5589,6 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -5733,7 +5652,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "dev": true, "license": "MIT" }, "node_modules/gopd": { @@ -5752,7 +5670,6 @@ "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, "license": "ISC" }, "node_modules/graphemer": { @@ -5892,7 +5809,6 @@ "version": "6.1.3", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.3.tgz", "integrity": "sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==", - "dev": true, "license": "ISC", "dependencies": { "lru-cache": "^7.5.1" @@ -5905,7 +5821,6 @@ "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -6288,7 +6203,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6578,7 +6492,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/iterator.prototype": { @@ -6603,7 +6516,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -6676,7 +6588,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", - "dev": true, "license": "MIT", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -6700,7 +6611,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -6713,7 +6623,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -6852,7 +6761,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, "license": "MIT" }, "node_modules/lodash.includes": { @@ -7084,7 +6992,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -7100,7 +7007,6 @@ "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" @@ -7300,7 +7206,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -7345,14 +7250,12 @@ "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, "license": "MIT" }, "node_modules/normalize-package-data": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "hosted-git-info": "^6.0.0", @@ -7378,7 +7281,6 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "semver": "^7.1.1" @@ -7391,7 +7293,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", - "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -7401,7 +7302,6 @@ "version": "10.1.0", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", - "dev": true, "license": "ISC", "dependencies": { "hosted-git-info": "^6.0.0", @@ -7417,7 +7317,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.2.tgz", "integrity": "sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==", - "dev": true, "license": "ISC", "dependencies": { "npm-install-checks": "^6.0.0", @@ -7730,7 +7629,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { @@ -7809,7 +7707,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7825,7 +7722,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -7842,7 +7738,6 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, "license": "ISC" }, "node_modules/path-to-regexp": { @@ -7864,7 +7759,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, "license": "MIT" }, "node_modules/pend": { @@ -7884,7 +7778,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -7907,7 +7800,6 @@ "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -7946,7 +7838,6 @@ "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, "license": "MIT", "bin": { "prettier": "bin-prettier.js" @@ -7962,7 +7853,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", - "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -7988,14 +7878,12 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true, "license": "ISC" }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, "license": "MIT", "dependencies": { "err-code": "^2.0.2", @@ -8335,7 +8223,6 @@ "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8413,7 +8300,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 14.18.0" @@ -8510,7 +8396,6 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -8531,7 +8416,6 @@ "version": "4.41.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.7" @@ -8842,7 +8726,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -8855,7 +8738,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8937,7 +8819,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -9034,7 +8915,6 @@ "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==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -9054,7 +8934,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, "license": "Apache-2.0", "dependencies": { "spdx-expression-parse": "^3.0.0", @@ -9065,14 +8944,12 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", @@ -9083,7 +8960,6 @@ "version": "3.0.21", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", - "dev": true, "license": "CC0-1.0" }, "node_modules/sqlstring": { @@ -9166,7 +9042,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -9185,7 +9060,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -9200,7 +9074,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9210,14 +9083,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -9328,7 +9199,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -9345,7 +9215,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -9358,7 +9227,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9458,7 +9326,6 @@ "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.4.4", @@ -9519,7 +9386,6 @@ "version": "3.1.6", "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", - "dev": true, "license": "MIT", "bin": { "tsconfck": "bin/tsconfck.js" @@ -9658,7 +9524,6 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -9730,7 +9595,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 10.0.0" @@ -9749,7 +9613,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, "funding": [ { "type": "opencollective", @@ -9805,7 +9668,6 @@ "version": "0.41.0", "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.41.0.tgz", "integrity": "sha512-igDBb8CTYr8YTQlOKgaN9nSS0Be7z+WRuaeYqGf3Cjz3aKmSnqEmYnkfVjzIuumGqfHpa3fLIvMEAfhrpqN8ng==", - "dev": true, "license": "MIT", "peerDependencies": { "typescript": ">=5" @@ -9820,7 +9682,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, "license": "Apache-2.0", "dependencies": { "spdx-correct": "^3.0.0", @@ -9831,7 +9692,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", - "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -9850,7 +9710,6 @@ "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", @@ -9925,7 +9784,6 @@ "version": "3.0.0-beta.2", "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.0-beta.2.tgz", "integrity": "sha512-ofTf6cfRdL30Wbl9n/BX81EyIR5s4PReLmSurrxQ+koLaWUNOEo8E0lCM53OJkb8vpa2URM2nSrxZsIFyvY1rg==", - "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", @@ -9948,7 +9806,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -9966,14 +9823,12 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/vite-tsconfig-paths": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", - "dev": true, "license": "MIT", "dependencies": { "debug": "^4.1.1", @@ -9993,7 +9848,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -10011,14 +9865,12 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/which": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -10211,7 +10063,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -10230,7 +10081,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -10248,7 +10098,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10258,7 +10107,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -10274,7 +10122,6 @@ "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, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -10287,21 +10134,18 @@ "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, "license": "MIT" }, "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", @@ -10316,7 +10160,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -10373,7 +10216,6 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", - "dev": true, "license": "ISC", "optional": true, "peer": true, diff --git a/Tokenization/webapp/package.json b/Tokenization/webapp/package.json index 2c3598cb4..5cacddfdb 100644 --- a/Tokenization/webapp/package.json +++ b/Tokenization/webapp/package.json @@ -8,13 +8,13 @@ "build": "react-router build", "dev": "react-router dev", "start": "react-router-serve ./build/server/index.js", - "test": "mocha --exit tests/**/*.js", + "test": "mocha --exit tests/mocha-index.cjs", "typecheck": "react-router typegen && tsc", "docker:typecheck": "docker compose exec webapp npm run typecheck", "docker:test:build": "cd .. && docker compose -f docker-compose.test.yml up --build --abort-on-container-exit ui-tests && docker compose -f docker-compose.test.yml stop && cd ./webapp", "docker:test": "cd .. && docker compose -f docker-compose.test.yml up --abort-on-container-exit ui-tests && docker compose -f docker-compose.test.yml stop && cd ./webapp", - "lint": "eslint app --ignore-pattern 'app/styles' --no-warn-ignored", - "lint:fix": "eslint --fix app --ignore-pattern 'app/styles' --no-warn-ignored" + "lint": "eslint app tests --ignore-pattern 'app/styles' --no-warn-ignored", + "lint:fix": "eslint --fix app tests --ignore-pattern 'app/styles' --no-warn-ignored" }, "repository": { "type": "git", @@ -31,30 +31,31 @@ "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@mui/material": "^7.1.1", + "@react-router/dev": "^7.5.0", "@react-router/node": "^7.5.0", + "@types/node": "^20", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.1", "isbot": "^5", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router": "^7.5.0", - "@react-router/dev": "^7.5.0", - "@types/node": "^20", - "@types/react": "^19.0.1", - "@types/react-dom": "^19.0.1", "typescript": "^5.7.2", "vite": "^6.0.11", "vite-tsconfig-paths": "^5.1.4" }, "devDependencies": { - "mocha": "^10.2.0", - "puppeteer": "^24.9.0", "@eslint/js": "^9.28.0", - "@stylistic/eslint-plugin-ts": "^2.10.1", "@stylistic/eslint-plugin-js": "^2.10.1", - "typescript-eslint": "^8.34.0", + "@stylistic/eslint-plugin-ts": "^2.10.1", "eslint": "^9.28.0", "eslint-plugin-jsdoc": "^50.7.1", + "eslint-plugin-mocha": "^11.2.0", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.0.0", - "globals": "^16.2.0" + "globals": "^16.2.0", + "mocha": "^10.2.0", + "puppeteer": "^24.9.0", + "typescript-eslint": "^8.34.0" } } diff --git a/Tokenization/webapp/tests/config.cjs b/Tokenization/webapp/tests/config.cjs new file mode 100644 index 000000000..56042109a --- /dev/null +++ b/Tokenization/webapp/tests/config.cjs @@ -0,0 +1,20 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +module.exports = { + http: { + port: 80, + hostname: 'prod-container', + }, +}; diff --git a/Tokenization/webapp/tests/main.test.js b/Tokenization/webapp/tests/main.test.js deleted file mode 100644 index fda6782b1..000000000 --- a/Tokenization/webapp/tests/main.test.js +++ /dev/null @@ -1,23 +0,0 @@ -import assert from 'assert' -import puppeteer from 'puppeteer' - -describe('Puppeteer test', () => { - it('Pupeteer launch', async () => { - const browser = await puppeteer.launch({ - headless: true, - executablePath: process.env.PUPPETEER_EXECUTABLE_PATH, - args: ['--no-sandbox'] - }) - - - const page = await browser.newPage(); - - await page.goto('http://prod-container:80/') - - await page.waitForSelector('header') - const headerContent = await page.$eval('header', el => el.textContent) - assert.ok(headerContent.includes('Tokenization')) - - await browser.close() - }); -}) \ No newline at end of file diff --git a/Tokenization/webapp/tests/mocha-index.cjs b/Tokenization/webapp/tests/mocha-index.cjs new file mode 100644 index 000000000..2133154d4 --- /dev/null +++ b/Tokenization/webapp/tests/mocha-index.cjs @@ -0,0 +1,77 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +const puppeteer = require('puppeteer'); +const config = require('./config.cjs'); + +let browser; +let page; +let url; + +global.test = { + page: null, + helpers: {}, +}; + +describe('Tokenization', function() { + + before(async function() { + this.timeout(50000); + this.slow(1000); + url = `http://${config.http.hostname}:${config.http.port}`; + + browser = await puppeteer.launch({ + headless: true, + executablePath: process.env.PUPPETEER_EXECUTABLE_PATH, + args: ['--no-sandbox'], + }); + + page = await browser.newPage(); + + page.on('error', (pageerror) => { + console.error(' ', pageerror); + this.ok = false; + }); + page.on('pageerror', (pageerror) => { + console.error(' ', pageerror); + this.ok = false; + }); + page.on('console', (msg) => { + for (let i = 0; i < msg.args().length; ++i) { + console.log(` ${msg.args()[i]}`); + } + }); + await page.setViewport({ width: 1600, height: 900 }); + + global.test.page = page; + global.test.helpers.url = url; + }); + + require('./public/basic.cjs'); + + beforeEach(function () { + return (this.ok = true); + }); + + afterEach(function () { + if (!this.ok) { + throw new Error('something went wrong'); + } + }); + + after(async function () { + await browser.close(); + }); + +}); diff --git a/Tokenization/webapp/tests/public/basic.cjs b/Tokenization/webapp/tests/public/basic.cjs new file mode 100644 index 000000000..04ca5d441 --- /dev/null +++ b/Tokenization/webapp/tests/public/basic.cjs @@ -0,0 +1,38 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +const assert = require('assert'); + +describe('no-label tests', function() { + let url; + let page; + + before(async function() { + ({ helpers: { url: url }, page: page } = test); + }); + + it('header content changes with navigation', async function() { + let headerContent; + + await page.goto(url); + await page.waitForSelector('header'); + headerContent = await page.$eval('header', el => el.textContent); + assert.ok(headerContent.includes('Tokenization')); + + await page.goto(`${url }/tokens`); + await page.waitForSelector('header'); + headerContent = await page.$eval('header', el => el.textContent); + assert.ok(headerContent.includes('Tokens')); + }); +}); From 0114076bcf010506208cd780e6de521fecadd4c0 Mon Sep 17 00:00:00 2001 From: Igor Sala Date: Wed, 8 Oct 2025 01:43:14 +0200 Subject: [PATCH 21/47] Added ui-tests for creating tokens --- .../webapp/app/routes/tokens/create.tsx | 32 ++-- Tokenization/webapp/app/ui/alert.tsx | 12 +- Tokenization/webapp/eslint.config.js | 5 +- Tokenization/webapp/tests/mocha-index.cjs | 1 + Tokenization/webapp/tests/public/basic.cjs | 4 +- .../webapp/tests/public/token-tests.cjs | 141 ++++++++++++++++++ 6 files changed, 167 insertions(+), 28 deletions(-) create mode 100644 Tokenization/webapp/tests/public/token-tests.cjs diff --git a/Tokenization/webapp/app/routes/tokens/create.tsx b/Tokenization/webapp/app/routes/tokens/create.tsx index 2522d3cdd..723948632 100644 --- a/Tokenization/webapp/app/routes/tokens/create.tsx +++ b/Tokenization/webapp/app/routes/tokens/create.tsx @@ -22,7 +22,7 @@ import { import { DangerAlert } from '~/ui/alert'; import { CreationTokenDialog } from '~/ui/dialog'; -import { useAuth, useSession } from '~/hooks/session'; +import { useAuth } from '~/hooks/session'; export interface OptionType { value: string; @@ -67,7 +67,7 @@ export default function CreateToken({ loaderData }: { loaderData?: OptionType[] const options: OptionType[] = loaderData ?? clientLoader(); - const auth = useAuth('admin') + const auth = useAuth('admin'); const resetForm = useCallback(() => { setExpirationTime(''); @@ -77,16 +77,16 @@ export default function CreateToken({ loaderData }: { loaderData?: OptionType[] }, []); const handleCreateToken = useCallback(() => { - if(auth) - { + if (auth) { + // eslint-disable-next-line no-console console.log('Creating token...', { first: firstSelectedService, second: secondSelectedService, expiration: expirationTime, permissions: selectedMethods, - }); - }else{ - setSnackbarMessage("Authorization error"); + }); + } else { + setSnackbarMessage('Authorization error'); setOpenSnackbar(true); } @@ -109,8 +109,8 @@ export default function CreateToken({ loaderData }: { loaderData?: OptionType[] if (!expirationTime) { message += 'Expiration time, '; } - if(selectedMethods.length == 0) { - message += 'HTTP methods, ' + if (selectedMethods.length == 0) { + message += 'HTTP methods, '; } message = message.slice(0, -2); setSnackbarMessage(message); @@ -128,6 +128,7 @@ export default function CreateToken({ loaderData }: { loaderData?: OptionType[]
+ id="first-service-select" options={options} value={firstSelectedOption} onChange={(srv: SingleValue) => { @@ -136,10 +137,11 @@ export default function CreateToken({ loaderData }: { loaderData?: OptionType[] placeholder="Select first service..." />
- +
+ id="second-service-select" options={options} value={secondSelectedOption} onChange={(srv: SingleValue) => { @@ -152,6 +154,7 @@ export default function CreateToken({ loaderData }: { loaderData?: OptionType[]
+ ; +} + +// eslint-disable-next-line jsdoc/require-jsdoc export default function CreateToken({ loaderData }: { loaderData?: OptionType[] }) { const navigate = useNavigate(); const [expirationTime, setExpirationTime] = useState(''); @@ -125,49 +163,56 @@ export default function CreateToken({ loaderData }: { loaderData?: OptionType[]

Create New Token

-
- - - id="first-service-select" - options={options} - value={firstSelectedOption} - onChange={(srv: SingleValue) => { - setFirstSelectedService(srv ? srv.value : ''); - }} - placeholder="Select first service..." - /> -
+ | MultiValue) => { + if (Array.isArray(srv)) { + setFirstSelectedService(''); + return; + } + const srv1 = srv as SingleValue; + setFirstSelectedService(srv1 ? srv1.value : ''); + }} + placeholder="Select first service..." + /> -
- - - id="second-service-select" - options={options} - value={secondSelectedOption} - onChange={(srv: SingleValue) => { - setSecondSelectedService(srv ? srv.value : ''); - }} - placeholder="Select second service..." - /> -
+ | MultiValue) => { + if (Array.isArray(srv)) { + setSecondSelectedService(''); + return; + } + const srv1 = srv as SingleValue; + setSecondSelectedService(srv1 ? srv1.value : ''); + }} + placeholder="Select second service..." + /> -
- - +
+ ); +}; diff --git a/Tokenization/webapp/app/components/form/form-select.tsx b/Tokenization/webapp/app/components/form/form-select.tsx new file mode 100644 index 000000000..b8404810c --- /dev/null +++ b/Tokenization/webapp/app/components/form/form-select.tsx @@ -0,0 +1,37 @@ +import React, { type SetStateAction } from "react" +import { type OptionType as Option} from "~/utils/types" + +interface SelectInterface { + id: string; + options: Option[]; + placeholder?: string; + label: string | null; + value: T //| T[]; + setValue: React.Dispatch> +} + +export const FormSelect = (props: SelectInterface) => { + const {id,options, placeholder, label, value, setValue} = props; + + return ( +
+ + +
+ + ) +} \ No newline at end of file diff --git a/Tokenization/webapp/app/components/form/form.tsx b/Tokenization/webapp/app/components/form/form.tsx new file mode 100644 index 000000000..a18aba5b6 --- /dev/null +++ b/Tokenization/webapp/app/components/form/form.tsx @@ -0,0 +1,18 @@ +import React from "react" + +interface FormInterface extends React.HTMLAttributes {} + + +export const Form = ({children, className, onSubmit, id}: FormInterface) => { + const _className = className ?? '' + return ( +
+ + {children} + +
+ ) +} \ No newline at end of file diff --git a/Tokenization/webapp/app/routes/tokens/create.tsx b/Tokenization/webapp/app/routes/tokens/create.tsx index 2d88c059d..28924372a 100644 --- a/Tokenization/webapp/app/routes/tokens/create.tsx +++ b/Tokenization/webapp/app/routes/tokens/create.tsx @@ -15,15 +15,19 @@ import { useNavigate } from 'react-router'; import Select, { type MultiValue, type SingleValue } from 'react-select'; import { useState, useCallback } from 'react'; -import { - TextField, - InputAdornment, -} from '@mui/material'; +// import { +// TextField, +// InputAdornment, +// } from '@mui/material'; import type { OptionType, HttpMethod } from '~/utils/types'; -import { DangerAlert } from '~/ui/alert'; -import { CreationTokenDialog } from '~/ui/dialog'; -import { useAuth } from '~/hooks/session'; +// import { DangerAlert } from '~/ui/alert'; +// import { CreationTokenDialog } from '~/ui/dialog'; +// import { useAuth } from '~/hooks/session'; +import { Form } from '~/components/form/form'; +import { Box1_2 } from '~/components/box'; +import { FormInput } from '~/components/form/form-input'; +import { FormSelect } from '~/components/form/form-select'; // eslint-disable-next-line jsdoc/require-jsdoc export function clientLoader(): OptionType[] { @@ -43,223 +47,42 @@ const httpMethodOptions = [ { value: 'DELETE', label: 'DELETE' }, ]; -/** - * FormCreationInput - * - * Container component for form inputs in the token creation form. - * - * @param props.children element input/select/itp. - * @param props.labelText Optional label text to display above the input field. - */ -function FormCreationInput({ children, labelText }: { children: React.ReactNode; labelText?: string }) { - return
- - {children} -
; -} - -interface FormCreationSelectInputProps { - id: string; - labelText?: string; - options: OptionType[]; - value: MultiValue | SingleValue; - onChange: (value: MultiValue | SingleValue) => void; - placeholder: string; - isMulti?: boolean; -} - -/** - * FormCreationSelectInput - * - * Container component for select inputs in the token creation form. - * - * @param props.id The id of the select input. - * @param props.labelText Optional label text to display above the select field. - * @param props.options The options to display in the select dropdown. - * @param props.value The currently selected value(s). - * @param props.onChange Callback function to handle changes in selection. - * @param props.placeholder Placeholder text for the select input. - * @param props.isMulti Boolean indicating if multiple selections are allowed. - */ -function FormCreationSelectInput(props: FormCreationSelectInputProps) { - const { labelText, ...rest } = props; - return - - - { - options.map((option) => ( - option.value !== value && - )) - } - -
- - ) +export function FormSelect({ + id, + options = [], + placeholder = 'Wybierz...', + label, + value, + setValue, +}: SelectInterface) { + const [open, setOpen] = useState(false); + const rootRef = useRef(null); + + const selected = options.find((o) => o.value === value) || null; + const visibleOptions = options.filter((o) => o.value !== value); + + useEffect(() => { + function handleOutsideClick(e: MouseEvent) { + if (rootRef.current && !rootRef.current.contains(e.target as Node)) { + setOpen(false); + } + } + document.addEventListener('mousedown', handleOutsideClick); + return () => document.removeEventListener('mousedown', handleOutsideClick); + }, []); + + const handleSelect = (val: T) => { + setValue(val); + setOpen(false); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Escape') setOpen(false); + if (e.key === 'Enter') setOpen((prev) => !prev); + }; + + return ( +
+ {label && } + +
setOpen((prev) => !prev)} + onKeyDown={handleKeyDown} + aria-haspopup="listbox" + aria-expanded={open} + className="custom-select__trigger" + > + + {selected ? selected.label : {placeholder}} + + {open ? '▴' : '▾'} +
+ + {open && ( +
    + {visibleOptions.length > 0 ? ( + visibleOptions.map((opt) => ( +
  • handleSelect(opt.value as T)} + onKeyDown={(e) => e.key === 'Enter' && handleSelect(opt.value as T)} + > + {opt.label} +
  • + )) + ) : ( +
  • Brak innych opcji
  • + )} +
+ )} +
+ ); } \ No newline at end of file From a256e5cde4c170323b876ab18de0e88231eb6e7d Mon Sep 17 00:00:00 2001 From: Igor S Date: Thu, 13 Nov 2025 21:57:51 +0100 Subject: [PATCH 28/47] MultiSelect logic created --- Tokenization/webapp/app/app.css | 5 +- .../app/components/form/form-select.tsx | 206 +++++++++++++----- .../webapp/app/routes/tokens/create.tsx | 43 ++-- Tokenization/webapp/app/utils/types.ts | 5 + 4 files changed, 190 insertions(+), 69 deletions(-) diff --git a/Tokenization/webapp/app/app.css b/Tokenization/webapp/app/app.css index 01349af52..4e2b38b08 100644 --- a/Tokenization/webapp/app/app.css +++ b/Tokenization/webapp/app/app.css @@ -32,11 +32,14 @@ .mb2 { margin-bottom: var(--space-s)} - .no-border { border: 0; } +.bra2 { + border: 2px solid black; +} + .brb2 { border-bottom: 2px solid black; } diff --git a/Tokenization/webapp/app/components/form/form-select.tsx b/Tokenization/webapp/app/components/form/form-select.tsx index 2c4038e69..eb54b8391 100644 --- a/Tokenization/webapp/app/components/form/form-select.tsx +++ b/Tokenization/webapp/app/components/form/form-select.tsx @@ -1,7 +1,7 @@ -import React, { useState, useRef, useEffect, type SetStateAction } from 'react'; -import { type OptionType as Option } from '~/utils/types'; +import React, { useEffect, useRef, useState, type SetStateAction, type JSX, type PropsWithChildren } from 'react'; +import { type OptionType as Option, type DialogPropsBase as DPB } from '~/utils/types'; -interface SelectInterface { +interface SelectInterface { id: string; options: Option[]; placeholder?: string; @@ -10,19 +10,94 @@ interface SelectInterface { setValue: React.Dispatch>; } -export function FormSelect({ +interface SelectLabelProps extends DPB { + selected: Option | Option[] | null; + placeholder: string; +} + +interface SelectOptionsProps extends DPB { + options: Option[]; + handleSelect?: (value: T) => void; +} + +const SelectFrame = ({open, setOpen, selected, placeholder}: SelectLabelProps) => { + let _selected: Option | null = Array.isArray(selected) ? (selected.length > 0 ? selected[0] : null) : selected; + + return (
setOpen((prev) => !prev)} + className="flex-row border p2 justify-between br2 bg-white bra2" + > + + {_selected ? _selected.label : {placeholder}} + + {open ? '▴' : '▾'} +
+ ) +} + +const SelectFrameMulti = (props: SelectLabelProps) => { + let {open, setOpen, selected, placeholder} = props + let _selected: Option[] | null = Array.isArray(selected) ? selected : (selected ? [selected] : null); + + return (
setOpen((prev) => !prev)} + className="flex-row border p2 justify-between br2 bg-white bra2" + > + + {_selected && + _selected.length > 0 ? + _selected.map((s) => s.label).join(', '): + {placeholder}} + + {open ? '▴' : '▾'} +
+ ) + +} + +const SelectOptions = ({open, setOpen, handleSelect, options}: SelectOptionsProps) => { + const visibleOptions = options; + const _handleSelect = (val: T) => { + handleSelect && handleSelect(val); + setOpen(false); + } + + return ( + <> + {open && ( +
    + {visibleOptions.length > 0 ? ( + visibleOptions.map((opt) => ( +
  • _handleSelect(opt.value as T)} + className="f4 menu-item m0" + > + {opt.label} +
  • + )) + ) : ( +
  • No options available
  • + )} +
+ )} + + ) + +} + +export const FormSelect = ({ id, options = [], - placeholder = 'Wybierz...', + placeholder = 'Choose an option...', label, value, setValue, -}: SelectInterface) { +}: SelectInterface): JSX.Element => { const [open, setOpen] = useState(false); const rootRef = useRef(null); const selected = options.find((o) => o.value === value) || null; - const visibleOptions = options.filter((o) => o.value !== value); useEffect(() => { function handleOutsideClick(e: MouseEvent) { @@ -34,55 +109,84 @@ export function FormSelect({ return () => document.removeEventListener('mousedown', handleOutsideClick); }, []); + const onClickExpand = () => { + setOpen((prev) => !prev); + } + const handleSelect = (val: T) => { setValue(val); - setOpen(false); - }; - - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Escape') setOpen(false); - if (e.key === 'Enter') setOpen((prev) => !prev); - }; + } return ( -
- {label && } +
+ {label && {label}} + + -
setOpen((prev) => !prev)} - onKeyDown={handleKeyDown} - aria-haspopup="listbox" - aria-expanded={open} - className="custom-select__trigger" - > - - {selected ? selected.label : {placeholder}} - - {open ? '▴' : '▾'} -
+
+ ); +} - {open && ( -
    - {visibleOptions.length > 0 ? ( - visibleOptions.map((opt) => ( -
  • handleSelect(opt.value as T)} - onKeyDown={(e) => e.key === 'Enter' && handleSelect(opt.value as T)} - > - {opt.label} -
  • - )) - ) : ( -
  • Brak innych opcji
  • - )} -
- )} +export const FormSelectMulti = ({ + id, + options = [], + placeholder = 'Choose options...', + label, + value, + setValue, +}: SelectInterface): JSX.Element => { + const [open, setOpen] = useState(false); + const rootRef = useRef(null); + + const selected = options.filter((o) => value.includes(o.value as unknown as T)) || []; + + useEffect(() => { + function handleOutsideClick(e: MouseEvent) { + if (rootRef.current && !rootRef.current.contains(e.target as Node)) { + setOpen(false); + } + } + document.addEventListener('mousedown', handleOutsideClick); + return () => document.removeEventListener('mousedown', handleOutsideClick); + }, []); + + const onClickExpand = () => { + setOpen((prev) => !prev); + } + + const handleSelect = (val: T) => { + setValue((prev) => { + return [...prev, val]; + }) + } + + return ( +
+ {label && {label}} + +
); -} \ No newline at end of file +} + + diff --git a/Tokenization/webapp/app/routes/tokens/create.tsx b/Tokenization/webapp/app/routes/tokens/create.tsx index 28924372a..ef384fdf3 100644 --- a/Tokenization/webapp/app/routes/tokens/create.tsx +++ b/Tokenization/webapp/app/routes/tokens/create.tsx @@ -27,7 +27,7 @@ import type { OptionType, HttpMethod } from '~/utils/types'; import { Form } from '~/components/form/form'; import { Box1_2 } from '~/components/box'; import { FormInput } from '~/components/form/form-input'; -import { FormSelect } from '~/components/form/form-select'; +import { FormSelect, FormSelectMulti } from '~/components/form/form-select'; // eslint-disable-next-line jsdoc/require-jsdoc export function clientLoader(): OptionType[] { @@ -62,25 +62,34 @@ export default function CreateToken({ loaderData }: { loaderData?: OptionType[] labelText="Expiration Time (hours):" value={expirationTime} setValue={setExpirationTime} - inputProps={{step: 0.1}} + inputProps={{step: 1, min: 0}} /> - {loaderData && <> + {loaderData && <> + } - + id="second-service-select" + options={loaderData} + value={secondSelectedService} + setValue={setSecondSelectedService} + placeholder="Select Second Service..." + label="Second Service" + /> + } diff --git a/Tokenization/webapp/app/utils/types.ts b/Tokenization/webapp/app/utils/types.ts index 743c48a13..603ac464f 100644 --- a/Tokenization/webapp/app/utils/types.ts +++ b/Tokenization/webapp/app/utils/types.ts @@ -18,3 +18,8 @@ export interface OptionType { } export type HttpMethod = 'GET' | 'POST' | 'DELETE' | 'PUT'; + +export interface DialogPropsBase { + open: boolean; + setOpen: React.Dispatch>; +} From 2a85fbfbac824fc049deea464f15c244c51403a1 Mon Sep 17 00:00:00 2001 From: Igor S Date: Sat, 15 Nov 2025 16:09:27 +0100 Subject: [PATCH 29/47] React-select logic and linting for whole pr --- Tokenization/webapp/app/app.css | 22 +- Tokenization/webapp/app/components/box.tsx | 86 +++--- .../webapp/app/components/form/form-input.tsx | 33 ++- .../app/components/form/form-select.tsx | 254 +++++++++--------- .../webapp/app/components/form/form.tsx | 43 +-- .../components/form/multi-select-helper.tsx | 41 +++ Tokenization/webapp/app/components/modal.tsx | 238 ++++++++-------- .../app/components/tokens/action-block.tsx | 1 - .../app/components/tokens/token-table.tsx | 101 ++++--- Tokenization/webapp/app/routes.ts | 4 +- .../webapp/app/routes/tokens/create.tsx | 25 +- .../webapp/app/routes/tokens/overview.tsx | 33 ++- .../webapp/app/styles/components-styles.css | 27 +- Tokenization/webapp/app/ui/header/header.tsx | 6 +- Tokenization/webapp/app/ui/layout.tsx | 18 +- Tokenization/webapp/app/ui/sidebar.tsx | 21 +- 16 files changed, 552 insertions(+), 401 deletions(-) create mode 100644 Tokenization/webapp/app/components/form/multi-select-helper.tsx diff --git a/Tokenization/webapp/app/app.css b/Tokenization/webapp/app/app.css index 4e2b38b08..8127b9789 100644 --- a/Tokenization/webapp/app/app.css +++ b/Tokenization/webapp/app/app.css @@ -12,8 +12,12 @@ * or submit itself to any jurisdiction. */ +:root { + --color-transparent: rgba(0,0,0,0) +} + .bg-transparent { - background-color: rgba(0,0,0,0); + background-color: var(--color-transparent); } .justify-end { @@ -55,3 +59,19 @@ .scale25 { transform: scale(2.5); } + +.menu-item-static { + /* This class is the same as menu-item from aliceo2 css framework but can be used wihouth hover and active actions*/ + text-decoration: none; + height: 2em; + line-height: 2em; + padding-left: 1em; + padding-right: 1em; + color: var(--color-gray-darker); + font-weight: 100; + margin: 0.25em; + border-radius: 0.25em; + display: block; + user-select: none; +} + diff --git a/Tokenization/webapp/app/components/box.tsx b/Tokenization/webapp/app/components/box.tsx index 19ba0ae57..27018cd5a 100644 --- a/Tokenization/webapp/app/components/box.tsx +++ b/Tokenization/webapp/app/components/box.tsx @@ -1,46 +1,56 @@ -import React from "react"; -import {Link} from "react-router" -import { IconArrowCircleRight, IconContainer } from "~/ui/icon"; +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ -interface BoxInterface{ - children: React.ReactNode; - link: string | null +import React from 'react'; +import { Link } from 'react-router'; +import { IconArrowCircleRight, IconContainer } from '~/ui/icon'; + +interface BoxInterface { + children: React.ReactNode; + link: string | null; } interface PrimaryBoxInterface extends BoxInterface { - className_div1: string, - className_div2: string + className_div1: string; + className_div2: string; } -export const Box = ({children, link, className_div1, className_div2}: PrimaryBoxInterface) => { - return ( -
-
-
- {link && - - - - } -
-
-
-
- {children} -
-
+export const Box = ({ children, link, className_div1, className_div2 }: PrimaryBoxInterface) => ( +
+
+
+ {link && + + + + } +
- ) -} - -export const Box1_2 = ({children, link}: BoxInterface) => { - return ( - +
+
{children} - - ) -} \ No newline at end of file +
+
+
+); + +export const Box1_2 = ({ children, link }: BoxInterface) => ( + + {children} + +); diff --git a/Tokenization/webapp/app/components/form/form-input.tsx b/Tokenization/webapp/app/components/form/form-input.tsx index 3ad931d58..36ce18f46 100644 --- a/Tokenization/webapp/app/components/form/form-input.tsx +++ b/Tokenization/webapp/app/components/form/form-input.tsx @@ -1,4 +1,18 @@ -import React from "react"; +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import React from 'react'; export interface FormInputProps { value: T; @@ -18,21 +32,20 @@ export const FormInput = ({ inputProps, }: FormInputProps) => { const inputId = inputProps?.id ?? labelProps?.htmlFor ?? undefined; - const type = typeof value === 'string' ? 'text' : 'number' + const type = typeof value === 'string' ? 'text' : 'number'; const handleChange = (e: React.ChangeEvent) => { - const target = e.target; + const { target } = e; let newVal: T; - if(typeof value === "number") { - const parsed = parseFloat(target.value) - newVal = (isNaN(parsed) ? 0 : parsed) as T; - }else { - newVal = target.value as T; - } + if (typeof value === 'number') { + const parsed = parseFloat(target.value); + newVal = (isNaN(parsed) ? 0 : parsed) as T; + } else { + newVal = target.value as T; + } setValue(newVal); }; - return (
{labelText && ( diff --git a/Tokenization/webapp/app/components/form/form-select.tsx b/Tokenization/webapp/app/components/form/form-select.tsx index eb54b8391..05b2f01d9 100644 --- a/Tokenization/webapp/app/components/form/form-select.tsx +++ b/Tokenization/webapp/app/components/form/form-select.tsx @@ -1,5 +1,20 @@ -import React, { useEffect, useRef, useState, type SetStateAction, type JSX, type PropsWithChildren } from 'react'; +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import React, { useEffect, useRef, useState, type SetStateAction, type JSX } from 'react'; import { type OptionType as Option, type DialogPropsBase as DPB } from '~/utils/types'; +import { SelectedList } from './multi-select-helper'; interface SelectInterface { id: string; @@ -8,97 +23,110 @@ interface SelectInterface { label: string | null; value: T; setValue: React.Dispatch>; + selected?: Option | Option[] | null; + handleSelect?: (value: T) => void; + handleDeselect?: (value: T) => void; + takeSelectedToOption?: boolean; + render?: React.ElementType; } -interface SelectLabelProps extends DPB { +interface SelectLabelProps extends DPB { selected: Option | Option[] | null; placeholder: string; + handleDeselect?: (value: T) => void; } -interface SelectOptionsProps extends DPB { +interface SelectOptionsProps extends DPB { options: Option[]; + selected?: Option | Option[] | null; + takeSelectedToOption?: boolean; handleSelect?: (value: T) => void; + } -const SelectFrame = ({open, setOpen, selected, placeholder}: SelectLabelProps) => { - let _selected: Option | null = Array.isArray(selected) ? (selected.length > 0 ? selected[0] : null) : selected; - +const SelectFrame = (props: SelectLabelProps) => { + const { open, setOpen, selected, placeholder } = props; + const _selected: Option | null = Array.isArray(selected) ? (selected.length > 0 ? selected[0] : null) : selected; + return (
setOpen((prev) => !prev)} - className="flex-row border p2 justify-between br2 bg-white bra2" - > - - {_selected ? _selected.label : {placeholder}} - - {open ? '▴' : '▾'} -
- ) -} + onClick={() => setOpen((prev) => !prev)} + className="flex-row border p2 justify-between br2 bg-white bra2" + > + + {_selected ? _selected.label : {placeholder}} + + {open ? '▴' : '▾'} +
+ ); +}; -const SelectFrameMulti = (props: SelectLabelProps) => { - let {open, setOpen, selected, placeholder} = props - let _selected: Option[] | null = Array.isArray(selected) ? selected : (selected ? [selected] : null); +const SelectFrameMulti = (props: SelectLabelProps) => { + const { open, setOpen, selected, placeholder, handleDeselect } = props; + const _selected: Option[] | null = Array.isArray(selected) ? selected : (selected ? [selected] : null); return (
setOpen((prev) => !prev)} - className="flex-row border p2 justify-between br2 bg-white bra2" - > - - {_selected && - _selected.length > 0 ? - _selected.map((s) => s.label).join(', '): - {placeholder}} - - {open ? '▴' : '▾'} -
- ) + onClick={() => setOpen((prev) => !prev)} + className="flex-row border p2 justify-between br2 bg-white bra2" + > + + {_selected && + _selected.length > 0 ? + : + {placeholder} + } + + {open ? '▴' : '▾'} +
+ ); -} +}; + +const SelectOptions = ({ open, setOpen, handleSelect, options, selected, takeSelectedToOption = true }: SelectOptionsProps) => { + const _selected = Array.isArray(selected) ? selected : [selected]; + const visibleOptions = takeSelectedToOption ? options : options.filter((opt) => !(_selected.includes(opt))); -const SelectOptions = ({open, setOpen, handleSelect, options}: SelectOptionsProps) => { - const visibleOptions = options; const _handleSelect = (val: T) => { - handleSelect && handleSelect(val); + handleSelect?.(val); setOpen(false); - } + }; - return ( + return ( <> {open && ( -
    - {visibleOptions.length > 0 ? ( - visibleOptions.map((opt) => ( -
  • _handleSelect(opt.value as T)} - className="f4 menu-item m0" - > - {opt.label} -
  • - )) - ) : ( -
  • No options available
  • - )} -
+
    + {visibleOptions.length > 0 ? ( + visibleOptions.map((opt) => ( +
  • _handleSelect(opt.value as T)} + className="f4 menu-item m0" + > + {opt.label} +
  • + )) + ) : ( +
  • No options available
  • + )} +
)} - ) - -} + ); +}; -export const FormSelect = ({ +const FormSelectBase = ({ id, options = [], - placeholder = 'Choose an option...', + placeholder = 'Choose an option', label, - value, - setValue, -}: SelectInterface): JSX.Element => { + selected, + handleSelect, + handleDeselect, + takeSelectedToOption, + render, +}: SelectInterface) => { const [open, setOpen] = useState(false); const rootRef = useRef(null); - const selected = options.find((o) => o.value === value) || null; - useEffect(() => { function handleOutsideClick(e: MouseEvent) { if (rootRef.current && !rootRef.current.contains(e.target as Node)) { @@ -111,82 +139,66 @@ export const FormSelect = ({ const onClickExpand = () => { setOpen((prev) => !prev); - } + }; - const handleSelect = (val: T) => { - setValue(val); - } + const selectFrame = render ? + React.createElement(render, { open, setOpen, selected, placeholder, handleDeselect, takeSelectedToOption }) + : null; return (
{label && {label}} - - + open={open} setOpen={setOpen} options={options} handleSelect={handleSelect} + selected={selected} + takeSelectedToOption={takeSelectedToOption} /> -
); -} +}; -export const FormSelectMulti = ({ - id, - options = [], - placeholder = 'Choose options...', - label, - value, - setValue, -}: SelectInterface): JSX.Element => { - const [open, setOpen] = useState(false); - const rootRef = useRef(null); +export const FormSelect = (props: SelectInterface): JSX.Element => { + const { value, setValue, options } = { ...props }; + const selected = options.find((o) => o.value === value) ?? null; + const handleSelect = (val: T) => { + setValue(val); + }; - const selected = options.filter((o) => value.includes(o.value as unknown as T)) || []; + return ( + - useEffect(() => { - function handleOutsideClick(e: MouseEvent) { - if (rootRef.current && !rootRef.current.contains(e.target as Node)) { - setOpen(false); - } - } - document.addEventListener('mousedown', handleOutsideClick); - return () => document.removeEventListener('mousedown', handleOutsideClick); - }, []); + ); +}; - const onClickExpand = () => { - setOpen((prev) => !prev); - } +export const FormSelectMulti = (props: SelectInterface): JSX.Element => { + const { value, setValue, options } = { ...props }; + const selected = options.filter((o) => value.includes(o.value as unknown as T)) || []; const handleSelect = (val: T) => { - setValue((prev) => { - return [...prev, val]; - }) - } + setValue((prev) => [...prev, val]); + }; + + const handleDeselect = (val: T) => { + setValue((prev) => prev.filter(v => v !== val)); + }; return ( -
- {label && {label}} - - -
+ ); -} - - +}; diff --git a/Tokenization/webapp/app/components/form/form.tsx b/Tokenization/webapp/app/components/form/form.tsx index a18aba5b6..17126f663 100644 --- a/Tokenization/webapp/app/components/form/form.tsx +++ b/Tokenization/webapp/app/components/form/form.tsx @@ -1,18 +1,31 @@ -import React from "react" +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ -interface FormInterface extends React.HTMLAttributes {} +import React from 'react'; +interface FormInterface extends React.HTMLAttributes {} -export const Form = ({children, className, onSubmit, id}: FormInterface) => { - const _className = className ?? '' - return ( -
-
- {children} -
-
- ) -} \ No newline at end of file +export const Form = ({ children, className, onSubmit, id }: FormInterface) => { + const _className = className ?? ''; + return ( +
+
+ {children} +
+
+ ); +}; diff --git a/Tokenization/webapp/app/components/form/multi-select-helper.tsx b/Tokenization/webapp/app/components/form/multi-select-helper.tsx new file mode 100644 index 000000000..c46ed8ad8 --- /dev/null +++ b/Tokenization/webapp/app/components/form/multi-select-helper.tsx @@ -0,0 +1,41 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { type OptionType as Option } from '~/utils/types'; +import { IconContainer, IconX } from '~/ui/icon'; + +const SelectedOption = ({ value, label, handleDeselect }: Option & { handleDeselect?: (value: any) => void }) => { + const _handleDeselect = (e: React.MouseEvent, value: string | number) => { + handleDeselect?.(value); + e.stopPropagation(); + }; + + return ( +
  • + {label} + + +
  • + ); +}; + +export const SelectedList = ({ selected, handleDeselect }: any) =>
      + { + selected.map((s: Option) => ) + } +
    ; diff --git a/Tokenization/webapp/app/components/modal.tsx b/Tokenization/webapp/app/components/modal.tsx index 29d013f44..649425a63 100644 --- a/Tokenization/webapp/app/components/modal.tsx +++ b/Tokenization/webapp/app/components/modal.tsx @@ -1,142 +1,146 @@ -import React, { type PropsWithChildren, } from 'react' +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import React, { type PropsWithChildren } from 'react'; import { IconContainer, IconX } from '~/ui/icon'; interface ModalInterface extends PropsWithChildren, React.HTMLAttributes { - open: boolean; - setOpen: React.Dispatch>; - onOpen?: () => void - onClose?: () => void; - timeout?: number | null; + open: boolean; + setOpen: React.Dispatch>; + onOpen?: () => void; + onClose?: () => void; + timeout?: number | null; } interface ButtonInterface extends PropsWithChildren, React.HTMLAttributes { - action?: () => void + action?: () => void; } -export const ModalTitle = ({children}: PropsWithChildren) => { - return

    - {children} -

    -} +export const ModalTitle = ({ children }: PropsWithChildren) =>

    + {children} +

    ; -export const ModalContent = ({children}: PropsWithChildren) => { - return - {children} - -} +export const ModalContent = ({ children }: PropsWithChildren) => + {children} +; -export const ModalButton = ({children, action, className}: ButtonInterface) => { - return -} +export const ModalButton = ({ children, action, className }: ButtonInterface) => ; -export const ModalButtonCancel = ({action}: ButtonInterface) => { - return - Cancel - -} +export const ModalButtonCancel = ({ action }: ButtonInterface) => + Cancel +; -export const ModalButtonAccept = ({action, className}: ButtonInterface) => { - return - Accept - -} +export const ModalButtonAccept = ({ action, className }: ButtonInterface) => + Accept +; -export const Modal = ({children, open, setOpen, onClose, className, timeout = null}: ModalInterface) => { - const timerRef = React.useRef(null); - - // timeout - React.useEffect(() => { - if(open && timeout) { - if(timerRef.current) clearTimeout(timerRef.current); - - timerRef.current = setTimeout(() => { - setOpen(false); - onClose && onClose(); - }, timeout); - } - - // unmounting timer - return () => { - if(timerRef.current) { - clearTimeout(timerRef.current) - timerRef.current = null; - } - } - }, [open, timeout, setOpen, onClose]) - - const arrChildren = React.Children.toArray(children) - - const checkIsComponent = (c: React.ReactNode, otype: React.ElementType): boolean => { - return React.isValidElement(c) && c.type === otype +export const Modal = ({ children, open, setOpen, onClose, className, timeout = null }: ModalInterface) => { + const timerRef = React.useRef(null); + + // Timeout + React.useEffect(() => { + if (open && timeout) { + if (timerRef.current) { + clearTimeout(timerRef.current); + } + + timerRef.current = setTimeout(() => { + setOpen(false); + onClose?.(); + }, timeout); } - const title = arrChildren.find( - (child) => checkIsComponent(child, ModalTitle) - ); - const content = arrChildren.find( - (child) => checkIsComponent(child, ModalContent) - ); - let buttonCancel = arrChildren.find( - (child) => checkIsComponent(child, ModalButtonCancel) - ); - let buttonAccept = arrChildren.find( - (child) => checkIsComponent(child, ModalButtonAccept) - ); - - const cancelAction = () => { - setOpen(false) - onClose && onClose(); - if(timerRef.current) { - clearTimeout(timerRef.current) - timerRef.current = null; - } + // Unmounting timer + return () => { + if (timerRef.current) { + clearTimeout(timerRef.current); + timerRef.current = null; + } + }; + }, [open, timeout, setOpen, onClose]); + + const arrChildren = React.Children.toArray(children); + + const checkIsComponent = (c: React.ReactNode, otype: React.ElementType): boolean => React.isValidElement(c) && c.type === otype; + + const title = arrChildren.find( + (child) => checkIsComponent(child, ModalTitle), + ); + const content = arrChildren.find( + (child) => checkIsComponent(child, ModalContent), + ); + let buttonCancel = arrChildren.find( + (child) => checkIsComponent(child, ModalButtonCancel), + ); + let buttonAccept = arrChildren.find( + (child) => checkIsComponent(child, ModalButtonAccept), + ); + + const cancelAction = () => { + setOpen(false); + onClose?.(); + if (timerRef.current) { + clearTimeout(timerRef.current); + timerRef.current = null; } + }; - const _acceptAction = React.isValidElement(buttonAccept) ? - (buttonAccept.props as ButtonInterface).action : - undefined + const _acceptAction = React.isValidElement(buttonAccept) ? + (buttonAccept.props as ButtonInterface).action : + undefined; - const acceptAction = () => { - _acceptAction && _acceptAction(); - cancelAction() - } + const acceptAction = () => { + _acceptAction?.(); + cancelAction(); + }; - buttonCancel = React.isValidElement(buttonCancel) ? - React.cloneElement(buttonCancel as React.ReactElement, {action: cancelAction}): - <> + buttonCancel = React.isValidElement(buttonCancel) ? + React.cloneElement(buttonCancel as React.ReactElement, { action: cancelAction }) : + <>; - buttonAccept = React.isValidElement(buttonAccept) ? - React.cloneElement(buttonAccept as React.ReactElement, {action: acceptAction}): - <> + buttonAccept = React.isValidElement(buttonAccept) ? + React.cloneElement(buttonAccept as React.ReactElement, { action: acceptAction }) : + <>; - const visibility = open ? - "d-block": - "d-none"; + const visibility = open ? + 'd-block' : + 'd-none'; - return ( + return (
    -
    -
    - {title ?? ''} - - -
    -
    -
    - {content ?? ''} -
    -
    - {buttonCancel ?? ''} - {buttonAccept ?? ''} -
    -
    +
    +
    + {title ?? ''} + + +
    +
    +
    + {content ?? ''} +
    +
    + {buttonCancel ?? ''} + {buttonAccept ?? ''} +
    +
    - ) -} \ No newline at end of file + ); +}; diff --git a/Tokenization/webapp/app/components/tokens/action-block.tsx b/Tokenization/webapp/app/components/tokens/action-block.tsx index b8aba541d..358375f8e 100644 --- a/Tokenization/webapp/app/components/tokens/action-block.tsx +++ b/Tokenization/webapp/app/components/tokens/action-block.tsx @@ -14,7 +14,6 @@ import { IconDelete } from '~/ui/icon'; - interface ActionBlockProps { tokenId: string; onClick: () => void; diff --git a/Tokenization/webapp/app/components/tokens/token-table.tsx b/Tokenization/webapp/app/components/tokens/token-table.tsx index 197cc205b..ae9c16180 100644 --- a/Tokenization/webapp/app/components/tokens/token-table.tsx +++ b/Tokenization/webapp/app/components/tokens/token-table.tsx @@ -1,29 +1,44 @@ -import { type PropsWithChildren } from "react"; -import { type Token } from "./token"; +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ -import { useState } from "react"; -import { Link } from "react-router"; +import { type PropsWithChildren } from 'react'; +import { type Token } from './token'; -import { useAuth } from "~/hooks/session"; -import ActionBlock from "./action-block"; -import { Modal, ModalTitle, ModalContent, ModalButtonCancel, ModalButtonAccept } from "../modal"; +import { useState } from 'react'; +import { Link } from 'react-router'; +import { useAuth } from '~/hooks/session'; +import ActionBlock from './action-block'; +import { Modal, ModalTitle, ModalContent, ModalButtonCancel, ModalButtonAccept } from '../modal'; -export function TokenTable({children}: PropsWithChildren) { +/** + * + */ +export function TokenTable({ children }: PropsWithChildren) { const theaders = ['ID', 'Service From', 'Service To', 'Expires at', 'Actions']; - return
    - - - - {theaders.map((content, index) => )} - - - {children} -
    {content}
    -
    + return
    + + + + {theaders.map((content, index) => )} + + + {children} +
    {content}
    +
    ; } - // Will be changed in next PR /** * Table component that displays a list of tokens with their ID and validity. @@ -38,35 +53,37 @@ export function TokenTableContent({ tokens }: { tokens: Token[] }) { const auth = useAuth('admin'); const deleteToken = () => { - if(auth) { - console.log(`Deleting token no. ${tokenId}`) + if (auth) { + console.log(`Deleting token no. ${tokenId}`); } - setTokenId('') - } + setTokenId(''); + }; return ( - <> - - {tokens.map((token: Token) => ( - - {token.tokenId} - {token.serviceFrom} - {token.serviceTo} - {token.exp} - - {setTokenId(token.tokenId); setOpen(true);}} - /> - - - ))} - + <> + + {tokens.map((token: Token) => ( + + {token.tokenId} + {token.serviceFrom} + {token.serviceTo} + {token.exp} + + { + setTokenId(token.tokenId); setOpen(true); + }} + /> + + + ))} + + > Token delete Are you sure you want to delete token with id: {tokenId}? @@ -76,4 +93,4 @@ export function TokenTableContent({ tokens }: { tokens: Token[] }) { ); -} \ No newline at end of file +} diff --git a/Tokenization/webapp/app/routes.ts b/Tokenization/webapp/app/routes.ts index 072a07052..f0f6a7232 100644 --- a/Tokenization/webapp/app/routes.ts +++ b/Tokenization/webapp/app/routes.ts @@ -20,8 +20,8 @@ export default [ ...prefix('tokens', [ index('routes/tokens/overview.tsx'), route(':tokenId', 'routes/tokens/details.tsx'), - // route('table', 'routes/tokens/table.tsx'), - route('new', 'routes/tokens/create.tsx') + // Route('table', 'routes/tokens/table.tsx'), + route('new', 'routes/tokens/create.tsx'), ]), route('*', 'routes/404.tsx'), ]), diff --git a/Tokenization/webapp/app/routes/tokens/create.tsx b/Tokenization/webapp/app/routes/tokens/create.tsx index ef384fdf3..3a162781c 100644 --- a/Tokenization/webapp/app/routes/tokens/create.tsx +++ b/Tokenization/webapp/app/routes/tokens/create.tsx @@ -15,15 +15,15 @@ import { useNavigate } from 'react-router'; import Select, { type MultiValue, type SingleValue } from 'react-select'; import { useState, useCallback } from 'react'; -// import { +// Import { // TextField, // InputAdornment, // } from '@mui/material'; import type { OptionType, HttpMethod } from '~/utils/types'; -// import { DangerAlert } from '~/ui/alert'; -// import { CreationTokenDialog } from '~/ui/dialog'; -// import { useAuth } from '~/hooks/session'; +// Import { DangerAlert } from '~/ui/alert'; +// Import { CreationTokenDialog } from '~/ui/dialog'; +// Import { useAuth } from '~/hooks/session'; import { Form } from '~/components/form/form'; import { Box1_2 } from '~/components/box'; import { FormInput } from '~/components/form/form-input'; @@ -47,8 +47,9 @@ const httpMethodOptions = [ { value: 'DELETE', label: 'DELETE' }, ]; - - +/** + * + */ export default function CreateToken({ loaderData }: { loaderData?: OptionType[] }) { const [expirationTime, setExpirationTime] = useState(0); const [firstSelectedService, setFirstSelectedService] = useState(''); @@ -59,13 +60,13 @@ export default function CreateToken({ loaderData }: { loaderData?: OptionType[]
    - labelText="Expiration Time (hours):" + labelText="Expiration Time (hours):" value={expirationTime} setValue={setExpirationTime} - inputProps={{step: 1, min: 0}} - /> + inputProps={{ step: 1, min: 0 }} + /> - + ); } diff --git a/Tokenization/webapp/app/routes/tokens/overview.tsx b/Tokenization/webapp/app/routes/tokens/overview.tsx index f1502d925..89693e1c9 100644 --- a/Tokenization/webapp/app/routes/tokens/overview.tsx +++ b/Tokenization/webapp/app/routes/tokens/overview.tsx @@ -17,25 +17,24 @@ import { Await, useLoaderData } from 'react-router'; import { Suspense } from 'react'; import { Spinner } from '~/ui/spinner'; -import { Box1_2 } from "../../components/box" +import { Box1_2 } from '../../components/box'; import { useSetHeader } from '~/ui/header/headerContext'; -import {TokenTable, TokenTableContent} from '../../components/tokens/token-table' - +import { TokenTable, TokenTableContent } from '../../components/tokens/token-table'; /** * Client loader that fetches all tokens from the API. * * @returns Promise that resolves to an array of tokens */ -export const clientLoader = async (): Promise<{ tokens: Promise; }> => { +export const clientLoader = async (): Promise<{ tokens: Promise }> => { const tokens = fetch('/api/tokens') .then(response => { if (!response.ok) { - throw new Error('An error occurred!'); + throw new Error('An error occurred!'); } - return response.json() - }); - + return response.json(); + }); + return { tokens }; }; @@ -47,21 +46,21 @@ export const clientLoader = async (): Promise<{ tokens: Promise; }> => */ export default function Overview() { - const {tokens} = useLoaderData() + const { tokens } = useLoaderData(); useSetHeader('Tokens'); - return ( + return (
    - }> - - {(resolvedTokens: Token[]) => } - - + Loading...}> + + {(resolvedTokens: Token[]) => } + + - +

    Create Token

    @@ -69,5 +68,5 @@ export default function Overview() {
    - ) + ); } diff --git a/Tokenization/webapp/app/styles/components-styles.css b/Tokenization/webapp/app/styles/components-styles.css index cd48975d9..664cab8e9 100644 --- a/Tokenization/webapp/app/styles/components-styles.css +++ b/Tokenization/webapp/app/styles/components-styles.css @@ -51,7 +51,7 @@ border: 2px solid black; } -/* Select component */ +/* Select components */ .my-select { width: 100%; @@ -64,4 +64,29 @@ cursor: pointer; transition: border 0.2s, box-shadow 0.2s; position: relative; +} + +ul.multiselect-list { + list-style: none; + padding: 0; + margin: 0; +} + +.multiselect-list > li { + border-radius: 10px; + border: 0.1rem solid black; + margin-right: 0.2rem; + padding-left: 0.4rem; +} + +.multiselect-list button { + border-radius: 50%; + margin-left: 0.5rem; + border: none; + background-color: var(--color-transparent); + cursor: pointer; +} + +.multiselect-list button:hover { + border: 0.1rem solid black; } \ No newline at end of file diff --git a/Tokenization/webapp/app/ui/header/header.tsx b/Tokenization/webapp/app/ui/header/header.tsx index ed650267c..bc2e3ae2c 100644 --- a/Tokenization/webapp/app/ui/header/header.tsx +++ b/Tokenization/webapp/app/ui/header/header.tsx @@ -26,9 +26,9 @@ import { IconHome, IconCog } from '../icon'; export function AppHeader({ children }: { children?: React.ReactNode }) { return (
    -
    - {children} -
    +
    + {children} +
    ); } diff --git a/Tokenization/webapp/app/ui/layout.tsx b/Tokenization/webapp/app/ui/layout.tsx index 7ca36ff06..e997f8c25 100644 --- a/Tokenization/webapp/app/ui/layout.tsx +++ b/Tokenization/webapp/app/ui/layout.tsx @@ -18,16 +18,14 @@ import { AppHeader } from './header/header'; import { AppSidebar } from './sidebar'; import { Spinner } from './spinner'; -import logo from "../assets/4_Color_Logo_CB.png"; +import logo from '../assets/4_Color_Logo_CB.png'; -const Logo = ({name}: {name: string}) => { - return ( -
    - -

    {name}

    -
    - ) -} +const Logo = ({ name }: { name: string }) => ( +
    + +

    {name}

    +
    +); /** * Component provides main layout for the application @@ -36,7 +34,7 @@ const Logo = ({name}: {name: string}) => { export default function Layout() { const { state } = useNavigation(); - + return (
    diff --git a/Tokenization/webapp/app/ui/sidebar.tsx b/Tokenization/webapp/app/ui/sidebar.tsx index cb99dda90..8b6fbc531 100644 --- a/Tokenization/webapp/app/ui/sidebar.tsx +++ b/Tokenization/webapp/app/ui/sidebar.tsx @@ -13,7 +13,7 @@ */ import type { NavLinkProps } from 'react-router'; -import React from 'react' +import React from 'react'; import { NavLink } from 'react-router'; type StyledNavLinkProps = { @@ -34,26 +34,25 @@ const StyledNavLink = ({ children, to }: StyledNavLinkProps) => {({ isActive }) => (
    {children}
    )}
    ; - -const NavList = ({children}: {children: React.ReactNode}) => { - const items = React.Children.toArray(children) +const NavList = ({ children }: { children: React.ReactNode }) => { + const items = React.Children.toArray(children); return
      { - items.map((el, idx) => + items.map((el, idx) =>
    • {el} -
    • + , ) } -
    -} + ; +}; /** * AppSidebar @@ -68,7 +67,7 @@ export const AppSidebar = () => -
    +
    ; From 87a47856dbe5a85c6b40e3a6db22fb5137997ead Mon Sep 17 00:00:00 2001 From: Igor S Date: Sat, 15 Nov 2025 17:41:24 +0100 Subject: [PATCH 30/47] Solving typescript type errors for handleSelect functions by templates --- .../webapp/app/components/form/form-select.tsx | 18 +++++++++--------- .../components/form/multi-select-helper.tsx | 1 - .../webapp/app/routes/tokens/overview.tsx | 10 +++++----- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Tokenization/webapp/app/components/form/form-select.tsx b/Tokenization/webapp/app/components/form/form-select.tsx index 05b2f01d9..5a42f4112 100644 --- a/Tokenization/webapp/app/components/form/form-select.tsx +++ b/Tokenization/webapp/app/components/form/form-select.tsx @@ -16,7 +16,7 @@ import React, { useEffect, useRef, useState, type SetStateAction, type JSX } fro import { type OptionType as Option, type DialogPropsBase as DPB } from '~/utils/types'; import { SelectedList } from './multi-select-helper'; -interface SelectInterface { +interface SelectInterface ? U : T> { id: string; options: Option[]; placeholder?: string; @@ -24,8 +24,8 @@ interface SelectInterface { value: T; setValue: React.Dispatch>; selected?: Option | Option[] | null; - handleSelect?: (value: T) => void; - handleDeselect?: (value: T) => void; + handleSelect?: (value: V) => void; + handleDeselect?: (value: V) => void; takeSelectedToOption?: boolean; render?: React.ElementType; } @@ -113,7 +113,7 @@ const SelectOptions = ({ open, setOpen, handleSelec ); }; -const FormSelectBase = ({ +const FormSelectBase = ? U : T>({ id, options = [], placeholder = 'Choose an option', @@ -123,7 +123,7 @@ const FormSelectBase = ({ handleDeselect, takeSelectedToOption, render, -}: SelectInterface) => { +}: SelectInterface) => { const [open, setOpen] = useState(false); const rootRef = useRef(null); @@ -149,11 +149,11 @@ const FormSelectBase = ({
    {label && {label}} {selectFrame} - + ? U : T) => void} selected={selected} takeSelectedToOption={takeSelectedToOption} /> @@ -161,7 +161,7 @@ const FormSelectBase = ({ ); }; -export const FormSelect = (props: SelectInterface): JSX.Element => { +export const FormSelect = (props: SelectInterface): JSX.Element => { const { value, setValue, options } = { ...props }; const selected = options.find((o) => o.value === value) ?? null; const handleSelect = (val: T) => { @@ -172,7 +172,7 @@ export const FormSelect = (props: SelectIn ? U : T) => void} render={SelectFrame} /> diff --git a/Tokenization/webapp/app/components/form/multi-select-helper.tsx b/Tokenization/webapp/app/components/form/multi-select-helper.tsx index c46ed8ad8..3ce276b6d 100644 --- a/Tokenization/webapp/app/components/form/multi-select-helper.tsx +++ b/Tokenization/webapp/app/components/form/multi-select-helper.tsx @@ -29,7 +29,6 @@ const SelectedOption = ({ value, label, handleDeselect }: Option & { handleDesel - ); }; diff --git a/Tokenization/webapp/app/routes/tokens/overview.tsx b/Tokenization/webapp/app/routes/tokens/overview.tsx index 89693e1c9..252351536 100644 --- a/Tokenization/webapp/app/routes/tokens/overview.tsx +++ b/Tokenization/webapp/app/routes/tokens/overview.tsx @@ -52,13 +52,13 @@ export default function Overview() { return (
    - - Loading...}> + }> + - {(resolvedTokens: Token[]) => } + {(resolvedTokens: Token[]) => } - - + + From d011b9d676a59c5a5e0a18834d1f90d67de526a0 Mon Sep 17 00:00:00 2001 From: Igor S Date: Sat, 15 Nov 2025 23:37:48 +0100 Subject: [PATCH 31/47] Window template and lgic implemented in hook, types transferred to .d.ts files. Restructurized code --- .../webapp/app/components/form/form-input.tsx | 16 +- .../app/components/form/form-select.tsx | 156 +----------------- .../webapp/app/components/form/form.d.ts | 54 ++++++ .../components/form/multi-select-helper.tsx | 4 +- .../app/components/form/select-helper.tsx | 138 ++++++++++++++++ .../app/components/hooks/useWindowLogic.tsx | 118 +++++++++++++ Tokenization/webapp/app/components/modal.tsx | 146 ---------------- .../app/components/tokens/token-table.tsx | 126 +++++++++----- .../webapp/app/components/window/alert.tsx | 23 +++ .../webapp/app/components/window/modal.tsx | 41 +++++ .../app/components/window/window-objects.tsx | 31 ++++ .../webapp/app/components/window/window.d.ts | 40 +++++ .../webapp/app/routes/tokens/create.tsx | 22 ++- .../webapp/app/routes/tokens/overview.tsx | 10 +- .../webapp/app/styles/components-styles.css | 35 ++++ 15 files changed, 585 insertions(+), 375 deletions(-) create mode 100644 Tokenization/webapp/app/components/form/form.d.ts create mode 100644 Tokenization/webapp/app/components/form/select-helper.tsx create mode 100644 Tokenization/webapp/app/components/hooks/useWindowLogic.tsx delete mode 100644 Tokenization/webapp/app/components/modal.tsx create mode 100644 Tokenization/webapp/app/components/window/alert.tsx create mode 100644 Tokenization/webapp/app/components/window/modal.tsx create mode 100644 Tokenization/webapp/app/components/window/window-objects.tsx create mode 100644 Tokenization/webapp/app/components/window/window.d.ts diff --git a/Tokenization/webapp/app/components/form/form-input.tsx b/Tokenization/webapp/app/components/form/form-input.tsx index 36ce18f46..2d0e4ccd0 100644 --- a/Tokenization/webapp/app/components/form/form-input.tsx +++ b/Tokenization/webapp/app/components/form/form-input.tsx @@ -13,15 +13,7 @@ */ import React from 'react'; - -export interface FormInputProps { - value: T; - setValue: React.Dispatch>; - labelText?: string; - containerProps?: React.HTMLAttributes; - labelProps?: React.LabelHTMLAttributes; - inputProps?: React.InputHTMLAttributes; -} +import { type FormInputInterface } from './form.d' export const FormInput = ({ value, @@ -30,9 +22,8 @@ export const FormInput = ({ containerProps, labelProps, inputProps, -}: FormInputProps) => { +}: FormInputInterface) => { const inputId = inputProps?.id ?? labelProps?.htmlFor ?? undefined; - const type = typeof value === 'string' ? 'text' : 'number'; const handleChange = (e: React.ChangeEvent) => { const { target } = e; @@ -47,7 +38,7 @@ export const FormInput = ({ }; return ( -
    +
    {labelText && (
    ); -}; +} -export const SelectFrameMulti = (props: SelectLabelProps) => { +/** + * SelectFrameMulti + * + * Renders the collapsed select frame for multi-select mode (shows selected list). + * + * @template T + * @param {object} props - component props + * @param {boolean} props.open - whether the dropdown is open + * @param {React.Dispatch>} props.setOpen - setter (useState dispatcher) to toggle open state + * @param {import('~/utils/types').OptionType | import('~/utils/types').OptionType[] | null} props.selected - selected option(s) + * @param {string} props.placeholder - placeholder text shown when no selection + * @param {(value: T) => void} [props.handleDeselect] - callback invoked when user removes a selected item + * + * Notes: + * - Uses SelectedList when selected is an array. - which should be the case for multi-select. + */ +export function SelectFrameMulti( + props: SelectLabelProps & { setOpen: React.Dispatch> }, +) { const { open, setOpen, selected, placeholder, handleDeselect } = props; const _selected: Option[] | null = Array.isArray(selected) ? selected : (selected ? [selected] : null); @@ -43,7 +80,7 @@ export const SelectFrameMulti = (props: SelectLabelPr > {_selected && - _selected.length > 0 ? + _selected.length > 0 ? void} /> : {placeholder} } @@ -51,10 +88,30 @@ export const SelectFrameMulti = (props: SelectLabelPr {open ? '▴' : '▾'}
    ); +} -}; - -export const SelectOptions = ({ takeSelectedToOption = true, ...rest }: SelectOptionsProps) => { +/** + * SelectOptions + * + * Renders dropdown list of options and calls handleSelect on click. + * + * @template T + * @param {object} props - component props + * @param {boolean} props.open - whether the dropdown is open + * @param {React.Dispatch>} props.setOpen - setter (useState dispatcher) to change open state + * @param {import('~/utils/types').OptionType[]} props.options - available options to render + * @param {import('~/utils/types').OptionType | import('~/utils/types').OptionType[] | null} [props.selected] - currently selected option(s); + * used to hide already-selected items when takeSelectedToOption is false + * @param {boolean} [props.takeSelectedToOption=true] - when false, selected options are removed from the options list + * @param {(value: T) => void} [props.handleSelect] - callback invoked with the option value when an option is clicked + * + * Notes: + * - Props come from SelectOptionsProps. + */ +export function SelectOptions( + { takeSelectedToOption = true, ...rest }: + SelectOptionsProps & { setOpen: React.Dispatch> }, +) { const { open, setOpen, handleSelect, options, selected } = rest; const _selected = Array.isArray(selected) ? selected : [selected]; const visibleOptions = takeSelectedToOption ? options : options.filter((opt) => !(_selected.includes(opt))); @@ -85,9 +142,31 @@ export const SelectOptions = ({ takeSelectedToOptio )} ); -}; +} -export const FormSelectBase = ? U : T>({ +/** + * FormSelectBase + * + * Base logic used by single and multi select components (frame + options + outside-click handling). + * + * @template T,V + * @param {object} props - component props + * @param {string} props.id - unique id for the select root element + * @param {import('~/utils/types').OptionType[]} props.options - list of options shown in the dropdown + * @param {string} [props.placeholder] - placeholder text shown when nothing selected + * @param {string|null} [props.label] - optional label element displayed above the select frame + * @param {import('~/utils/types').OptionType | import('~/utils/types').OptionType[] | null} [props.selected] - + * currently selected option(s) passed to the frame renderer + * @param {(value: V) => void} [props.handleSelect] - callback invoked when an option is selected + * @param {(value: V) => void} [props.handleDeselect] - callback invoked when an option is deselected (used in multi-select) + * @param {boolean} [props.takeSelectedToOption] - whether selected items remain visible in the options list + * @param {React.ElementType} [props.render] - renderer for the select frame - + * FormSelect uses SelectFrame, FormSelectMulti uses SelectFrameMulti + * + * Notes: + * - This component handles outside-click closing and delegates frame and options rendering. + */ +export function FormSelectBase ? U : T>({ id, options = [], placeholder = 'Choose an option', @@ -97,7 +176,7 @@ export const FormSelectBase = ) => { +}: SelectInterface) { const [open, setOpen] = useState(false); const rootRef = useRef(null); @@ -133,4 +212,4 @@ export const FormSelectBase =
    ); -}; +} diff --git a/Tokenization/webapp/app/components/hooks/useWindowLogic.tsx b/Tokenization/webapp/app/components/hooks/useWindowLogic.tsx index 099f06d26..1276b8063 100644 --- a/Tokenization/webapp/app/components/hooks/useWindowLogic.tsx +++ b/Tokenization/webapp/app/components/hooks/useWindowLogic.tsx @@ -19,6 +19,20 @@ import { type ButtonInterface, type WindowElementsWithAction } from '../window/w import { WindowTitle, WindowContent, WindowButtonAccept, WindowButtonCancel, WindowCloseIcon } from '../window/window-objects'; /** + * GetWindowChildrenAndActions + * + * Non-hook helper. Scans React children and extracts known Window subcomponents + * (WindowTitle, WindowContent, WindowButtonCancel, WindowButtonAccept, WindowCloseIcon). + * + * @param {object} props + * @param {React.PropsWithChildren} props.children - children passed to the Window component + * @returns {WindowElementsWithAction} Object with: + * - title: React.ReactNode | undefined (the WindowTitle child if present) + * - content: React.ReactNode | undefined (the WindowContent child if present) + * - buttonCancel: React.ReactNode | undefined (the WindowButtonCancel child if present) + * - buttonAccept: React.ReactNode | undefined (the WindowButtonAccept child if present) + * - closeIcon: React.ReactNode | undefined (the WindowCloseIcon child if present) + * - acceptAction: (() => void) | undefined (action extracted from the accept button props, if any) * */ function getWindowChildrenAndActions({ children }: PropsWithChildren) { @@ -49,7 +63,23 @@ function getWindowChildrenAndActions({ children }: PropsWithChildren) { } /** + * ProvidePropsForWindowChildren + * + * Non-hook helper. Clones provided button/close icon elements and injects + * runtime action handlers (cancelAction / wrapped acceptAction). * + * @param {WindowElementsWithAction} params - object containing children and actions + * @param {React.ReactNode} params.closeIcon - close icon element (may be undefined) + * @param {React.ReactNode} params.buttonCancel - cancel button element (may be undefined) + * @param {React.ReactNode} params.buttonAccept - accept button element (may be undefined) + * @param {() => void} params.cancelAction - function to call to cancel/close the window + * @param {() => void | undefined} params.acceptAction - original accept action (optional); will be wrapped so it also triggers cancelAction + * @returns {{buttonCancel: React.ReactNode, buttonAccept: React.ReactNode, closeIcon: React.ReactNode}} + * + * Behaviour: + * - If a provided element is a valid React element, it will be cloned with an injected + * `action` prop bound to cancelAction or the wrapped accept action. + * - Otherwise returns empty fragments for missing elements. */ function providePropsForWindowChildren({ closeIcon, buttonCancel, buttonAccept, cancelAction, acceptAction }: WindowElementsWithAction) { const _acceptAction = () => { @@ -78,7 +108,24 @@ function providePropsForWindowChildren({ closeIcon, buttonCancel, buttonAccept, } /** + * UseWindowLogic + * + * Hook. Manages simple timing/close behaviour for window-like components. * + * @param {WindowInterface} params + * @param {boolean} params.open - whether the window is open + * @param {(open: boolean) => void} params.setOpen - state setter (React.Dispatch) used to open/close the window + * @param {() => void | undefined} params.onClose - optional callback invoked when window is closed + * @param {number | null} [params.timeout=null] - optional auto-close timeout in milliseconds + * @returns {{ cancelAction: () => void }} + * + * Behaviour / side-effects: + * - When `open` is true and `timeout` is a number, starts a timer that calls setOpen(false) and onClose() after timeout. + * - Cleans up the timer on unmount or when dependencies change. + * - cancelAction: synchronous function that closes the window (setOpen(false)), calls onClose(), and clears the timer. + * + * Notes: + * - This is a React hook (uses useEffect, useRef) and must be called following hooks rules. */ function useWindowLogic({ open, setOpen, onClose, timeout = null }: WindowInterface) { const timerRef = React.useRef(null); @@ -118,7 +165,36 @@ function useWindowLogic({ open, setOpen, onClose, timeout = null }: WindowInterf } /** + * UseFullWindowLogic + * + * Hook. Combines child extraction and runtime wiring for window components. + * + * @param {WindowInterface} params + * @param {React.ReactNode} params.children - children of the window component (may include WindowTitle, WindowContent, buttons, close icon) + * @param {boolean} params.open - whether the window is open + * @param {React.Dispatch>} params.setOpen - dispatcher to toggle window open state + * @param {() => void | undefined} [params.onClose] - callback invoked when window is closed + * @param {number | null} [params.timeout=null] - optional auto-close timeout in ms + * @returns {{ + * visibility: string, + * ui_elements: { + * title: React.ReactNode | undefined, + * content: React.ReactNode | undefined, + * closeIcon: React.ReactNode, + * buttonAccept: React.ReactNode, + * buttonCancel: React.ReactNode + * } + * }} + * + * Behaviour: + * - Calls useWindowLogic to obtain cancelAction and timer behaviour. + * - Extracts window subcomponents and any accept action using getWindowChildrenAndActions. + * - Produces cloned button/closeIcon nodes wired to cancelAction / wrapped accept action via providePropsForWindowChildren. + * - Computes `visibility` string: 'd-block' when open, 'd-none' when closed. * + * Notes: + * - This is a hook and must follow React hooks rules. + * - Return structure is intended for direct consumption by Window-like components to render content and controls. */ export function useFullWindowLogic({ children, open, setOpen, onClose, timeout = null }: WindowInterface) { const { cancelAction } = useWindowLogic({ open, setOpen, onClose, timeout }); diff --git a/Tokenization/webapp/app/components/tokens/action-block.tsx b/Tokenization/webapp/app/components/tokens/action-block.tsx index 358375f8e..abbd60c0c 100644 --- a/Tokenization/webapp/app/components/tokens/action-block.tsx +++ b/Tokenization/webapp/app/components/tokens/action-block.tsx @@ -20,7 +20,13 @@ interface ActionBlockProps { } /** - * Action block component that provides token actions such as delete + * ActionBlock + * + * Small UI block that renders action controls for a token (currently a delete button). + * + * @param {object} props - component props + * @param {string} props.tokenId - id of the token the actions operate on (used for aria/title) + * @param {() => void} props.onClick - click handler invoked when the action button is pressed */ export default function ActionBlock({ tokenId, onClick }: ActionBlockProps) { return ( diff --git a/Tokenization/webapp/app/components/tokens/token-table.tsx b/Tokenization/webapp/app/components/tokens/token-table.tsx index 0ca888c50..5298e2c84 100644 --- a/Tokenization/webapp/app/components/tokens/token-table.tsx +++ b/Tokenization/webapp/app/components/tokens/token-table.tsx @@ -24,7 +24,13 @@ import Alert from '../window/alert'; import { WindowTitle, WindowContent, WindowButtonCancel, WindowButtonAccept, WindowCloseIcon } from '../window/window-objects'; /** + * TokenTableHeader * + * Renders the static table header row for the token table. + * + * Notes: + * - No props. + * - Small, non-reusable helper. */ function TokenTableHeader() { const theaders = ['ID', 'Service From', 'Service To', 'Expires at', 'Actions']; @@ -38,10 +44,16 @@ function TokenTableHeader() { } /** - * Table component that displays a list of tokens with their ID and validity. - * Token IDs are clickable links that navigate to the token details page. + * TokenTableContent + * + * Renders table body rows for the provided tokens. + * + * @param {object} props - component props + * @param {Token[]} props.tokens - array of token records to display + * @param {(val: string) => void} props.actionBlockOnClick - callback invoked with tokenId when action button clicked * - * @param tokens - Array of tokens to display + * Notes: + * - Simple rendering helper; not a generic reusable component. */ function TokenTableContent({ tokens, actionBlockOnClick }: { tokens: Token[]; actionBlockOnClick: (val: string) => void }) { return ( @@ -67,7 +79,16 @@ function TokenTableContent({ tokens, actionBlockOnClick }: { tokens: Token[]; ac } /** + * TokenTable + * + * Table wrapper that manages modal/alert UI for token actions. + * + * @param {object} props - component props + * @param {Token[]} props.tokens - tokens to display in the table * + * Notes: + * - Manages local state for delete confirmation and result alert. + * - Intentionally small and application-specific (not designed as a reusable library component). */ export function TokenTable({ tokens }: { tokens: Token[] }) { const [openM, setOpenM] = useState(false); // Used for modal logic diff --git a/Tokenization/webapp/app/components/window/alert.tsx b/Tokenization/webapp/app/components/window/alert.tsx index 8c7bb064e..09fb8aa21 100644 --- a/Tokenization/webapp/app/components/window/alert.tsx +++ b/Tokenization/webapp/app/components/window/alert.tsx @@ -15,6 +15,23 @@ import { type WindowInterface } from './window.d'; import { useFullWindowLogic } from '../hooks/useWindowLogic'; +/** + * Alert + * + * Small transient alert window. + * + * @param {object} props - component props (see WindowInterface in window.d.ts) + * @param {React.ReactNode} props.children - alert content (typically WindowTitle + WindowContent + optional WindowCloseIcon) + * @param {boolean} props.open - whether the modal is visible (provided via DPB) + * @param {React.Dispatch>} props.setOpen - dispatcher to control visibility (provided via DPB) + * @param {() => void} [props.onClose] - optional callback invoked when alert closes + * @param {number|null} [props.timeout] - optional auto-close timeout in milliseconds (useful for transient alerts) + * @param {string} [props.className] - additional CSS classes applied to the modal container + * (expected to be used to control bg-color, but can be more versatile) + * Behaviour: + * - Delegates lifecycle (timeout, close action) and child wiring to useFullWindowLogic(props). + * - Renders title, content and closeIcon produced by the hook. + */ const Alert = (props: WindowInterface) => { const { className } = props; diff --git a/Tokenization/webapp/app/components/window/modal.tsx b/Tokenization/webapp/app/components/window/modal.tsx index d90a71cd7..99fa0875f 100644 --- a/Tokenization/webapp/app/components/window/modal.tsx +++ b/Tokenization/webapp/app/components/window/modal.tsx @@ -15,6 +15,27 @@ import { type WindowInterface } from './window.d'; import { useFullWindowLogic } from '../hooks/useWindowLogic'; +/** + * Modal + * + * Application modal window component. + * + * @param {object} props - component props (see WindowInterface in window.d.ts) + * @param {React.ReactNode} props.children - modal content (title/content/buttons expected as Window* + * from ./window-objects.tsx children) + * @param {boolean} props.open - whether the modal is visible (provided via DPB) + * @param {React.Dispatch>} props.setOpen - dispatcher to control visibility (provided via DPB) + * @param {() => void} [props.onOpen] - optional callback invoked when modal opens + * @param {() => void} [props.onClose] - optional callback invoked when modal closes + * @param {number|null} [props.timeout] - optional auto-close timeout in milliseconds + * @param {string} [props.className] - additional CSS classes applied to the modal container + * (expected to be used to control bg-color, but can be more versatile) + * + * Behaviour: + * - Uses useFullWindowLogic(props) to wire lifecycle (open/close/timeout) and to extract/wire Window child elements: + * title, content, closeIcon, buttonCancel, buttonAccept. + * - Renders those wired elements inside a modal overlay. + */ const Modal = (props: WindowInterface) => { const { className } = props; const { visibility, ui_elements: { title, content, closeIcon, buttonCancel, buttonAccept } } = useFullWindowLogic(props); diff --git a/Tokenization/webapp/app/components/window/window-objects.tsx b/Tokenization/webapp/app/components/window/window-objects.tsx index 58c13b4af..4581eda5e 100644 --- a/Tokenization/webapp/app/components/window/window-objects.tsx +++ b/Tokenization/webapp/app/components/window/window-objects.tsx @@ -17,16 +17,45 @@ import { type PropsWithChildren } from 'react'; import { type ButtonInterface } from './window.d'; import { IconContainer, IconX } from '~/ui/icon'; +/** + * WindowTitle + * + * Small presentational component used as a child of Modal/Alert to mark the title region. + * + * @param {object} props - component props + * @param {React.ReactNode} props.children - title content + */ export const WindowTitle = ({ children }: PropsWithChildren) =>

    {children}

    ; +/** + * WindowContent + * + * Presentational wrapper for main window content. + * + * @param {object} props - component props + * @param {React.ReactNode} props.children - content to display inside the window body + */ export const WindowContent = ({ children }: PropsWithChildren) => {children} ; +/** + * WindowButton / WindowButtonCancel / WindowButtonAccept + * + * Reusable button components used inside Window children. + * + * @param {object} props - component props + * @param {React.ReactNode} props.children - button label/content + * @param {() => void} [props.action] - callback invoked on click (wrapped by parent logic when cloned) + * @param {string} [props.className] - additional CSS classes + * + * Notes: + * - Parent window logic (useFullWindowLogic) clones these elements and injects action handlers. + */ const WindowButton = ({ children, action, className }: ButtonInterface) => - - - - ); -} From 8f2ef4d0ef0c69ab831671153fe66094c06b5705 Mon Sep 17 00:00:00 2001 From: Igor S Date: Mon, 24 Nov 2025 22:31:04 +0100 Subject: [PATCH 40/47] Tests for token creation form are now suited for new layout components --- .../webapp/app/routes/tokens/create.tsx | 26 +++++++--- Tokenization/webapp/tests/public/basic.cjs | 21 ++++---- .../webapp/tests/public/token-tests.cjs | 49 +++++++++---------- 3 files changed, 54 insertions(+), 42 deletions(-) diff --git a/Tokenization/webapp/app/routes/tokens/create.tsx b/Tokenization/webapp/app/routes/tokens/create.tsx index 759cbe197..92ad1d373 100644 --- a/Tokenization/webapp/app/routes/tokens/create.tsx +++ b/Tokenization/webapp/app/routes/tokens/create.tsx @@ -12,7 +12,7 @@ * or submit itself to any jurisdiction. */ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import type { OptionType, HttpMethod } from '~/utils/types'; import { Form } from '~/components/form/form'; @@ -45,7 +45,7 @@ const httpMethodOptions = [ ]; /** - * Component is used for /tokens/create route to create new tokens. + * Component is used for /tokens/new route to create new tokens. */ export default function CreateToken({ loaderData }: { loaderData?: OptionType[] }) { const [expirationTime, setExpirationTime] = useState(''); @@ -53,6 +53,18 @@ export default function CreateToken({ loaderData }: { loaderData?: OptionType[] const [secondSelectedService, setSecondSelectedService] = useState(''); const [selectedMethods, setSelectedMethods] = useState([]); + const [firstSelectedLabel, setFirstSelectedLabel] = useState(''); + const [secondSelectedLabel, setSecondSelectedLabel] = useState(''); + + useEffect(() => { + if (loaderData) { + const firstLabel = loaderData.find(option => option.value === firstSelectedService)?.label || ''; + const secondLabel = loaderData.find(option => option.value === secondSelectedService)?.label || ''; + setFirstSelectedLabel(firstLabel); + setSecondSelectedLabel(secondLabel); + } + }, [firstSelectedService, secondSelectedService, loaderData]); + const auth = useAuth('admin'); const [openAlert, setOpenAlert] = useState(false); const [openModal, setOpenModal] = useState(false); @@ -101,7 +113,7 @@ export default function CreateToken({ loaderData }: { loaderData?: OptionType[] setMsg('Token has been created successfully.'); setSuccess(true); } else { - setTitle('Token was not created'); + setTitle('Authorization error'); setMsg('You do not have permission to perform this operation.'); setSuccess(false); } @@ -176,13 +188,13 @@ export default function CreateToken({ loaderData }: { loaderData?: OptionType[] setOpen={setOpenModal} className='bg-primary ' > - Token creation + Confirm Token Creation
    Are you sure you want to create the token with the specified settings?
    -
    Service from: {firstSelectedService}
    -
    Service to: {secondSelectedService}
    -
    Expiration time of {expirationTime} hours
    +
    Service from: {firstSelectedLabel}
    +
    Service to: {secondSelectedLabel}
    +
    Expiration time: {expirationTime} hours
    HTTP methods: {selectedMethods.join(', ')}
    diff --git a/Tokenization/webapp/tests/public/basic.cjs b/Tokenization/webapp/tests/public/basic.cjs index ef6ab722a..4e49342c6 100644 --- a/Tokenization/webapp/tests/public/basic.cjs +++ b/Tokenization/webapp/tests/public/basic.cjs @@ -22,17 +22,20 @@ describe('general tests', function() { ({ helpers: { url: url }, page: page } = test); }); - it('header content changes with navigation', async function() { - let headerContent; - + it('header contains links to /tokens and /certs with correct names', async function() { await page.goto(url); await page.waitForSelector('header'); - headerContent = await page.$eval('header', el => el.textContent); - assert.ok(headerContent.includes('Tokenization')); - await page.goto(`${url}/tokens`); - await page.waitForSelector('header'); - headerContent = await page.$eval('header', el => el.textContent); - assert.ok(headerContent.includes('Tokens')); + // verify link to /tokens exists and its text mentions "Token" + const tokensLinkText = await page.$eval('header a[href="/tokens"]', el => el.textContent || ''); + const tokensLinkHref = await page.$eval('header a[href="/tokens"]', el => el.getAttribute('href')); + assert.strictEqual(tokensLinkHref, '/tokens'); + assert.ok(/Token/i.test(tokensLinkText), `tokens link text should include "Token", got "${tokensLinkText}"`); + + // verify link to /certs exists and its text mentions "Cert" (covers "Certs" or "Certificates") + const certsLinkText = await page.$eval('header a[href="/certs"]', el => el.textContent || ''); + const certsLinkHref = await page.$eval('header a[href="/certs"]', el => el.getAttribute('href')); + assert.strictEqual(certsLinkHref, '/certs'); + assert.ok(/Certificates/i.test(certsLinkText), `certs link text should include "Cert", got "${certsLinkText}"`); }); }); diff --git a/Tokenization/webapp/tests/public/token-tests.cjs b/Tokenization/webapp/tests/public/token-tests.cjs index e8f34eace..597ec01ac 100644 --- a/Tokenization/webapp/tests/public/token-tests.cjs +++ b/Tokenization/webapp/tests/public/token-tests.cjs @@ -17,7 +17,7 @@ const assert = require('assert'); async function selectReactOption(reactSelect, optionIndex) { await reactSelect.click(); await setTimeout(() => {}, 100); - return reactSelect.$(`div[role="listbox"] div:nth-child(${optionIndex})`); + return reactSelect.$(`ul li:nth-child(${optionIndex})`); } async function fillNumberInput(inputElement, number) { @@ -41,7 +41,7 @@ async function fillAllFormFields(page, reactSelect1, reactSelect2, reactSelect3, await fillNumberInput(expirationInput, filledNumber); await button.click(); - const dialogHandle = await page.waitForSelector('.MuiDialog-paper'); + const dialogHandle = await page.waitForSelector('.modal'); const dialogContent = (await dialogHandle.evaluate(el => el.textContent)).trim(); return { @@ -54,7 +54,7 @@ async function fillAllFormFields(page, reactSelect1, reactSelect2, reactSelect3, }; } -describe('token creation', function() { +describe('token creation unsuccessful', function() { let url; let page; @@ -74,8 +74,8 @@ describe('token creation', function() { ] = await Promise.all([ page.waitForSelector('#first-service-select'), page.waitForSelector('#second-service-select'), - page.waitForSelector('#http-methods-select'), - page.waitForSelector('#expiration-time-input'), + page.waitForSelector('#http-select-methods'), + page.waitForSelector('.my-input > input[type="number"]'), page.waitForSelector('button[type="submit"]'), ]); @@ -86,11 +86,14 @@ describe('token creation', function() { this.button = button; }); - it('Not filling form shows error', async function() { + it('Not filling form shows error alert', async function() { + const alert = await page.waitForSelector('.alert'); + const alertClass1 = await page.$eval('.alert', el => el.className); + assert.ok(alertClass1.includes('d-none')); // alert is hidden initially + await this.button.click(); - await page.waitForSelector('#danger-alert'); - const alertContent = await page.$eval('#danger-alert', el => el.textContent); - assert.ok(alertContent.includes('Please fill in all required fields')); + const alertClass2 = await page.$eval('.alert', el => el.className); + assert.ok(alertClass2.includes('d-block')); // alert is shown after submit }); describe('filling partially the form', function() { @@ -101,8 +104,7 @@ describe('token creation', function() { await opt2.click(); await this.button.click(); - await page.waitForSelector('#danger-alert'); - const alertContent = await page.$eval('#danger-alert', el => el.textContent); + const alertContent = await page.$eval('.alert', el => el.textContent); assert.ok(alertContent.includes('Expiration time', 'HTTP methods')); }); @@ -112,8 +114,7 @@ describe('token creation', function() { await fillNumberInput(this.expirationInput, 10); await this.button.click(); - await page.waitForSelector('#danger-alert'); - const alertContent = await page.$eval('#danger-alert', el => el.textContent); + const alertContent = await page.$eval('.alert', el => el.textContent); assert.ok(alertContent.includes('First service', 'HTTP methods')); }); @@ -122,20 +123,21 @@ describe('token creation', function() { await opt3.click(); await this.button.click(); - await page.waitForSelector('#danger-alert'); - const alertContent = await page.$eval('#danger-alert', el => el.textContent); + const alertContent = await page.$eval('.alert', el => el.textContent); assert.ok( alertContent.includes('First service', 'Second service', 'Expiration time'), ); }); }); - it('filling the form correctly shows proper success message with alert', async function() { + it('filling the form correctly shows proper success message on modal window', async function() { const { dialogContent, select1Content, select2Content, select3Content, filledNumber } = await fillAllFormFields(page, this.reactSelect1, this.reactSelect2, this.reactSelect3, this.expirationInput, this.button); - assert.ok(dialogContent.includes('Confirm Token Creation')); - assert.ok(dialogContent.includes('First machine: ' + select1Content)); - assert.ok(dialogContent.includes('Second machine: ' + select2Content)); + console.log(dialogContent, select1Content, select2Content, select3Content, filledNumber + ) + assert.ok(dialogContent.includes('Confirm Token Creation')); + assert.ok(dialogContent.includes('Service from: ' + select1Content)); + assert.ok(dialogContent.includes('Service to: ' + select2Content)); assert.ok(dialogContent.includes(select3Content)); assert.ok(dialogContent.includes('Expiration time: ' + filledNumber.toString() + ' hours')); }); @@ -147,12 +149,7 @@ describe('token creation', function() { const confirmButton = await dialogHandle.$('button:nth-child(2)'); await confirmButton.click(); - await page.waitForSelector('#danger-alert'); - const alertContent = await page.$eval('#danger-alert', el => el.textContent); + const alertContent = await page.$eval('.alert', el => el.textContent); assert.ok(alertContent.includes('Authorization error')); - }), - - it('happy path', async function() { - assert.ok(true); - }); + }) }); From 28e3c11d3aa3683e6d887f8a84db88ddd24c22a9 Mon Sep 17 00:00:00 2001 From: Igor S Date: Tue, 25 Nov 2025 00:31:47 +0100 Subject: [PATCH 41/47] Tests for token deleting and creating created for current UI --- .../app/components/tokens/token-table.tsx | 2 +- .../webapp/app/routes/tokens/create.tsx | 4 +- Tokenization/webapp/package.json | 2 +- Tokenization/webapp/tests/mocha-index.cjs | 6 +- .../public/{basic.cjs => layout-tests.cjs} | 16 ++++ .../webapp/tests/public/tokens/helpers.cjs | 59 ++++++++++++++ .../token-creation-err.cjs} | 50 ++---------- .../public/tokens/token-deletion-err.cjs | 73 +++++++++++++++++ .../tests/public/tokens/token-happy-path.cjs | 80 +++++++++++++++++++ 9 files changed, 241 insertions(+), 51 deletions(-) rename Tokenization/webapp/tests/public/{basic.cjs => layout-tests.cjs} (66%) create mode 100644 Tokenization/webapp/tests/public/tokens/helpers.cjs rename Tokenization/webapp/tests/public/{token-tests.cjs => tokens/token-creation-err.cjs} (75%) create mode 100644 Tokenization/webapp/tests/public/tokens/token-deletion-err.cjs create mode 100644 Tokenization/webapp/tests/public/tokens/token-happy-path.cjs diff --git a/Tokenization/webapp/app/components/tokens/token-table.tsx b/Tokenization/webapp/app/components/tokens/token-table.tsx index 5298e2c84..65f3559bf 100644 --- a/Tokenization/webapp/app/components/tokens/token-table.tsx +++ b/Tokenization/webapp/app/components/tokens/token-table.tsx @@ -102,7 +102,7 @@ export function TokenTable({ tokens }: { tokens: Token[] }) { }; const failureInfo = { - title: 'Token wasn\'t deleted', + title: 'Authorization error', content: 'You don\'t have permission to do that operation!', }; diff --git a/Tokenization/webapp/app/routes/tokens/create.tsx b/Tokenization/webapp/app/routes/tokens/create.tsx index 92ad1d373..853211610 100644 --- a/Tokenization/webapp/app/routes/tokens/create.tsx +++ b/Tokenization/webapp/app/routes/tokens/create.tsx @@ -58,8 +58,8 @@ export default function CreateToken({ loaderData }: { loaderData?: OptionType[] useEffect(() => { if (loaderData) { - const firstLabel = loaderData.find(option => option.value === firstSelectedService)?.label || ''; - const secondLabel = loaderData.find(option => option.value === secondSelectedService)?.label || ''; + const firstLabel = loaderData.find(option => option.value === firstSelectedService)?.label ?? ''; + const secondLabel = loaderData.find(option => option.value === secondSelectedService)?.label ?? ''; setFirstSelectedLabel(firstLabel); setSecondSelectedLabel(secondLabel); } diff --git a/Tokenization/webapp/package.json b/Tokenization/webapp/package.json index 8eeb7e1bc..5c449fc9f 100644 --- a/Tokenization/webapp/package.json +++ b/Tokenization/webapp/package.json @@ -8,7 +8,7 @@ "build": "react-router build", "dev": "react-router dev", "start": "react-router-serve ./build/server/index.js", - "test": "mocha --exit tests/mocha-index.cjs", + "test": "mocha --timeout 10000 --exit tests/mocha-index.cjs", "typecheck": "react-router typegen && tsc", "docker:typecheck": "docker compose exec webapp npm run typecheck", "docker:test:build": "cd .. && docker compose -f docker-compose.test.yml up --build --abort-on-container-exit ui-tests && docker compose -f docker-compose.test.yml stop && cd ./webapp", diff --git a/Tokenization/webapp/tests/mocha-index.cjs b/Tokenization/webapp/tests/mocha-index.cjs index 4e02b8a84..957a38b38 100644 --- a/Tokenization/webapp/tests/mocha-index.cjs +++ b/Tokenization/webapp/tests/mocha-index.cjs @@ -58,8 +58,10 @@ describe('Tokenization', function() { global.test.helpers.url = url; }); - require('./public/basic.cjs'); - require('./public/token-tests.cjs'); + require('./public/layout-tests.cjs'); + require('./public/tokens/token-creation-err.cjs'); + require('./public/tokens/token-happy-path.cjs'); + require('./public/tokens/token-deletion-err.cjs'); beforeEach(function () { return (this.ok = true); diff --git a/Tokenization/webapp/tests/public/basic.cjs b/Tokenization/webapp/tests/public/layout-tests.cjs similarity index 66% rename from Tokenization/webapp/tests/public/basic.cjs rename to Tokenization/webapp/tests/public/layout-tests.cjs index 4e49342c6..a692655dd 100644 --- a/Tokenization/webapp/tests/public/basic.cjs +++ b/Tokenization/webapp/tests/public/layout-tests.cjs @@ -38,4 +38,20 @@ describe('general tests', function() { assert.strictEqual(certsLinkHref, '/certs'); assert.ok(/Certificates/i.test(certsLinkText), `certs link text should include "Cert", got "${certsLinkText}"`); }); + it('/tokens route displays token table and creation form', async function() { + await page.goto(`${url}/tokens`); + + await page.waitForSelector('#content'); + const pageContent = await page.$eval('#content', el => el.textContent || ''); + assert.ok(pageContent.includes('Create Token'), 'Token Creation form should be present'); + + // wait for table rows populated by the API instead of an arbitrary timeout + await page.waitForSelector('table thead'); + await page.waitForSelector('table tbody tr'); // ensures API filled the table + const headers = await page.$$eval('table thead th', ths => ths.map(t => (t.textContent || '').trim())); + + const expected = ['ID', 'Service From', 'Service To', 'Expires at', 'Actions']; + const missing = expected.filter(h => !headers.includes(h)); + assert.strictEqual(missing.length, 0, `Missing table headers: ${missing.join(', ')}`); + }); }); diff --git a/Tokenization/webapp/tests/public/tokens/helpers.cjs b/Tokenization/webapp/tests/public/tokens/helpers.cjs new file mode 100644 index 000000000..235012607 --- /dev/null +++ b/Tokenization/webapp/tests/public/tokens/helpers.cjs @@ -0,0 +1,59 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +async function selectReactOption(reactSelect, optionIndex) { + await reactSelect.click(); + await setTimeout(() => {}, 100); + return reactSelect.$(`ul li:nth-child(${optionIndex})`); +} + +async function fillNumberInput(inputElement, number) { + return inputElement.type(number.toString()); +} + +async function fillAllFormFields(page, reactSelect1, reactSelect2, reactSelect3, expirationInput, button) { + const opt1 = await selectReactOption(reactSelect1, 1); + await opt1.click(); + const select1Content = (await opt1.evaluate(el => el.textContent)).trim(); + + const opt2 = await selectReactOption( reactSelect2, 2); + await opt2.click(); + const select2Content = (await opt2.evaluate(el => el.textContent)).trim(); + + const opt3 = await selectReactOption(reactSelect3, 1); + await opt3.click(); + const select3Content = (await opt3.evaluate(el => el.textContent)).trim(); + + const filledNumber = 10; + await fillNumberInput(expirationInput, filledNumber); + await button.click(); + + const dialogHandle = await page.waitForSelector('.modal'); + const dialogContent = (await dialogHandle.evaluate(el => el.textContent)).trim(); + + return { + dialogHandle, + dialogContent, + select1Content, + select2Content, + select3Content, + filledNumber, + }; +} + +module.exports = { + selectReactOption, + fillNumberInput, + fillAllFormFields, +}; diff --git a/Tokenization/webapp/tests/public/token-tests.cjs b/Tokenization/webapp/tests/public/tokens/token-creation-err.cjs similarity index 75% rename from Tokenization/webapp/tests/public/token-tests.cjs rename to Tokenization/webapp/tests/public/tokens/token-creation-err.cjs index 597ec01ac..4362954db 100644 --- a/Tokenization/webapp/tests/public/token-tests.cjs +++ b/Tokenization/webapp/tests/public/tokens/token-creation-err.cjs @@ -13,46 +13,7 @@ */ const assert = require('assert'); - -async function selectReactOption(reactSelect, optionIndex) { - await reactSelect.click(); - await setTimeout(() => {}, 100); - return reactSelect.$(`ul li:nth-child(${optionIndex})`); -} - -async function fillNumberInput(inputElement, number) { - return inputElement.type(number.toString()); -} - -async function fillAllFormFields(page, reactSelect1, reactSelect2, reactSelect3, expirationInput, button) { - const opt1 = await selectReactOption(reactSelect1, 1); - await opt1.click(); - const select1Content = (await opt1.evaluate(el => el.textContent)).trim(); - - const opt2 = await selectReactOption( reactSelect2, 2); - await opt2.click(); - const select2Content = (await opt2.evaluate(el => el.textContent)).trim(); - - const opt3 = await selectReactOption(reactSelect3, 1); - await opt3.click(); - const select3Content = (await opt3.evaluate(el => el.textContent)).trim(); - - const filledNumber = 10; - await fillNumberInput(expirationInput, filledNumber); - await button.click(); - - const dialogHandle = await page.waitForSelector('.modal'); - const dialogContent = (await dialogHandle.evaluate(el => el.textContent)).trim(); - - return { - dialogHandle, - dialogContent, - select1Content, - select2Content, - select3Content, - filledNumber, - }; -} +const { selectReactOption, fillNumberInput, fillAllFormFields } = require('./helpers.cjs'); describe('token creation unsuccessful', function() { let url; @@ -90,7 +51,7 @@ describe('token creation unsuccessful', function() { const alert = await page.waitForSelector('.alert'); const alertClass1 = await page.$eval('.alert', el => el.className); assert.ok(alertClass1.includes('d-none')); // alert is hidden initially - + await this.button.click(); const alertClass2 = await page.$eval('.alert', el => el.className); assert.ok(alertClass2.includes('d-block')); // alert is shown after submit @@ -133,9 +94,8 @@ describe('token creation unsuccessful', function() { it('filling the form correctly shows proper success message on modal window', async function() { const { dialogContent, select1Content, select2Content, select3Content, filledNumber } = await fillAllFormFields(page, this.reactSelect1, this.reactSelect2, this.reactSelect3, this.expirationInput, this.button); - console.log(dialogContent, select1Content, select2Content, select3Content, filledNumber - ) - assert.ok(dialogContent.includes('Confirm Token Creation')); + + assert.ok(dialogContent.includes('Confirm Token Creation')); assert.ok(dialogContent.includes('Service from: ' + select1Content)); assert.ok(dialogContent.includes('Service to: ' + select2Content)); assert.ok(dialogContent.includes(select3Content)); @@ -151,5 +111,5 @@ describe('token creation unsuccessful', function() { const alertContent = await page.$eval('.alert', el => el.textContent); assert.ok(alertContent.includes('Authorization error')); - }) + }); }); diff --git a/Tokenization/webapp/tests/public/tokens/token-deletion-err.cjs b/Tokenization/webapp/tests/public/tokens/token-deletion-err.cjs new file mode 100644 index 000000000..12918e3e6 --- /dev/null +++ b/Tokenization/webapp/tests/public/tokens/token-deletion-err.cjs @@ -0,0 +1,73 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +const assert = require('assert'); + +describe('token deletion unsuccessful', function() { + let url; + let page; + + before(async function() { + ({ page, helpers: { url } } = test); + }); + + beforeEach(async function() { + await page.goto(`${url}/tokens`); // no admin access + await page.waitForSelector('table'); // wait for API to populate the table + + const _trs = await page.$$('table tbody tr'); + const trs = []; + for (const trEl of _trs) { + const id = await trEl.$eval('td:nth-child(1)', el => (el.textContent || '').trim()); + const deleteBtn = await trEl.$('button.btn-sm.bg-danger') || await trEl.$('button.bg-danger') || null; + trs.push({ tr: trEl, id, deleteBtn }); // collecting relevant info + } + + this.trs = trs; + }); + + // parametrized test for first and third row + const rowsToTest = [ + { index: 0, description: 'first row' }, + { index: 2, description: 'third row' }, + ]; + + rowsToTest.forEach(({ index, description }) => { + it(`correct id displayed in deletion confirmation modal for ${description}`, async function() { + const row = this.trs[index]; + await row.deleteBtn.click(); + + const modalContent = await page.$eval('.modal', el => el.textContent || ''); + const modalClass = await page.$eval('.modal-overlay', el => el.className); + assert.ok (modalClass.includes('d-block'), 'confirmation modal should be shown after clicking delete button'); + assert.ok(modalContent.includes(`id: ${row.id}?`), 'confirmation modal should mention the correct token ID'); + + const closeBtn = await page.$('button:nth-child(1)'); // close button + await closeBtn.click(); + }); + }); + + it('deletion shows auth error alert', async function() { + const row = this.trs[0]; // first row + await row.deleteBtn.click(); + + const confirmBtn = await page.$('button.btn-danger'); // confirm deletion button + await confirmBtn.click(); + + const alertClass = await page.$eval('.alert', el => el.className); + const alertContent = await page.$eval('.alert', el => el.textContent); + assert.ok(alertClass.includes('d-block') && alertClass.includes('bg-danger'), 'error alert should be shown after failed deletion'); + assert.ok(alertContent.includes('Authorization error'), 'error alert should mention authorization issue'); + }) +}); diff --git a/Tokenization/webapp/tests/public/tokens/token-happy-path.cjs b/Tokenization/webapp/tests/public/tokens/token-happy-path.cjs new file mode 100644 index 000000000..69de05482 --- /dev/null +++ b/Tokenization/webapp/tests/public/tokens/token-happy-path.cjs @@ -0,0 +1,80 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +const assert = require('assert'); +const { fillAllFormFields } = require('./helpers.cjs'); + +describe('token creation successful', function() { + let url; + let page; + + before(async function() { + ({ page, helpers: { url } } = test); + }); + + it('happy path: token creation successful', async function() { + await page.goto(`${url}/tokens/new?access=admin`); // admin access added + setTimeout(() => {}, 1000); + const { dialogHandle } = await fillAllFormFields( + page, + await page.waitForSelector('#first-service-select'), + await page.waitForSelector('#second-service-select'), + await page.waitForSelector('#http-select-methods'), + await page.waitForSelector('.my-input > input[type="number"]'), + await page.waitForSelector('button[type="submit"]'), + ); + const btn = await dialogHandle.$('.btn-success'); // accept button + + // TODO: mock/fake the API and verify the request payload here + await btn.click(); + const className = await page.$eval('.alert', el => el.className); + const content = await page.$eval('.alert', el => el.textContent); + assert.ok(className.includes('d-block') && className.includes('bg-success'), 'success alert should be shown after creating token'); + }); + + it('happy path: token deletion successful', async function() { + await page.goto(`${url}/tokens?access=admin`); // admin access added + await page.waitForSelector('table tbody tr'); + + const _trs = await page.$$('table tbody tr'); + + const trs = []; + for (const trEl of _trs) { + const id = await trEl.$eval('td:nth-child(1)', el => (el.textContent || '').trim()); + const deleteBtn = await trEl.$('button.btn-sm.bg-danger') || await trEl.$('button.bg-danger') || null; + trs.push({ tr: trEl, id, deleteBtn }); // collecting relevant info + } + + const row = trs[1]; // second row + assert.ok(row, 'expected at least two table rows'); + assert.ok(row.deleteBtn, `delete button not found in row with id ${row.id}`); + + await row.deleteBtn.click(); + + const modal = await page.waitForSelector('.modal-overlay'); + const className = await page.$eval('.modal-overlay', el => el.className); + const content = await page.$eval('.modal-overlay', el => el.textContent || ''); + assert.ok(className.includes('d-block'), 'confirmation modal should be shown after clicking delete button'); + assert.ok(content.includes(`id: ${row.id}?`), 'confirmation modal should mention the correct token ID'); + + const confirmBtn = await modal.$('button.btn-danger'); + assert.ok(confirmBtn, 'confirm button not found in modal'); + await confirmBtn.click(); + + // TODO: mock/fake the API and verify the deletion request here + const alertClass = await page.$eval('.alert', el => el.className); + assert.ok(alertClass.includes('d-block') && alertClass.includes('bg-success'), 'success alert should be shown after deleting token'); + + }); +}); From 7c3798bf60fc8453a6a260c082193d79bbfd67a4 Mon Sep 17 00:00:00 2001 From: Igor S Date: Tue, 25 Nov 2025 00:43:33 +0100 Subject: [PATCH 42/47] linting and delete of unused variables --- .../public/tokens/token-creation-err.cjs | 2 +- .../public/tokens/token-deletion-err.cjs | 20 +++++++++---------- .../tests/public/tokens/token-happy-path.cjs | 1 - 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Tokenization/webapp/tests/public/tokens/token-creation-err.cjs b/Tokenization/webapp/tests/public/tokens/token-creation-err.cjs index 4362954db..cba0895ef 100644 --- a/Tokenization/webapp/tests/public/tokens/token-creation-err.cjs +++ b/Tokenization/webapp/tests/public/tokens/token-creation-err.cjs @@ -48,7 +48,7 @@ describe('token creation unsuccessful', function() { }); it('Not filling form shows error alert', async function() { - const alert = await page.waitForSelector('.alert'); + await page.waitForSelector('.alert'); const alertClass1 = await page.$eval('.alert', el => el.className); assert.ok(alertClass1.includes('d-none')); // alert is hidden initially diff --git a/Tokenization/webapp/tests/public/tokens/token-deletion-err.cjs b/Tokenization/webapp/tests/public/tokens/token-deletion-err.cjs index 12918e3e6..68f490a5b 100644 --- a/Tokenization/webapp/tests/public/tokens/token-deletion-err.cjs +++ b/Tokenization/webapp/tests/public/tokens/token-deletion-err.cjs @@ -58,16 +58,16 @@ describe('token deletion unsuccessful', function() { }); }); - it('deletion shows auth error alert', async function() { - const row = this.trs[0]; // first row - await row.deleteBtn.click(); + it('deletion shows auth error alert', async function() { + const row = this.trs[0]; // first row + await row.deleteBtn.click(); - const confirmBtn = await page.$('button.btn-danger'); // confirm deletion button - await confirmBtn.click(); + const confirmBtn = await page.$('button.btn-danger'); // confirm deletion button + await confirmBtn.click(); - const alertClass = await page.$eval('.alert', el => el.className); - const alertContent = await page.$eval('.alert', el => el.textContent); - assert.ok(alertClass.includes('d-block') && alertClass.includes('bg-danger'), 'error alert should be shown after failed deletion'); - assert.ok(alertContent.includes('Authorization error'), 'error alert should mention authorization issue'); - }) + const alertClass = await page.$eval('.alert', el => el.className); + const alertContent = await page.$eval('.alert', el => el.textContent); + assert.ok(alertClass.includes('d-block') && alertClass.includes('bg-danger'), 'error alert should be shown after failed deletion'); + assert.ok(alertContent.includes('Authorization error'), 'error alert should mention authorization issue'); + }); }); diff --git a/Tokenization/webapp/tests/public/tokens/token-happy-path.cjs b/Tokenization/webapp/tests/public/tokens/token-happy-path.cjs index 69de05482..d0447afb4 100644 --- a/Tokenization/webapp/tests/public/tokens/token-happy-path.cjs +++ b/Tokenization/webapp/tests/public/tokens/token-happy-path.cjs @@ -39,7 +39,6 @@ describe('token creation successful', function() { // TODO: mock/fake the API and verify the request payload here await btn.click(); const className = await page.$eval('.alert', el => el.className); - const content = await page.$eval('.alert', el => el.textContent); assert.ok(className.includes('d-block') && className.includes('bg-success'), 'success alert should be shown after creating token'); }); From 6c28e1f4b62cbe39e25a76a504dceb801d87fe2a Mon Sep 17 00:00:00 2001 From: Igor S Date: Tue, 25 Nov 2025 05:28:02 +0100 Subject: [PATCH 43/47] For better timeout Alert now is added to DOM conditionally over using CSS. Tests were adjusted to that --- .../app/components/tokens/token-table.tsx | 5 ++- .../webapp/app/components/window/alert.tsx | 24 +++++++++-- .../webapp/app/routes/tokens/create.tsx | 40 +++++++++++-------- .../public/tokens/token-creation-err.cjs | 21 +++++----- .../public/tokens/token-deletion-err.cjs | 7 ++-- .../tests/public/tokens/token-happy-path.cjs | 10 +++-- 6 files changed, 70 insertions(+), 37 deletions(-) diff --git a/Tokenization/webapp/app/components/tokens/token-table.tsx b/Tokenization/webapp/app/components/tokens/token-table.tsx index 65f3559bf..9ae8cdc9d 100644 --- a/Tokenization/webapp/app/components/tokens/token-table.tsx +++ b/Tokenization/webapp/app/components/tokens/token-table.tsx @@ -95,6 +95,7 @@ export function TokenTable({ tokens }: { tokens: Token[] }) { const [openA, setOpenA] = useState(false); // Used for alert logic const [tokenId, setTokenId] = useState(''); const auth = useAuth('admin'); + const [key, setKey] = useState(0); // used to force re-mount of Alert component const successInfo = { title: 'Token deleted', @@ -112,6 +113,7 @@ export function TokenTable({ tokens }: { tokens: Token[] }) { // eslint-disable-next-line no-console console.log(`Deleting token no. ${tokenId}`); } + setKey((prevKey) => prevKey + 1); setOpenA(true); setTokenId(''); }; @@ -142,10 +144,11 @@ export function TokenTable({ tokens }: { tokens: Token[] }) { {auth ? successInfo.title : failureInfo.title} {auth ? successInfo.content : failureInfo.content} diff --git a/Tokenization/webapp/app/components/window/alert.tsx b/Tokenization/webapp/app/components/window/alert.tsx index 09fb8aa21..15bb7c4ee 100644 --- a/Tokenization/webapp/app/components/window/alert.tsx +++ b/Tokenization/webapp/app/components/window/alert.tsx @@ -15,6 +15,15 @@ import { type WindowInterface } from './window.d'; import { useFullWindowLogic } from '../hooks/useWindowLogic'; +// Used to represent an alert message +// key might be used to force re-mounting the component for repeated alerts +export interface AlertType { + key: number; + title: string; + message: string; + success: boolean; + } + /** * Alert * @@ -22,23 +31,30 @@ import { useFullWindowLogic } from '../hooks/useWindowLogic'; * * @param {object} props - component props (see WindowInterface in window.d.ts) * @param {React.ReactNode} props.children - alert content (typically WindowTitle + WindowContent + optional WindowCloseIcon) - * @param {boolean} props.open - whether the modal is visible (provided via DPB) - * @param {React.Dispatch>} props.setOpen - dispatcher to control visibility (provided via DPB) + * @param {boolean} props.open - whether the modal is mounted (provided via DPB) + * @param {React.Dispatch>} props.setOpen - dispatcher to control mounting (provided via DPB) * @param {() => void} [props.onClose] - optional callback invoked when alert closes * @param {number|null} [props.timeout] - optional auto-close timeout in milliseconds (useful for transient alerts) * @param {string} [props.className] - additional CSS classes applied to the modal container * (expected to be used to control bg-color, but can be more versatile) * Behaviour: * - Delegates lifecycle (timeout, close action) and child wiring to useFullWindowLogic(props). + * - In contrast to Modal, doesn't use visibility control through CSS, but simply doesn't render if !open. + * - Thanks to that, timeout-based auto-close works more expectably. * - Renders title, content and closeIcon produced by the hook. */ const Alert = (props: WindowInterface) => { const { className } = props; - const { visibility, ui_elements: { title, content, closeIcon } } = useFullWindowLogic(props); + const { ui_elements: { title, content, closeIcon } } = useFullWindowLogic(props); + + const {open} = props; + if (!open) { + return null; + } return ( -
    +
    {title ?? ''} {closeIcon ?? ''} diff --git a/Tokenization/webapp/app/routes/tokens/create.tsx b/Tokenization/webapp/app/routes/tokens/create.tsx index 853211610..90dde8733 100644 --- a/Tokenization/webapp/app/routes/tokens/create.tsx +++ b/Tokenization/webapp/app/routes/tokens/create.tsx @@ -22,7 +22,7 @@ import { FormSelect, FormSelectMulti } from '~/components/form/form-select'; import { SelectGroup } from '~/components/form/select-group'; import { ResetButton, SubmitButton } from '~/components/form/form-buttons'; import { useAuth } from '~/hooks/session'; -import Alert from '~/components/window/alert'; +import Alert, {type AlertType} from '~/components/window/alert'; import { WindowButtonAccept, WindowButtonCancel, WindowCloseIcon, WindowContent, WindowTitle } from '~/components/window/window-objects'; import Modal from '~/components/window/modal'; @@ -69,9 +69,7 @@ export default function CreateToken({ loaderData }: { loaderData?: OptionType[] const [openAlert, setOpenAlert] = useState(false); const [openModal, setOpenModal] = useState(false); - const [title, setTitle] = useState('Token created'); - const [msg, setMsg] = useState('Token has been created successfully.'); - const [success, setSuccess] = useState(false); + const [alert, setAlert] = useState(null); const onSubmit = () => { if (expirationTime && firstSelectedService && secondSelectedService && selectedMethods.length > 0) { @@ -91,9 +89,12 @@ export default function CreateToken({ loaderData }: { loaderData?: OptionType[] message += 'HTTP methods, '; } message = message.slice(0, -2); - setTitle('Form incomplete'); - setMsg(message); - setSuccess(false); + setAlert({ + key: Date.now(), + title: 'Form incomplete', + message, + success: false, + }) setOpenAlert(true); } }; @@ -109,13 +110,19 @@ export default function CreateToken({ loaderData }: { loaderData?: OptionType[] if (auth) { // eslint-disable-next-line no-console console.log('Creating token'); - setTitle('Token created'); - setMsg('Token has been created successfully.'); - setSuccess(true); + setAlert({ + key: Date.now(), + title: 'Token created', + message: 'Token has been created successfully.', + success: true, + }); } else { - setTitle('Authorization error'); - setMsg('You do not have permission to perform this operation.'); - setSuccess(false); + setAlert({ + key: Date.now(), + title: 'Authorization error', + message: 'You do not have permission to perform this operation.', + success: false, + }); } setOpenAlert(true); }; @@ -174,13 +181,14 @@ export default function CreateToken({ loaderData }: { loaderData?: OptionType[]
    - {title} - {msg} + {alert?.title} + {alert?.message} el.className); - assert.ok(alertClass1.includes('d-none')); // alert is hidden initially + const n_alert = await page.$('.alert'); + assert.ok(!n_alert); // alert is not present before submit await this.button.click(); - const alertClass2 = await page.$eval('.alert', el => el.className); - assert.ok(alertClass2.includes('d-block')); // alert is shown after submit + const alert = await page.waitForSelector('.alert'); + assert.ok(alert); // alert is shown after submit }); describe('filling partially the form', function() { @@ -65,7 +64,8 @@ describe('token creation unsuccessful', function() { await opt2.click(); await this.button.click(); - const alertContent = await page.$eval('.alert', el => el.textContent); + const alert = await page.waitForSelector('.alert') + const alertContent = await alert.evaluate(el => el.textContent); assert.ok(alertContent.includes('Expiration time', 'HTTP methods')); }); @@ -75,7 +75,8 @@ describe('token creation unsuccessful', function() { await fillNumberInput(this.expirationInput, 10); await this.button.click(); - const alertContent = await page.$eval('.alert', el => el.textContent); + const alert = await page.waitForSelector('.alert'); + const alertContent = await alert.evaluate(el => el.textContent); assert.ok(alertContent.includes('First service', 'HTTP methods')); }); @@ -84,7 +85,8 @@ describe('token creation unsuccessful', function() { await opt3.click(); await this.button.click(); - const alertContent = await page.$eval('.alert', el => el.textContent); + const alert = await page.waitForSelector('.alert'); + const alertContent = await alert.evaluate(el => el.textContent); assert.ok( alertContent.includes('First service', 'Second service', 'Expiration time'), ); @@ -109,7 +111,8 @@ describe('token creation unsuccessful', function() { const confirmButton = await dialogHandle.$('button:nth-child(2)'); await confirmButton.click(); - const alertContent = await page.$eval('.alert', el => el.textContent); + const alert = await page.waitForSelector('.alert'); + const alertContent = await alert.evaluate(el => el.textContent); assert.ok(alertContent.includes('Authorization error')); }); }); diff --git a/Tokenization/webapp/tests/public/tokens/token-deletion-err.cjs b/Tokenization/webapp/tests/public/tokens/token-deletion-err.cjs index 68f490a5b..ac07adab0 100644 --- a/Tokenization/webapp/tests/public/tokens/token-deletion-err.cjs +++ b/Tokenization/webapp/tests/public/tokens/token-deletion-err.cjs @@ -65,9 +65,10 @@ describe('token deletion unsuccessful', function() { const confirmBtn = await page.$('button.btn-danger'); // confirm deletion button await confirmBtn.click(); - const alertClass = await page.$eval('.alert', el => el.className); - const alertContent = await page.$eval('.alert', el => el.textContent); - assert.ok(alertClass.includes('d-block') && alertClass.includes('bg-danger'), 'error alert should be shown after failed deletion'); + const alert = await page.waitForSelector('.alert'); + const alertClass = await alert.evaluate(el => el.className); + const alertContent = await alert.evaluate(el => el.textContent); + assert.ok(alertClass.includes('bg-danger'), 'error alert should be shown after failed deletion'); assert.ok(alertContent.includes('Authorization error'), 'error alert should mention authorization issue'); }); }); diff --git a/Tokenization/webapp/tests/public/tokens/token-happy-path.cjs b/Tokenization/webapp/tests/public/tokens/token-happy-path.cjs index d0447afb4..f1bbc6e7b 100644 --- a/Tokenization/webapp/tests/public/tokens/token-happy-path.cjs +++ b/Tokenization/webapp/tests/public/tokens/token-happy-path.cjs @@ -38,8 +38,9 @@ describe('token creation successful', function() { // TODO: mock/fake the API and verify the request payload here await btn.click(); - const className = await page.$eval('.alert', el => el.className); - assert.ok(className.includes('d-block') && className.includes('bg-success'), 'success alert should be shown after creating token'); + const alert = await page.waitForSelector('.alert'); + const className = await alert.evaluate(el => el.className); + assert.ok(className.includes('bg-success'), 'success alert should be shown after creating token'); }); it('happy path: token deletion successful', async function() { @@ -72,8 +73,9 @@ describe('token creation successful', function() { await confirmBtn.click(); // TODO: mock/fake the API and verify the deletion request here - const alertClass = await page.$eval('.alert', el => el.className); - assert.ok(alertClass.includes('d-block') && alertClass.includes('bg-success'), 'success alert should be shown after deleting token'); + const alert = await page.waitForSelector('.alert'); + const alertClass = await alert.evaluate(el => el.className); + assert.ok(alertClass.includes('bg-success'), 'success alert should be shown after deleting token'); }); }); From 1398a2d37f909f470bb3a99a8c3d1ec0985b5c79 Mon Sep 17 00:00:00 2001 From: Igor S Date: Tue, 25 Nov 2025 05:52:00 +0100 Subject: [PATCH 44/47] refactor for token-form to use context api and for form component to be reusable --- .../app/components/tokens/token-form.tsx | 71 +++++++ .../webapp/app/contexts/tokens/token-form.tsx | 105 ++++++++++ .../webapp/app/hooks/tokens/token-form.tsx | 8 + .../webapp/app/routes/tokens/create.tsx | 192 +----------------- 4 files changed, 194 insertions(+), 182 deletions(-) create mode 100644 Tokenization/webapp/app/components/tokens/token-form.tsx create mode 100644 Tokenization/webapp/app/contexts/tokens/token-form.tsx create mode 100644 Tokenization/webapp/app/hooks/tokens/token-form.tsx diff --git a/Tokenization/webapp/app/components/tokens/token-form.tsx b/Tokenization/webapp/app/components/tokens/token-form.tsx new file mode 100644 index 000000000..a8dc2ef4d --- /dev/null +++ b/Tokenization/webapp/app/components/tokens/token-form.tsx @@ -0,0 +1,71 @@ +import { FormInput } from '~/components/form/form-input'; +import { FormSelectMulti, FormSelect } from '~/components/form/form-select'; +import { SelectGroup } from '~/components/form/select-group'; +import { ResetButton, SubmitButton } from '~/components/form/form-buttons'; +import { useTokenForm } from '~/hooks/tokens/token-form'; +import { Form } from '../form/form'; +import Modal from '~/components/window/modal'; +import { WindowTitle, WindowContent, WindowButtonAccept, WindowButtonCancel, WindowCloseIcon } from '~/components/window/window-objects'; +import Alert from '../window/alert'; + +const httpMethodOptions = [ + { value: 'GET', label: 'GET' }, + { value: 'POST', label: 'POST' }, + { value: 'PUT', label: 'PUT' }, + { value: 'DELETE', label: 'DELETE' }, +]; + +export function TokenForm() { + const { state, actions } = useTokenForm(); + return ( + + + + {state.loaderData && ( + <> + + + + +
    + + +
    + + )} + + ); +} + +export function TokenFormWindows() { + const { state, actions } = useTokenForm(); + return ( + <> + + Confirm Token Creation + +
    +
    Are you sure you want to create the token with the specified settings?
    +
    Service from: {state.firstLabel}
    +
    Service to: {state.secondLabel}
    +
    Expiration time: {state.expirationTime} hours
    +
    HTTP methods: {state.selectedMethods.join(', ')}
    +
    +
    + + +
    + + {state.alert?.title} + {state.alert?.message} + + + + ); +} \ No newline at end of file diff --git a/Tokenization/webapp/app/contexts/tokens/token-form.tsx b/Tokenization/webapp/app/contexts/tokens/token-form.tsx new file mode 100644 index 000000000..8d3dddc65 --- /dev/null +++ b/Tokenization/webapp/app/contexts/tokens/token-form.tsx @@ -0,0 +1,105 @@ +import React, { createContext, useEffect, useMemo, useState } from 'react'; +import type { OptionType, HttpMethod } from '~/utils/types'; +import type { AlertType } from '~/components/window/alert'; +import { useAuth } from '~/hooks/session'; + +type State = { + loaderData?: OptionType[]; + expirationTime: string; + firstSelectedService: string; + secondSelectedService: string; + selectedMethods: HttpMethod[]; + firstLabel: string; + secondLabel: string; + openAlert: boolean; + openModal: boolean; + alert: AlertType | null; +}; + +type Actions = { + setExpirationTime: React.Dispatch>; + setFirstSelectedService: React.Dispatch>; + setSecondSelectedService: React.Dispatch>; + setSelectedMethods: React.Dispatch>; + onSubmit: () => void; + onReset: () => void; + callApi: () => void; + setOpenAlert: React.Dispatch>; + setOpenModal: React.Dispatch>; +}; + +export const TokenFormContext = createContext<{ state: State; actions: Actions } | undefined>(undefined); + +export function TokenFormProvider({ loaderData, children }: { loaderData?: OptionType[]; children: React.ReactNode }) { + const [expirationTime, setExpirationTime] = useState(''); + const [firstSelectedService, setFirstSelectedService] = useState(''); + const [secondSelectedService, setSecondSelectedService] = useState(''); + const [selectedMethods, setSelectedMethods] = useState([]); + const [firstLabel, setFirstLabel] = useState(''); + const [secondLabel, setSecondLabel] = useState(''); + const [openAlert, setOpenAlert] = useState(false); + const [openModal, setOpenModal] = useState(false); + const [alert, setAlert] = useState(null); + const auth = useAuth("admin"); + + useEffect(() => { + if (!loaderData) return; + const f = loaderData.find(o => o.value === firstSelectedService)?.label ?? ''; + const s = loaderData.find(o => o.value === secondSelectedService)?.label ?? ''; + setFirstLabel(f); + setSecondLabel(s); + }, [firstSelectedService, secondSelectedService, loaderData]); + + const onSubmit = () => { + if (expirationTime && firstSelectedService && secondSelectedService && selectedMethods.length > 0) { + setOpenModal(true); + } else { + let message = 'Please fill in all required fields: '; + if (!firstSelectedService) message += 'First service, '; + if (!secondSelectedService) message += 'Second service, '; + if (!expirationTime) message += 'Expiration time, '; + if (selectedMethods.length === 0) message += 'HTTP methods, '; + message = message.slice(0, -2); + setAlert({ key: Date.now(), title: 'Form incomplete', message, success: false }); + setOpenAlert(true); + } + }; + + const onReset = () => { + setExpirationTime(''); + setFirstSelectedService(''); + setSecondSelectedService(''); + setSelectedMethods([]); + }; + + const callApi = () => { + if(auth) { + setAlert({ key: Date.now(), title: 'Token created', message: 'Token has been created successfully.', success: true }); + } else { + setAlert({ key: Date.now(), title: 'Authorization error', message: 'You cannot perform this action without authorization.', success: false }); + } + setOpenAlert(true); + setOpenModal(false); + }; + + const state = useMemo(() => ({ + loaderData, + expirationTime, + firstSelectedService, + secondSelectedService, + selectedMethods, + firstLabel, + secondLabel, + openAlert, + openModal, + alert, + }), [loaderData, expirationTime, firstSelectedService, secondSelectedService, selectedMethods, firstLabel, secondLabel, openAlert, openModal, alert]); + + const actions: Actions = useMemo(() => ({ + setExpirationTime, setFirstSelectedService, setSecondSelectedService, setSelectedMethods, + onSubmit, onReset, callApi, setOpenAlert, setOpenModal, + }), [setExpirationTime, setFirstSelectedService, setSecondSelectedService, setSelectedMethods, onSubmit, onReset, callApi, setOpenAlert, setOpenModal]); + + return {children}; +} + diff --git a/Tokenization/webapp/app/hooks/tokens/token-form.tsx b/Tokenization/webapp/app/hooks/tokens/token-form.tsx new file mode 100644 index 000000000..91928f088 --- /dev/null +++ b/Tokenization/webapp/app/hooks/tokens/token-form.tsx @@ -0,0 +1,8 @@ +import { useContext } from "react"; +import { TokenFormContext } from "~/contexts/tokens/token-form"; + +export function useTokenForm() { + const ctx = useContext(TokenFormContext); + if (!ctx) throw new Error('useTokenForm must be used inside TokenFormProvider'); + return ctx; +} \ No newline at end of file diff --git a/Tokenization/webapp/app/routes/tokens/create.tsx b/Tokenization/webapp/app/routes/tokens/create.tsx index 90dde8733..6c2595b54 100644 --- a/Tokenization/webapp/app/routes/tokens/create.tsx +++ b/Tokenization/webapp/app/routes/tokens/create.tsx @@ -12,19 +12,10 @@ * or submit itself to any jurisdiction. */ -import { useState, useEffect } from 'react'; - -import type { OptionType, HttpMethod } from '~/utils/types'; -import { Form } from '~/components/form/form'; +import { TokenFormProvider } from '~/contexts/tokens/token-form'; +import { TokenForm, TokenFormWindows } from '~/components/tokens/token-form'; import { Box1_1 } from '~/components/box'; -import { FormInput } from '~/components/form/form-input'; -import { FormSelect, FormSelectMulti } from '~/components/form/form-select'; -import { SelectGroup } from '~/components/form/select-group'; -import { ResetButton, SubmitButton } from '~/components/form/form-buttons'; -import { useAuth } from '~/hooks/session'; -import Alert, {type AlertType} from '~/components/window/alert'; -import { WindowButtonAccept, WindowButtonCancel, WindowCloseIcon, WindowContent, WindowTitle } from '~/components/window/window-objects'; -import Modal from '~/components/window/modal'; +import type { OptionType } from '~/utils/types'; // eslint-disable-next-line jsdoc/require-jsdoc export function clientLoader(): OptionType[] { @@ -36,179 +27,16 @@ export function clientLoader(): OptionType[] { ]; } -// HTTP Method options -const httpMethodOptions = [ - { value: 'GET', label: 'GET' }, - { value: 'POST', label: 'POST' }, - { value: 'PUT', label: 'PUT' }, - { value: 'DELETE', label: 'DELETE' }, -]; - /** * Component is used for /tokens/new route to create new tokens. */ export default function CreateToken({ loaderData }: { loaderData?: OptionType[] }) { - const [expirationTime, setExpirationTime] = useState(''); - const [firstSelectedService, setFirstSelectedService] = useState(''); - const [secondSelectedService, setSecondSelectedService] = useState(''); - const [selectedMethods, setSelectedMethods] = useState([]); - - const [firstSelectedLabel, setFirstSelectedLabel] = useState(''); - const [secondSelectedLabel, setSecondSelectedLabel] = useState(''); - - useEffect(() => { - if (loaderData) { - const firstLabel = loaderData.find(option => option.value === firstSelectedService)?.label ?? ''; - const secondLabel = loaderData.find(option => option.value === secondSelectedService)?.label ?? ''; - setFirstSelectedLabel(firstLabel); - setSecondSelectedLabel(secondLabel); - } - }, [firstSelectedService, secondSelectedService, loaderData]); - - const auth = useAuth('admin'); - const [openAlert, setOpenAlert] = useState(false); - const [openModal, setOpenModal] = useState(false); - - const [alert, setAlert] = useState(null); - - const onSubmit = () => { - if (expirationTime && firstSelectedService && secondSelectedService && selectedMethods.length > 0) { - setOpenModal(true); - } else { - let message = 'Please fill in all required fields: '; - if (!firstSelectedService) { - message += 'First service, '; - } - if (!secondSelectedService) { - message += 'Second service, '; - } - if (!expirationTime) { - message += 'Expiration time, '; - } - if (selectedMethods.length == 0) { - message += 'HTTP methods, '; - } - message = message.slice(0, -2); - setAlert({ - key: Date.now(), - title: 'Form incomplete', - message, - success: false, - }) - setOpenAlert(true); - } - }; - - const onReset = () => { - setExpirationTime(''); - setFirstSelectedService(''); - setSecondSelectedService(''); - setSelectedMethods([]); - }; - - const callApi = () => { - if (auth) { - // eslint-disable-next-line no-console - console.log('Creating token'); - setAlert({ - key: Date.now(), - title: 'Token created', - message: 'Token has been created successfully.', - success: true, - }); - } else { - setAlert({ - key: Date.now(), - title: 'Authorization error', - message: 'You do not have permission to perform this operation.', - success: false, - }); - } - setOpenAlert(true); - }; - - return ( <> - -
    -
    - - {/* - labelText='Expiration Time (date)' - value={null} - setValue={null} - inputProps={{ type: 'datetime-local' }} - /> */} - - {loaderData && - <> - - - - -
    - - -
    - - } - -
    -
    - - {alert?.title} - {alert?.message} - - - - Confirm Token Creation - -
    -
    Are you sure you want to create the token with the specified settings?
    -
    Service from: {firstSelectedLabel}
    -
    Service to: {secondSelectedLabel}
    -
    Expiration time: {expirationTime} hours
    -
    HTTP methods: {selectedMethods.join(', ')}
    -
    -
    - - -
    - + return ( + + + + + + ); } From b35948c4d1b2f2dfd904b8e25a93a29458dd3da5 Mon Sep 17 00:00:00 2001 From: Igor S Date: Wed, 26 Nov 2025 12:47:26 +0100 Subject: [PATCH 45/47] Documentation and dependency lists updated - functions in context wrapped with useCallback. --- .../app/components/tokens/token-form.tsx | 106 +++++++++++++----- .../app/components/tokens/token-table.tsx | 2 +- .../webapp/app/components/window/alert.tsx | 16 +-- .../webapp/app/contexts/tokens/token-form.tsx | 98 ++++++++++++---- .../webapp/app/hooks/tokens/token-form.tsx | 28 ++++- .../public/tokens/token-creation-err.cjs | 2 +- 6 files changed, 189 insertions(+), 63 deletions(-) diff --git a/Tokenization/webapp/app/components/tokens/token-form.tsx b/Tokenization/webapp/app/components/tokens/token-form.tsx index a8dc2ef4d..6926d1f5e 100644 --- a/Tokenization/webapp/app/components/tokens/token-form.tsx +++ b/Tokenization/webapp/app/components/tokens/token-form.tsx @@ -1,3 +1,17 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + import { FormInput } from '~/components/form/form-input'; import { FormSelectMulti, FormSelect } from '~/components/form/form-select'; import { SelectGroup } from '~/components/form/select-group'; @@ -15,17 +29,46 @@ const httpMethodOptions = [ { value: 'DELETE', label: 'DELETE' }, ]; +/** + * Not reusable Token Form component + */ export function TokenForm() { const { state, actions } = useTokenForm(); return (
    - - + + {state.loaderData && ( <> - - + +
    @@ -37,35 +80,38 @@ export function TokenForm() { ); } +/** + * Not reusable Windows prepared for Token Form component + */ export function TokenFormWindows() { const { state, actions } = useTokenForm(); return ( <> - - Confirm Token Creation - -
    -
    Are you sure you want to create the token with the specified settings?
    -
    Service from: {state.firstLabel}
    -
    Service to: {state.secondLabel}
    -
    Expiration time: {state.expirationTime} hours
    -
    HTTP methods: {state.selectedMethods.join(', ')}
    -
    -
    - - -
    - - {state.alert?.title} - {state.alert?.message} - - + + Confirm Token Creation + +
    +
    Are you sure you want to create the token with the specified settings?
    +
    Service from: {state.firstLabel}
    +
    Service to: {state.secondLabel}
    +
    Expiration time: {state.expirationTime} hours
    +
    HTTP methods: {state.selectedMethods.join(', ')}
    +
    +
    + + +
    + + {state.alert?.title} + {state.alert?.message} + + ); -} \ No newline at end of file +} diff --git a/Tokenization/webapp/app/components/tokens/token-table.tsx b/Tokenization/webapp/app/components/tokens/token-table.tsx index 9ae8cdc9d..73d7ec19e 100644 --- a/Tokenization/webapp/app/components/tokens/token-table.tsx +++ b/Tokenization/webapp/app/components/tokens/token-table.tsx @@ -95,7 +95,7 @@ export function TokenTable({ tokens }: { tokens: Token[] }) { const [openA, setOpenA] = useState(false); // Used for alert logic const [tokenId, setTokenId] = useState(''); const auth = useAuth('admin'); - const [key, setKey] = useState(0); // used to force re-mount of Alert component + const [key, setKey] = useState(0); // Used to force re-mount of Alert component const successInfo = { title: 'Token deleted', diff --git a/Tokenization/webapp/app/components/window/alert.tsx b/Tokenization/webapp/app/components/window/alert.tsx index 15bb7c4ee..07ea6dcb2 100644 --- a/Tokenization/webapp/app/components/window/alert.tsx +++ b/Tokenization/webapp/app/components/window/alert.tsx @@ -16,13 +16,13 @@ import { type WindowInterface } from './window.d'; import { useFullWindowLogic } from '../hooks/useWindowLogic'; // Used to represent an alert message -// key might be used to force re-mounting the component for repeated alerts +// Key might be used to force re-mounting the component for repeated alerts export interface AlertType { - key: number; - title: string; - message: string; - success: boolean; - } + key: number; + title: string; + message: string; + success: boolean; +} /** * Alert @@ -47,8 +47,8 @@ const Alert = (props: WindowInterface) => { const { className } = props; const { ui_elements: { title, content, closeIcon } } = useFullWindowLogic(props); - - const {open} = props; + + const { open } = props; if (!open) { return null; } diff --git a/Tokenization/webapp/app/contexts/tokens/token-form.tsx b/Tokenization/webapp/app/contexts/tokens/token-form.tsx index 8d3dddc65..0ec777ea8 100644 --- a/Tokenization/webapp/app/contexts/tokens/token-form.tsx +++ b/Tokenization/webapp/app/contexts/tokens/token-form.tsx @@ -1,4 +1,18 @@ -import React, { createContext, useEffect, useMemo, useState } from 'react'; +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import React, { createContext, useCallback, useEffect, useMemo, useState } from 'react'; import type { OptionType, HttpMethod } from '~/utils/types'; import type { AlertType } from '~/components/window/alert'; import { useAuth } from '~/hooks/session'; @@ -30,6 +44,16 @@ type Actions = { export const TokenFormContext = createContext<{ state: State; actions: Actions } | undefined>(undefined); +/** + * Form context provider which holds all state and actions for Token Form. + * It is used to wrap the Token Form and its windows - for them to simplify the props passing. + * It can be the way for all routes to have their own context providers in the future. + * As its elegant and not global solution, actions and state can be used corectly only inside + * components wrapped by this provider. + * + * @example {} + * @usage It is used in webapp/app/hooks/tokens/token-form.tsx - by using useContext(TokenFormContext) + */ export function TokenFormProvider({ loaderData, children }: { loaderData?: OptionType[]; children: React.ReactNode }) { const [expirationTime, setExpirationTime] = useState(''); const [firstSelectedService, setFirstSelectedService] = useState(''); @@ -40,47 +64,63 @@ export function TokenFormProvider({ loaderData, children }: { loaderData?: Optio const [openAlert, setOpenAlert] = useState(false); const [openModal, setOpenModal] = useState(false); const [alert, setAlert] = useState(null); - const auth = useAuth("admin"); + const auth = useAuth('admin'); useEffect(() => { - if (!loaderData) return; + if (!loaderData) { + return; + } const f = loaderData.find(o => o.value === firstSelectedService)?.label ?? ''; const s = loaderData.find(o => o.value === secondSelectedService)?.label ?? ''; setFirstLabel(f); setSecondLabel(s); }, [firstSelectedService, secondSelectedService, loaderData]); - const onSubmit = () => { + const onSubmit = useCallback(() => { if (expirationTime && firstSelectedService && secondSelectedService && selectedMethods.length > 0) { setOpenModal(true); } else { let message = 'Please fill in all required fields: '; - if (!firstSelectedService) message += 'First service, '; - if (!secondSelectedService) message += 'Second service, '; - if (!expirationTime) message += 'Expiration time, '; - if (selectedMethods.length === 0) message += 'HTTP methods, '; + if (!firstSelectedService) { + message += 'First service, '; + } + if (!secondSelectedService) { + message += 'Second service, '; + } + if (!expirationTime) { + message += 'Expiration time, '; + } + if (selectedMethods.length === 0) { + message += 'HTTP methods, '; + } message = message.slice(0, -2); setAlert({ key: Date.now(), title: 'Form incomplete', message, success: false }); setOpenAlert(true); } - }; + }, [expirationTime, firstSelectedService, secondSelectedService, selectedMethods]); - const onReset = () => { + const onReset = useCallback(() => { setExpirationTime(''); setFirstSelectedService(''); setSecondSelectedService(''); setSelectedMethods([]); - }; + }, [setExpirationTime, setFirstSelectedService, setSecondSelectedService, setSelectedMethods]); - const callApi = () => { - if(auth) { - setAlert({ key: Date.now(), title: 'Token created', message: 'Token has been created successfully.', success: true }); + const callApi = useCallback(() => { + if (auth) { + setAlert({ key: Date.now(), + title: 'Token created', + message: 'Token has been created successfully.', + success: true }); } else { - setAlert({ key: Date.now(), title: 'Authorization error', message: 'You cannot perform this action without authorization.', success: false }); + setAlert({ key: Date.now(), + title: 'Authorization error', + message: 'You cannot perform this action without authorization.', + success: false }); } setOpenAlert(true); setOpenModal(false); - }; + }, [auth, setOpenAlert, setAlert, setOpenModal]); const state = useMemo(() => ({ loaderData, @@ -93,13 +133,33 @@ export function TokenFormProvider({ loaderData, children }: { loaderData?: Optio openAlert, openModal, alert, - }), [loaderData, expirationTime, firstSelectedService, secondSelectedService, selectedMethods, firstLabel, secondLabel, openAlert, openModal, alert]); + }), + [loaderData, + expirationTime, + firstSelectedService, + secondSelectedService, + selectedMethods, + firstLabel, + secondLabel, + openAlert, + openModal, + alert, + ]); const actions: Actions = useMemo(() => ({ setExpirationTime, setFirstSelectedService, setSecondSelectedService, setSelectedMethods, onSubmit, onReset, callApi, setOpenAlert, setOpenModal, - }), [setExpirationTime, setFirstSelectedService, setSecondSelectedService, setSelectedMethods, onSubmit, onReset, callApi, setOpenAlert, setOpenModal]); + }), + [setExpirationTime, + setFirstSelectedService, + setSecondSelectedService, + setSelectedMethods, + setOpenAlert, + setOpenModal, + onSubmit, + onReset, + callApi, + ]); return {children}; } - diff --git a/Tokenization/webapp/app/hooks/tokens/token-form.tsx b/Tokenization/webapp/app/hooks/tokens/token-form.tsx index 91928f088..2e91fbb88 100644 --- a/Tokenization/webapp/app/hooks/tokens/token-form.tsx +++ b/Tokenization/webapp/app/hooks/tokens/token-form.tsx @@ -1,8 +1,28 @@ -import { useContext } from "react"; -import { TokenFormContext } from "~/contexts/tokens/token-form"; +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ +import { useContext } from 'react'; +import { TokenFormContext } from '~/contexts/tokens/token-form'; + +/** + * Used to access Token Form context created for Token Form component. + * in webapp/app/contexts/tokens/token-form.tsx + */ export function useTokenForm() { const ctx = useContext(TokenFormContext); - if (!ctx) throw new Error('useTokenForm must be used inside TokenFormProvider'); + if (!ctx) { + throw new Error('useTokenForm must be used inside TokenFormProvider'); + } return ctx; -} \ No newline at end of file +} diff --git a/Tokenization/webapp/tests/public/tokens/token-creation-err.cjs b/Tokenization/webapp/tests/public/tokens/token-creation-err.cjs index 49187f50e..6ae373fd8 100644 --- a/Tokenization/webapp/tests/public/tokens/token-creation-err.cjs +++ b/Tokenization/webapp/tests/public/tokens/token-creation-err.cjs @@ -64,7 +64,7 @@ describe('token creation unsuccessful', function() { await opt2.click(); await this.button.click(); - const alert = await page.waitForSelector('.alert') + const alert = await page.waitForSelector('.alert'); const alertContent = await alert.evaluate(el => el.textContent); assert.ok(alertContent.includes('Expiration time', 'HTTP methods')); }); From a789807a8e1ba2bbaed7a6ab75f1e4257b2d656f Mon Sep 17 00:00:00 2001 From: Igor S Date: Wed, 26 Nov 2025 16:13:23 +0100 Subject: [PATCH 46/47] Delete unnecessary alert-list file --- .../app/components/window/alert-list.tsx | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 Tokenization/webapp/app/components/window/alert-list.tsx diff --git a/Tokenization/webapp/app/components/window/alert-list.tsx b/Tokenization/webapp/app/components/window/alert-list.tsx deleted file mode 100644 index 42a6ff138..000000000 --- a/Tokenization/webapp/app/components/window/alert-list.tsx +++ /dev/null @@ -1,37 +0,0 @@ -// Import type { UUID } from "crypto"; -// Import Alert from "./alert" -// Import type { DialogPropsBase as DPB } from "~/utils/types"; - -// Interface Notification { -// Id: UUID; -// Type: 'success' | 'error' | 'info' | 'warning'; -// Message: string; -// } - -// Interface AlertListProps { -// Notifications: Notification[]; -// SetNotifications: React.Dispatch>; -// } - -// Export default ({notifications, setNotifications}: AlertListProps) => { -// Return ( -// {notifications.map((notification) => ( -// -// ) -// } -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ From 584461c68520d45e91be863b7ee3fc0dafd71e24 Mon Sep 17 00:00:00 2001 From: Igor S Date: Mon, 8 Dec 2025 00:42:40 +0100 Subject: [PATCH 47/47] Selects and inputs are now adjusted to use fetcher.Form component from react-router - which eases using clientAction to perform api calls. --- .../webapp/app/components/form/form-input.tsx | 48 +++++++++++++----- .../webapp/app/components/form/form.d.ts | 8 ++- .../webapp/app/components/form/form.tsx | 21 +++++--- .../app/components/form/select-helper.tsx | 2 + .../webapp/app/components/hooks/useForm.tsx | 35 +++++++++++++ .../app/components/tokens/token-form.tsx | 49 +++++++++++++++++-- .../webapp/app/contexts/tokens/token-form.tsx | 35 ++++++------- .../webapp/app/routes/tokens/create.tsx | 10 ++++ 8 files changed, 159 insertions(+), 49 deletions(-) create mode 100644 Tokenization/webapp/app/components/hooks/useForm.tsx diff --git a/Tokenization/webapp/app/components/form/form-input.tsx b/Tokenization/webapp/app/components/form/form-input.tsx index f6f6368f6..a5b5715cb 100644 --- a/Tokenization/webapp/app/components/form/form-input.tsx +++ b/Tokenization/webapp/app/components/form/form-input.tsx @@ -12,7 +12,7 @@ * or submit itself to any jurisdiction. */ -import React from 'react'; +import React, { type PropsWithChildren } from 'react'; import { type FormInputInterface } from './form.d'; /** @@ -30,16 +30,14 @@ import { type FormInputInterface } from './form.d'; * @param {React.InputHTMLAttributes} [props.inputProps] - Props spread onto the input element. * */ -export function FormInput({ +function FormInput({ value, setValue, labelText, - containerProps, - labelProps, - inputProps, -}: FormInputInterface) { - const inputId = inputProps?.id ?? labelProps?.htmlFor ?? undefined; + name, +}: PropsWithChildren>) { + // If setValue and value are provided than input is controlled const handleChange = (e: React.ChangeEvent) => { const { target } = e; let newVal: T; @@ -49,22 +47,48 @@ export function FormInput({ } else { newVal = target.value as T; } - setValue(newVal); + setValue?.(newVal); }; return ( -
    +
    {labelText && ( -
    ); } + +/** + * FormInputNumber + * + * Number input specialization of FormInput. + * + * @param {object} props - component props + * @param {number} props.value - current numeric value + * @param {(v: number) => void} props.setValue - setter for the numeric value + * @param {string} [props.labelText] - optional label text + * @param {string} props.name - input name attribute + * + * notes: + * - step is set to 1 and min to 0 in the input element + * - if value and setValue are provided than input is controlled + */ +export function FormInputNumber({ value, setValue, labelText, name }: FormInputInterface) { + return + + ; +} diff --git a/Tokenization/webapp/app/components/form/form.d.ts b/Tokenization/webapp/app/components/form/form.d.ts index ed9bb860c..db8a4e99f 100644 --- a/Tokenization/webapp/app/components/form/form.d.ts +++ b/Tokenization/webapp/app/components/form/form.d.ts @@ -17,12 +17,10 @@ import { type SetStateAction } from 'react'; import type { OptionType as Option, DialogPropsBase as DPB } from '../../utils/types'; export interface FormInputInterface { - value: T; - setValue: React.Dispatch>; + value?: T; + setValue?: React.Dispatch>; labelText?: string; - containerProps?: React.HTMLAttributes; - labelProps?: React.LabelHTMLAttributes; - inputProps?: React.InputHTMLAttributes; + name: string; } export interface SelectInterface ? U : T> { diff --git a/Tokenization/webapp/app/components/form/form.tsx b/Tokenization/webapp/app/components/form/form.tsx index 53a0bb4ae..131b327a0 100644 --- a/Tokenization/webapp/app/components/form/form.tsx +++ b/Tokenization/webapp/app/components/form/form.tsx @@ -13,30 +13,39 @@ */ import React from 'react'; +import type { useFetcher } from 'react-router'; -interface FormInterface extends React.HTMLAttributes {} +interface FormInterface extends React.HTMLAttributes { + action: string; + fetcher: ReturnType; + submitRef: React.RefObject; +} /** * Form * - * Lightweight wrapper around a native element. + * Lightweight wrapper around a react-router's element. * * @param {object} props - component props * @param {React.ReactNode} props.children - contents rendered inside the form * @param {string} [props.className] - CSS classes applied to the outer wrapper
    * @param {string} [props.id] - id applied to the outer wrapper
    */ -export const Form = ({ children, className, id }: FormInterface) => { - const _className = className ?? ''; +export const Form = ({ children, className, id, action, fetcher, submitRef }: FormInterface) => { + const _className = className ? ` ${className}` : ''; return (
    - + {children} - + +
    ); }; diff --git a/Tokenization/webapp/app/components/form/select-helper.tsx b/Tokenization/webapp/app/components/form/select-helper.tsx index 145dfc28e..021722434 100644 --- a/Tokenization/webapp/app/components/form/select-helper.tsx +++ b/Tokenization/webapp/app/components/form/select-helper.tsx @@ -176,6 +176,7 @@ export function FormSelectBase) { const [open, setOpen] = useState(false); const rootRef = useRef(null); @@ -210,6 +211,7 @@ export function FormSelectBase +
    ); } diff --git a/Tokenization/webapp/app/components/hooks/useForm.tsx b/Tokenization/webapp/app/components/hooks/useForm.tsx new file mode 100644 index 000000000..a123d3c67 --- /dev/null +++ b/Tokenization/webapp/app/components/hooks/useForm.tsx @@ -0,0 +1,35 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { useCallback, useRef } from 'react'; +import { useFetcher } from 'react-router'; + +/** + * Hook useForm + * + * Provides a fetcher form along with a submit function and a ref to the form element. It cooperates with + *
    component to allow programmatic form submission. + * + * @returns {fetcher: ReturnType, submit: () => void, ref: React.RefObject} + * + */ +export default function useForm() { + const fetcher = useFetcher(); + const ref = useRef(null); + const submit = useCallback(() => { + ref.current?.click(); + }, [ref]); + + return { fetcher, submit, ref }; +} diff --git a/Tokenization/webapp/app/components/tokens/token-form.tsx b/Tokenization/webapp/app/components/tokens/token-form.tsx index 6926d1f5e..b512974aa 100644 --- a/Tokenization/webapp/app/components/tokens/token-form.tsx +++ b/Tokenization/webapp/app/components/tokens/token-form.tsx @@ -12,7 +12,7 @@ * or submit itself to any jurisdiction. */ -import { FormInput } from '~/components/form/form-input'; +import { FormInputNumber } from '~/components/form/form-input'; import { FormSelectMulti, FormSelect } from '~/components/form/form-select'; import { SelectGroup } from '~/components/form/select-group'; import { ResetButton, SubmitButton } from '~/components/form/form-buttons'; @@ -21,6 +21,8 @@ import { Form } from '../form/form'; import Modal from '~/components/window/modal'; import { WindowTitle, WindowContent, WindowButtonAccept, WindowButtonCancel, WindowCloseIcon } from '~/components/window/window-objects'; import Alert from '../window/alert'; +import { useAuth } from '~/utils/session'; +import { useEffect } from 'react'; const httpMethodOptions = [ { value: 'GET', label: 'GET' }, @@ -34,13 +36,15 @@ const httpMethodOptions = [ */ export function TokenForm() { const { state, actions } = useTokenForm(); + const { fetcher, ref } = state; + return ( - - + { + if (auth) { + submit(); + } else { + setAlert({ key: Date.now(), + title: 'Authorization error', + message: 'You cannot perform this action without authorization.', + success: false }); + setOpenAlert(true); + } + setOpenModal(false); + }; + + useEffect(() => { + if (fetcher.state === 'idle' && (fetcher.data as any)?.success === true) { + setAlert({ key: Date.now(), + title: 'Token created', + message: 'Token has been created successfully.', + success: true }); + setOpenAlert(true); + } else if (fetcher.state === 'idle' && (fetcher.data as any)?.success === false) { + setAlert({ key: Date.now(), + title: 'Token creation failed', + message: 'An error occurred while creating the token.', + success: false }); + setOpenAlert(true); + } + }, [fetcher, fetcher.state, setAlert, setOpenAlert]); + return ( <> @@ -98,7 +137,7 @@ export function TokenFormWindows() {
    HTTP methods: {state.selectedMethods.join(', ')}
    - + ; + ref: React.RefObject; }; type Actions = { @@ -37,9 +40,10 @@ type Actions = { setSelectedMethods: React.Dispatch>; onSubmit: () => void; onReset: () => void; - callApi: () => void; setOpenAlert: React.Dispatch>; setOpenModal: React.Dispatch>; + setAlert: React.Dispatch>; + submit: () => void; }; export const TokenFormContext = createContext<{ state: State; actions: Actions } | undefined>(undefined); @@ -64,7 +68,7 @@ export function TokenFormProvider({ loaderData, children }: { loaderData?: Optio const [openAlert, setOpenAlert] = useState(false); const [openModal, setOpenModal] = useState(false); const [alert, setAlert] = useState(null); - const auth = useAuth('admin'); + const { fetcher, ref, submit } = useForm(); useEffect(() => { if (!loaderData) { @@ -106,22 +110,6 @@ export function TokenFormProvider({ loaderData, children }: { loaderData?: Optio setSelectedMethods([]); }, [setExpirationTime, setFirstSelectedService, setSecondSelectedService, setSelectedMethods]); - const callApi = useCallback(() => { - if (auth) { - setAlert({ key: Date.now(), - title: 'Token created', - message: 'Token has been created successfully.', - success: true }); - } else { - setAlert({ key: Date.now(), - title: 'Authorization error', - message: 'You cannot perform this action without authorization.', - success: false }); - } - setOpenAlert(true); - setOpenModal(false); - }, [auth, setOpenAlert, setAlert, setOpenModal]); - const state = useMemo(() => ({ loaderData, expirationTime, @@ -133,6 +121,8 @@ export function TokenFormProvider({ loaderData, children }: { loaderData?: Optio openAlert, openModal, alert, + fetcher, + ref, }), [loaderData, expirationTime, @@ -144,11 +134,13 @@ export function TokenFormProvider({ loaderData, children }: { loaderData?: Optio openAlert, openModal, alert, + fetcher, + ref, ]); const actions: Actions = useMemo(() => ({ setExpirationTime, setFirstSelectedService, setSecondSelectedService, setSelectedMethods, - onSubmit, onReset, callApi, setOpenAlert, setOpenModal, + onSubmit, onReset, setAlert, setOpenAlert, setOpenModal, submit, }), [setExpirationTime, setFirstSelectedService, @@ -158,7 +150,8 @@ export function TokenFormProvider({ loaderData, children }: { loaderData?: Optio setOpenModal, onSubmit, onReset, - callApi, + setAlert, + submit, ]); return {children}; diff --git a/Tokenization/webapp/app/routes/tokens/create.tsx b/Tokenization/webapp/app/routes/tokens/create.tsx index 6c2595b54..7eed5b520 100644 --- a/Tokenization/webapp/app/routes/tokens/create.tsx +++ b/Tokenization/webapp/app/routes/tokens/create.tsx @@ -12,11 +12,21 @@ * or submit itself to any jurisdiction. */ +import type { Route } from './+types/overview'; + import { TokenFormProvider } from '~/contexts/tokens/token-form'; import { TokenForm, TokenFormWindows } from '~/components/tokens/token-form'; import { Box1_1 } from '~/components/box'; import type { OptionType } from '~/utils/types'; +// eslint-disable-next-line jsdoc/require-jsdoc +export async function clientAction({ request }: Route.ClientActionArgs) { + const formData = await request.formData(); + // eslint-disable-next-line no-console + console.log(Object.fromEntries(formData.entries())); + return { success: true }; +} + // eslint-disable-next-line jsdoc/require-jsdoc export function clientLoader(): OptionType[] { return [