From e07cc59e7767794f87e0d5f996057686f864e205 Mon Sep 17 00:00:00 2001 From: Bhakti Narvekar Date: Sat, 14 Jun 2025 20:15:45 -0700 Subject: [PATCH 01/68] Tilt setup for backend Signed-off-by: Bhakti Narvekar --- workspaces/backend/Tiltfile | 23 ++++++++ workspaces/backend/devenv/backend.yaml | 75 ++++++++++++++++++++++++ workspaces/backend/devenv/dev.Dockerfile | 7 +++ 3 files changed, 105 insertions(+) create mode 100644 workspaces/backend/Tiltfile create mode 100644 workspaces/backend/devenv/backend.yaml create mode 100644 workspaces/backend/devenv/dev.Dockerfile diff --git a/workspaces/backend/Tiltfile b/workspaces/backend/Tiltfile new file mode 100644 index 00000000..17870008 --- /dev/null +++ b/workspaces/backend/Tiltfile @@ -0,0 +1,23 @@ +load("ext://restart_process", "docker_build_with_restart") + +local_resource( + "backend-bin", + "CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ../bin/backend cmd/main.go", + deps=["cmd", "internal", "go.mod", "go.sum"], +) + + +docker_build_with_restart( + "backend", + context="..", # this is where dev.Dockerfile lives + dockerfile="devenv/dev.Dockerfile", + entrypoint=["/backend"], + only=["bin/backend"], + live_update=[ + sync("../bin/backend", "/backend"), + ], +) + +k8s_yaml("devenv/backend.yaml") + +k8s_resource("backend", port_forwards=4000) diff --git a/workspaces/backend/devenv/backend.yaml b/workspaces/backend/devenv/backend.yaml new file mode 100644 index 00000000..2a94da4f --- /dev/null +++ b/workspaces/backend/devenv/backend.yaml @@ -0,0 +1,75 @@ +# Deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backend +spec: + replicas: 1 + selector: + matchLabels: + app: backend + template: + metadata: + labels: + app: backend + spec: + serviceAccountName: backend-svc # custom service account + containers: + - name: backend + image: backend + ports: + - containerPort: 4000 + +--- +# Service +apiVersion: v1 +kind: Service +metadata: + name: backend +spec: + selector: + app: backend + ports: + - protocol: TCP + port: 80 + targetPort: 4000 + +--- +# ServiceAccount +apiVersion: v1 +kind: ServiceAccount +metadata: + name: backend-svc + namespace: default + +--- +# ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: backend-role +rules: + - apiGroups: [""] + resources: ["namespaces"] + verbs: ["get", "list", "watch", "create", "update", "delete"] + - apiGroups: ["kubeflow.org"] + resources: ["workspacekinds"] + verbs: ["get", "list", "watch","create","update","delete"] # ["get", "list", "watch", "create", "update", "delete"] + - apiGroups: ["kubeflow.org"] + resources: ["workspaces"] + verbs: ["get", "list", "watch", "create", "update", "delete"] + +--- +# ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: backend-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: backend-role +subjects: + - kind: ServiceAccount + name: backend-svc + namespace: default diff --git a/workspaces/backend/devenv/dev.Dockerfile b/workspaces/backend/devenv/dev.Dockerfile new file mode 100644 index 00000000..fde72648 --- /dev/null +++ b/workspaces/backend/devenv/dev.Dockerfile @@ -0,0 +1,7 @@ +FROM alpine:3.18 + +WORKDIR /app + +COPY bin/backend /backend + +ENTRYPOINT ["/backend"] \ No newline at end of file From 0ac6f9f0671208e651a23cd3e4f16f83ed7552c2 Mon Sep 17 00:00:00 2001 From: Paulo Rego <832830+paulovmr@users.noreply.github.com> Date: Thu, 5 Jun 2025 08:01:15 -0300 Subject: [PATCH 02/68] feat(ws): added namespaces tab to workspace kind details (#406) Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> --- .../src/app/hooks/useWorkspaceCountPerKind.ts | 10 +++--- .../details/WorkspaceKindDetails.tsx | 21 ++++++++++++ .../WorkspaceKindDetailsNamespaces.tsx | 33 +++++++++++++++++++ workspaces/frontend/src/app/types.ts | 4 ++- 4 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsNamespaces.tsx diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts b/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts index 8e3e259e..7ff1aa4f 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts @@ -1,12 +1,9 @@ import * as React from 'react'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { Workspace, WorkspaceKind } from '~/shared/api/backendApiTypes'; -import { WorkspaceCountPerKindImagePodConfig } from '~/app/types'; +import { WorkspaceCountPerOption } from '~/app/types'; -export type WorkspaceCountPerKind = Record< - WorkspaceKind['name'], - WorkspaceCountPerKindImagePodConfig ->; +export type WorkspaceCountPerKind = Record; export const useWorkspaceCountPerKind = (): WorkspaceCountPerKind => { const { api } = useNotebookAPI(); @@ -22,6 +19,7 @@ export const useWorkspaceCountPerKind = (): WorkspaceCountPerKind => { count: 0, countByImage: {}, countByPodConfig: {}, + countByNamespace: {}, }; acc[workspace.workspaceKind.name].count = (acc[workspace.workspaceKind.name].count || 0) + 1; @@ -37,6 +35,8 @@ export const useWorkspaceCountPerKind = (): WorkspaceCountPerKind => { (acc[workspace.workspaceKind.name].countByPodConfig[ workspace.podTemplate.options.podConfig.current.id ] || 0) + 1; + acc[workspace.workspaceKind.name].countByNamespace[workspace.namespace] = + (acc[workspace.workspaceKind.name].countByNamespace[workspace.namespace] || 0) + 1; return acc; }, {}); setWorkspaceCountPerKind(countPerKind); diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx index 13a168eb..ba65dbd2 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx @@ -14,6 +14,7 @@ import { } from '@patternfly/react-core'; import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import { WorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind'; +import { WorkspaceKindDetailsNamespaces } from '~/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsNamespaces'; import { WorkspaceKindDetailsOverview } from './WorkspaceKindDetailsOverview'; import { WorkspaceKindDetailsImages } from './WorkspaceKindDetailsImages'; import { WorkspaceKindDetailsPodConfigs } from './WorkspaceKindDetailsPodConfigs'; @@ -67,6 +68,12 @@ export const WorkspaceKindDetails: React.FunctionComponent + Namespaces} + tabContentId="namespacesTabContent" + aria-label="Namespaces" + /> @@ -110,6 +117,20 @@ export const WorkspaceKindDetails: React.FunctionComponent + ); diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsNamespaces.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsNamespaces.tsx new file mode 100644 index 00000000..fb14174f --- /dev/null +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsNamespaces.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { List, ListItem } from '@patternfly/react-core'; +import { WorkspaceKind } from '~/shared/api/backendApiTypes'; +import { WorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind'; + +type WorkspaceDetailsNamespacesProps = { + workspaceKind: WorkspaceKind; + workspaceCountPerKind: WorkspaceCountPerKind; +}; + +export const WorkspaceKindDetailsNamespaces: React.FunctionComponent< + WorkspaceDetailsNamespacesProps +> = ({ workspaceKind, workspaceCountPerKind }) => ( + + {Object.keys( + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + workspaceCountPerKind[workspaceKind.name] + ? workspaceCountPerKind[workspaceKind.name].countByNamespace + : [], + ).map((namespace, rowIndex) => ( + + {namespace}:{' '} + { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + workspaceCountPerKind[workspaceKind.name] + ? workspaceCountPerKind[workspaceKind.name].countByNamespace[namespace] + : 0 + } + {' Workspaces'} + + ))} + +); diff --git a/workspaces/frontend/src/app/types.ts b/workspaces/frontend/src/app/types.ts index 4b8ce928..c153af0a 100644 --- a/workspaces/frontend/src/app/types.ts +++ b/workspaces/frontend/src/app/types.ts @@ -4,6 +4,7 @@ import { WorkspacePodConfigValue, WorkspacePodVolumeMount, WorkspacePodSecretMount, + Workspace, } from '~/shared/api/backendApiTypes'; export interface WorkspacesColumnNames { @@ -47,8 +48,9 @@ export interface WorkspaceFormData { properties: WorkspaceFormProperties; } -export interface WorkspaceCountPerKindImagePodConfig { +export interface WorkspaceCountPerOption { count: number; countByImage: Record; countByPodConfig: Record; + countByNamespace: Record; } From 92661f09b651ea89e67a84ba051ba6d11683176d Mon Sep 17 00:00:00 2001 From: Paulo Rego <832830+paulovmr@users.noreply.github.com> Date: Thu, 5 Jun 2025 12:14:15 -0300 Subject: [PATCH 03/68] chore(ws): Upgrade vulnerable package webpack-dev-server (#407) Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> --- workspaces/frontend/package-lock.json | 633 ++++++++++++++++++-------- workspaces/frontend/package.json | 2 +- 2 files changed, 450 insertions(+), 185 deletions(-) diff --git a/workspaces/frontend/package-lock.json b/workspaces/frontend/package-lock.json index 68a58be2..c50e6cbd 100644 --- a/workspaces/frontend/package-lock.json +++ b/workspaces/frontend/package-lock.json @@ -82,7 +82,7 @@ "webpack": "^5.91.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.2", + "webpack-dev-server": "^5.2.2", "webpack-merge": "^5.10.0" }, "engines": { @@ -3376,11 +3376,69 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz", + "integrity": "sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.6.0.tgz", + "integrity": "sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz", - "integrity": "sha512-nkalE/f1RvRGChwBnEIoBfSEYOXnCRdleKuv6+lePbMDrMZXeDQnqak5XDOeBgrPPyPfAdcCu/B5z+v3VhplGg==", - "dev": true + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true, + "license": "MIT" }, "node_modules/@monaco-editor/loader": { "version": "1.4.0", @@ -4674,10 +4732,11 @@ } }, "node_modules/@types/bonjour": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", - "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -4712,10 +4771,11 @@ } }, "node_modules/@types/connect-history-api-fallback": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", - "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", "dev": true, + "license": "MIT", "dependencies": { "@types/express-serve-static-core": "*", "@types/node": "*" @@ -4729,26 +4789,29 @@ "license": "MIT" }, "node_modules/@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz", + "integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==", "dev": true, + "license": "MIT", "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", + "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "*" } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.28", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", - "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", "@types/qs": "*", - "@types/range-parser": "*" + "@types/range-parser": "*", + "@types/send": "*" } }, "node_modules/@types/graceful-fs": { @@ -4766,6 +4829,13 @@ "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", "dev": true }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-proxy": { "version": "1.17.8", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz", @@ -4864,10 +4934,11 @@ "optional": true }, "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "18.19.34", @@ -4878,6 +4949,16 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -4951,28 +5032,43 @@ } }, "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "dev": true + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } }, "node_modules/@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*" } }, "node_modules/@types/serve-static": { - "version": "1.13.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", - "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", "dev": true, + "license": "MIT", "dependencies": { - "@types/mime": "^1", - "@types/node": "*" + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" } }, "node_modules/@types/sinonjs__fake-timers": { @@ -4990,10 +5086,11 @@ "license": "MIT" }, "node_modules/@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -5029,10 +5126,11 @@ } }, "node_modules/@types/ws": { - "version": "8.5.5", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", - "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -6375,23 +6473,16 @@ "license": "MIT" }, "node_modules/bonjour-service": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.12.tgz", - "integrity": "sha512-pMmguXYCu63Ug37DluMKEHdxc+aaIf/ay4YbF8Gxtba+9d3u+rmEWy61VK3Z3hp8Rskok3BunHYnG0dUHAsblw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", "dev": true, + "license": "MIT", "dependencies": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.4" + "multicast-dns": "^7.2.5" } }, - "node_modules/bonjour-service/node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", - "dev": true - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -6657,6 +6748,22 @@ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -6956,16 +7063,11 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -6978,6 +7080,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -8736,16 +8841,34 @@ "node": ">=0.10.0" } }, - "node_modules/default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", "dev": true, + "license": "MIT", "dependencies": { - "execa": "^5.0.0" + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" }, "engines": { - "node": ">= 10" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/default-require-extensions": { @@ -8781,12 +8904,16 @@ } }, "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/define-properties": { @@ -8902,17 +9029,12 @@ "node": ">=8" } }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", - "dev": true - }, "node_modules/dns-packet": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.3.1.tgz", - "integrity": "sha512-spBwIj0TK0Ey3666GwIdWVfUpLyubpU53BTCu8iPn4r4oXd9O14Hjg3EHw3ts2oed77/SeckunUYCyRlSngqHw==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", "dev": true, + "license": "MIT", "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" }, @@ -11327,6 +11449,21 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "devOptional": true }, + "node_modules/fsevents": { + "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, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/fsu": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/fsu/-/fsu-1.1.1.tgz", @@ -11748,22 +11885,6 @@ "node": ">=12" } }, - "node_modules/html-entities": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", - "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ] - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -12071,6 +12192,16 @@ "node": ">=10.17.0" } }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -12304,10 +12435,11 @@ } }, "node_modules/ipaddr.js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", - "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10" } @@ -12566,6 +12698,41 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-installed-globally": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", @@ -12607,6 +12774,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number-object": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", @@ -15073,13 +15253,14 @@ } }, "node_modules/launch-editor": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.0.tgz", - "integrity": "sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz", + "integrity": "sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==", "dev": true, + "license": "MIT", "dependencies": { "picocolors": "^1.0.0", - "shell-quote": "^1.7.3" + "shell-quote": "^1.8.1" } }, "node_modules/lazy-ass": { @@ -16490,10 +16671,11 @@ "license": "MIT" }, "node_modules/multicast-dns": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.4.tgz", - "integrity": "sha512-XkCYOU+rr2Ft3LI6w4ye51M3VK31qJXFIxu0XLw169PtKG0Zx47OrXeVW/GCYOfpC9s1yyyf1S+L8/4LY0J9Zw==", + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", "dev": true, + "license": "MIT", "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" @@ -16577,6 +16759,7 @@ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" } @@ -17181,6 +17364,41 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz", + "integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open/node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/opener": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", @@ -17270,16 +17488,21 @@ } }, "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", "dev": true, + "license": "MIT", "dependencies": { - "@types/retry": "0.12.0", + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", "retry": "^0.13.1" }, "engines": { - "node": ">=8" + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-try": { @@ -18951,6 +19174,7 @@ "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -19042,6 +19266,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -19301,11 +19538,13 @@ "dev": true }, "node_modules/selfsigned": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", - "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", "dev": true, + "license": "MIT", "dependencies": { + "@types/node-forge": "^1.3.0", "node-forge": "^1" }, "engines": { @@ -20693,6 +20932,19 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "optional": true }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "license": "Unlicense", + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, "node_modules/throttleit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", @@ -20714,7 +20966,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/tinydate": { "version": "1.3.0", @@ -20819,6 +21072,23 @@ "node": ">= 4.0.0" } }, + "node_modules/tree-dump": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.3.tgz", + "integrity": "sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -21804,77 +22074,103 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", - "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", + "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", "dev": true, + "license": "MIT", "dependencies": { "colorette": "^2.0.10", - "memfs": "^3.4.3", + "memfs": "^4.6.0", "mime-types": "^2.1.31", + "on-finished": "^2.4.1", "range-parser": "^1.2.1", "schema-utils": "^4.0.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware/node_modules/memfs": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz", + "integrity": "sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" } }, "node_modules/webpack-dev-server": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", - "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz", + "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", "dev": true, + "license": "MIT", "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.5", + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", "colorette": "^2.0.10", "compression": "^1.7.4", "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", + "express": "^4.21.2", "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", + "http-proxy-middleware": "^2.0.9", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.4", - "ws": "^8.13.0" + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" }, "bin": { "webpack-dev-server": "bin/webpack-dev-server.js" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" + "webpack": "^5.0.0" }, "peerDependenciesMeta": { "webpack": { @@ -21885,43 +22181,12 @@ } } }, - "node_modules/webpack-dev-server/node_modules/open": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", - "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", - "dev": true, - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-dev-server/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "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" }, diff --git a/workspaces/frontend/package.json b/workspaces/frontend/package.json index c0dbdd1e..c02da03f 100644 --- a/workspaces/frontend/package.json +++ b/workspaces/frontend/package.json @@ -93,7 +93,7 @@ "webpack": "^5.91.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.2", + "webpack-dev-server": "^5.2.2", "webpack-merge": "^5.10.0" }, "dependencies": { From 4ce7875e193ec601063efc31da7ea446439afe19 Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:25:08 -0400 Subject: [PATCH 04/68] fix(ws): Action Button Alignment and Jupyter Image Display (#408) Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> add icon to workspaceKindsColumns interface fix actions cell alignment move card title to fix spacing --- workspaces/frontend/src/app/actions/WorkspaceKindsActions.tsx | 2 +- .../app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx | 4 ++-- workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/workspaces/frontend/src/app/actions/WorkspaceKindsActions.tsx b/workspaces/frontend/src/app/actions/WorkspaceKindsActions.tsx index b9db0aae..40608d81 100644 --- a/workspaces/frontend/src/app/actions/WorkspaceKindsActions.tsx +++ b/workspaces/frontend/src/app/actions/WorkspaceKindsActions.tsx @@ -12,7 +12,7 @@ export function buildKindLogoDictionary(workspaceKinds: WorkspaceKind[] | []): K for (const workspaceKind of workspaceKinds) { try { - kindLogoDict[workspaceKind.name] = workspaceKind.logo.url; + kindLogoDict[workspaceKind.name] = workspaceKind.icon.url; } catch { kindLogoDict[workspaceKind.name] = ''; } diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx index ad677777..e7a07f61 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx @@ -115,9 +115,9 @@ export const WorkspaceFormKindList: React.FunctionComponent - {`${kind.name} - {kind.displayName} + {`${kind.name} + {kind.displayName} {kind.description} ))} diff --git a/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx b/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx index 7036486b..afbaba37 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx @@ -627,7 +627,7 @@ export const Workspaces: React.FunctionComponent = () => { })} - + From cd02eb46c697bbe0393304ad7890d293790a4b8c Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:28:08 -0400 Subject: [PATCH 05/68] fix(ws): Expose active nav item on initial Workspaces page load (#419) Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> --- workspaces/frontend/src/app/AppRoutes.tsx | 4 ++-- workspaces/frontend/src/app/NavSidebar.tsx | 23 ++++++++++++------- .../frontend/src/shared/style/MUI-theme.scss | 6 ++--- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/workspaces/frontend/src/app/AppRoutes.tsx b/workspaces/frontend/src/app/AppRoutes.tsx index 757ef109..ec6d60e4 100644 --- a/workspaces/frontend/src/app/AppRoutes.tsx +++ b/workspaces/frontend/src/app/AppRoutes.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Route, Routes } from 'react-router-dom'; +import { Route, Routes, Navigate } from 'react-router-dom'; import { AppRoutePaths } from '~/app/routes'; import { WorkspaceForm } from '~/app/pages/Workspaces/Form/WorkspaceForm'; import { NotFound } from './pages/notFound/NotFound'; @@ -64,7 +64,7 @@ const AppRoutes: React.FC = () => { } /> } /> } /> - } /> + } /> } /> { // TODO: Remove the linter skip when we implement authentication diff --git a/workspaces/frontend/src/app/NavSidebar.tsx b/workspaces/frontend/src/app/NavSidebar.tsx index 49a169ad..50ed31d9 100644 --- a/workspaces/frontend/src/app/NavSidebar.tsx +++ b/workspaces/frontend/src/app/NavSidebar.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { NavLink } from 'react-router-dom'; +import { NavLink, useLocation } from 'react-router-dom'; import { Brand, Nav, @@ -12,13 +12,20 @@ import { import { useNavData, isNavDataGroup, NavDataHref, NavDataGroup } from './AppRoutes'; import { isMUITheme, LOGO_LIGHT } from './const'; -const NavHref: React.FC<{ item: NavDataHref }> = ({ item }) => ( - - - {item.label} - - -); +const NavHref: React.FC<{ item: NavDataHref }> = ({ item }) => { + const location = useLocation(); + + // With the redirect in place, we can now use a simple path comparison. + const isActive = location.pathname === item.path; + + return ( + + + {item.label} + + + ); +}; const NavGroup: React.FC<{ item: NavDataGroup }> = ({ item }) => { const { children } = item; diff --git a/workspaces/frontend/src/shared/style/MUI-theme.scss b/workspaces/frontend/src/shared/style/MUI-theme.scss index de78bc9b..6184369c 100644 --- a/workspaces/frontend/src/shared/style/MUI-theme.scss +++ b/workspaces/frontend/src/shared/style/MUI-theme.scss @@ -543,10 +543,10 @@ --pf-v6-c-nav__link--hover--Color: var(--kf-central-sidebar-default-color); --pf-v6-c-nav__link--FontSize: var(--pf-t--global--font--size--md); - &.active { + &.pf-m-current { border-left: 3px solid var(--mui-palette-common-white); - --pf-v6-c-nav__link--Color: var(--mui-palette-common-white); - --pf-v6-c-nav__link--hover--Color: var(--mui-palette-common-white); + --pf-v6-c-nav__link--m-current--BackgroundColor: #ffffff1b; + --pf-v6-c-nav__link--m-current--Color: var(--mui-palette-common-white); } &.pf-v6-c-brand { From 99538a7f8147a75e3d8f9bd404f1e0512f0b73c9 Mon Sep 17 00:00:00 2001 From: Paulo Rego <832830+paulovmr@users.noreply.github.com> Date: Mon, 16 Jun 2025 17:51:08 -0300 Subject: [PATCH 06/68] chore(ws): enforce named imports for react hooks (#414) Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> --- .../frontend/{.eslintrc => .eslintrc.js} | 15 ++- .../no-react-hook-namespace.js | 34 +++++++ workspaces/frontend/package.json | 4 +- .../__tests__/unit/testUtils/hooks.spec.ts | 8 +- workspaces/frontend/src/app/App.tsx | 4 +- workspaces/frontend/src/app/AppRoutes.tsx | 2 +- .../src/app/EnsureAPIAvailability.tsx | 2 +- workspaces/frontend/src/app/NavSidebar.tsx | 4 +- .../app/components/ThemeAwareSearchInput.tsx | 2 +- .../src/app/context/BrowserStorageContext.tsx | 16 +++- .../app/context/NamespaceContextProvider.tsx | 2 +- .../src/app/context/NotebookContext.tsx | 5 +- .../src/app/context/useNotebookAPIState.tsx | 6 +- .../frontend/src/app/error/ErrorBoundary.tsx | 2 +- .../frontend/src/app/error/ErrorDetails.tsx | 2 +- .../frontend/src/app/error/UpdateState.tsx | 2 +- .../src/app/hooks/useGenericObjectState.ts | 12 +-- .../frontend/src/app/hooks/useNamespaces.ts | 4 +- .../frontend/src/app/hooks/useNotebookAPI.ts | 4 +- .../src/app/hooks/useWorkspaceCountPerKind.ts | 8 +- .../src/app/hooks/useWorkspaceFormData.ts | 4 +- .../src/app/hooks/useWorkspaceKindByName.ts | 4 +- .../src/app/hooks/useWorkspaceKinds.ts | 4 +- .../frontend/src/app/hooks/useWorkspaces.ts | 4 +- .../frontend/src/app/pages/Debug/Debug.tsx | 2 +- .../pages/WorkspaceKinds/WorkspaceKinds.tsx | 94 +++++++++---------- .../details/WorkspaceKindDetails.tsx | 4 +- .../app/pages/Workspaces/DataVolumesList.tsx | 2 +- .../Workspaces/Details/WorkspaceDetails.tsx | 4 +- .../Details/WorkspaceDetailsActions.tsx | 4 +- .../pages/Workspaces/ExpandedWorkspaceRow.tsx | 2 +- .../pages/Workspaces/Form/WorkspaceForm.tsx | 7 +- .../Form/image/WorkspaceFormImageList.tsx | 4 +- .../image/WorkspaceFormImageSelection.tsx | 4 +- .../Form/kind/WorkspaceFormKindList.tsx | 4 +- .../Form/kind/WorkspaceFormKindSelection.tsx | 2 +- .../podConfig/WorkspaceFormPodConfigList.tsx | 4 +- .../WorkspaceFormPodConfigSelection.tsx | 4 +- .../WorkspaceFormPropertiesSelection.tsx | 3 +- .../WorkspaceFormPropertiesVolumes.tsx | 2 +- .../Workspaces/WorkspaceConnectAction.tsx | 4 +- .../src/app/pages/Workspaces/Workspaces.tsx | 45 +++++---- .../WorkspaceRedirectInformationView.tsx | 10 +- .../WorkspaceRestartActionModal.tsx | 2 +- .../WorkspaceStartActionModal.tsx | 12 +-- .../WorkspaceStopActionModal.tsx | 12 +-- .../src/app/pages/notFound/NotFound.tsx | 2 +- .../frontend/src/shared/api/useAPIState.ts | 8 +- .../src/shared/components/ActionButton.tsx | 6 +- .../shared/components/CustomEmptyState.tsx | 2 +- .../src/shared/components/DeleteModal.tsx | 4 +- .../frontend/src/shared/components/Filter.tsx | 49 +++++----- .../shared/components/NamespaceSelector.tsx | 2 +- .../src/shared/utilities/useFetchState.ts | 28 +++--- 54 files changed, 268 insertions(+), 218 deletions(-) rename workspaces/frontend/{.eslintrc => .eslintrc.js} (96%) create mode 100644 workspaces/frontend/eslint-local-rules/no-react-hook-namespace.js diff --git a/workspaces/frontend/.eslintrc b/workspaces/frontend/.eslintrc.js similarity index 96% rename from workspaces/frontend/.eslintrc rename to workspaces/frontend/.eslintrc.js index bb2e6537..fc6719dd 100644 --- a/workspaces/frontend/.eslintrc +++ b/workspaces/frontend/.eslintrc.js @@ -1,4 +1,6 @@ -{ +const noReactHookNamespace = require('./eslint-local-rules/no-react-hook-namespace'); + +module.exports = { "parser": "@typescript-eslint/parser", "env": { "browser": true, @@ -216,7 +218,8 @@ "no-useless-return": "error", "symbol-description": "error", "yoda": "error", - "func-names": "warn" + "func-names": "warn", + "no-react-hook-namespace": "error" }, "overrides": [ { @@ -262,6 +265,12 @@ } ] } + }, + { + files: ['**/*.{js,jsx,ts,tsx}'], + rules: { + 'no-react-hook-namespace': 'error', + }, } ] -} +}; diff --git a/workspaces/frontend/eslint-local-rules/no-react-hook-namespace.js b/workspaces/frontend/eslint-local-rules/no-react-hook-namespace.js new file mode 100644 index 00000000..031e312f --- /dev/null +++ b/workspaces/frontend/eslint-local-rules/no-react-hook-namespace.js @@ -0,0 +1,34 @@ +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'Disallow using React hooks through React namespace', + }, + messages: { + avoidNamespaceHook: 'Import React hook "{{hook}}" directly instead of using React.{{hook}}.', + }, + schema: [], + }, + create(context) { + const hooks = new Set([ + 'useState', 'useEffect', 'useContext', 'useReducer', + 'useCallback', 'useMemo', 'useRef', 'useLayoutEffect', + 'useImperativeHandle', 'useDebugValue', 'useDeferredValue', + 'useTransition', 'useId', 'useSyncExternalStore', + ]); + return { + MemberExpression(node) { + if ( + node.object?.name === 'React' && + hooks.has(node.property?.name) + ) { + context.report({ + node, + messageId: 'avoidNamespaceHook', + data: { hook: node.property.name }, + }); + } + }, + }; + }, +}; diff --git a/workspaces/frontend/package.json b/workspaces/frontend/package.json index c02da03f..e699465a 100644 --- a/workspaces/frontend/package.json +++ b/workspaces/frontend/package.json @@ -24,8 +24,8 @@ "test:unit": "npm run test:jest -- --silent", "test:watch": "jest --watch", "test:coverage": "jest --coverage", - "test:fix": "eslint --ext .js,.ts,.jsx,.tsx ./src --fix", - "test:lint": "eslint --max-warnings 0 --ext .js,.ts,.jsx,.tsx ./src", + "test:fix": "eslint --rulesdir eslint-local-rules --ext .js,.ts,.jsx,.tsx ./src --fix", + "test:lint": "eslint --rulesdir eslint-local-rules --max-warnings 0 --ext .js,.ts,.jsx,.tsx ./src", "cypress:open": "cypress open --project src/__tests__/cypress", "cypress:open:mock": "CY_MOCK=1 CY_WS_PORT=9002 npm run cypress:open -- ", "cypress:run": "cypress run -b chrome --project src/__tests__/cypress", diff --git a/workspaces/frontend/src/__tests__/unit/testUtils/hooks.spec.ts b/workspaces/frontend/src/__tests__/unit/testUtils/hooks.spec.ts index d2509314..318d7de3 100644 --- a/workspaces/frontend/src/__tests__/unit/testUtils/hooks.spec.ts +++ b/workspaces/frontend/src/__tests__/unit/testUtils/hooks.spec.ts @@ -1,15 +1,15 @@ -import * as React from 'react'; +import { useEffect, useRef, useState } from 'react'; import { createComparativeValue, renderHook, standardUseFetchState, testHook } from './hooks'; const useSayHello = (who: string, showCount = false) => { - const countRef = React.useRef(0); + const countRef = useRef(0); countRef.current++; return `Hello ${who}!${showCount && countRef.current > 1 ? ` x${countRef.current}` : ''}`; }; const useSayHelloDelayed = (who: string, delay = 0) => { - const [speech, setSpeech] = React.useState(''); - React.useEffect(() => { + const [speech, setSpeech] = useState(''); + useEffect(() => { const handle = setTimeout(() => setSpeech(`Hello ${who}!`), delay); return () => clearTimeout(handle); }, [who, delay]); diff --git a/workspaces/frontend/src/app/App.tsx b/workspaces/frontend/src/app/App.tsx index 04d42de0..4f1718c4 100644 --- a/workspaces/frontend/src/app/App.tsx +++ b/workspaces/frontend/src/app/App.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React, { useEffect } from 'react'; import '@patternfly/react-core/dist/styles/base.css'; import './app.css'; import { @@ -26,7 +26,7 @@ import { isMUITheme, Theme } from './const'; import { BrowserStorageContextProvider } from './context/BrowserStorageContext'; const App: React.FC = () => { - React.useEffect(() => { + useEffect(() => { // Apply the theme based on the value of STYLE_THEME if (isMUITheme()) { document.documentElement.classList.add(Theme.MUI); diff --git a/workspaces/frontend/src/app/AppRoutes.tsx b/workspaces/frontend/src/app/AppRoutes.tsx index ec6d60e4..e64f1878 100644 --- a/workspaces/frontend/src/app/AppRoutes.tsx +++ b/workspaces/frontend/src/app/AppRoutes.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { Route, Routes, Navigate } from 'react-router-dom'; import { AppRoutePaths } from '~/app/routes'; import { WorkspaceForm } from '~/app/pages/Workspaces/Form/WorkspaceForm'; diff --git a/workspaces/frontend/src/app/EnsureAPIAvailability.tsx b/workspaces/frontend/src/app/EnsureAPIAvailability.tsx index 53dbeeaf..9fc3c24a 100644 --- a/workspaces/frontend/src/app/EnsureAPIAvailability.tsx +++ b/workspaces/frontend/src/app/EnsureAPIAvailability.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { Bullseye, Spinner } from '@patternfly/react-core'; import { useNotebookAPI } from './hooks/useNotebookAPI'; diff --git a/workspaces/frontend/src/app/NavSidebar.tsx b/workspaces/frontend/src/app/NavSidebar.tsx index 50ed31d9..e9b7195b 100644 --- a/workspaces/frontend/src/app/NavSidebar.tsx +++ b/workspaces/frontend/src/app/NavSidebar.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React, { useState } from 'react'; import { NavLink, useLocation } from 'react-router-dom'; import { Brand, @@ -29,7 +29,7 @@ const NavHref: React.FC<{ item: NavDataHref }> = ({ item }) => { const NavGroup: React.FC<{ item: NavDataGroup }> = ({ item }) => { const { children } = item; - const [expanded, setExpanded] = React.useState(false); + const [expanded, setExpanded] = useState(false); return ( unknown; @@ -17,8 +25,8 @@ const BrowserStorageContext = createContext({ export const BrowserStorageContextProvider: React.FC = ({ children, }) => { - const [values, setValues] = React.useState<{ [key: string]: unknown }>({}); - const valuesRef = React.useRef(values); + const [values, setValues] = useState<{ [key: string]: unknown }>({}); + const valuesRef = useRef(values); useEffect(() => { valuesRef.current = values; }, [values]); @@ -49,7 +57,7 @@ export const BrowserStorageContextProvider: React.FC ({ getValue, setValue }), [getValue, setValue, values]); + const contextValue = useMemo(() => ({ getValue, setValue }), [getValue, setValue, values]); return ( {children} diff --git a/workspaces/frontend/src/app/context/NamespaceContextProvider.tsx b/workspaces/frontend/src/app/context/NamespaceContextProvider.tsx index 40634a64..09781c4d 100644 --- a/workspaces/frontend/src/app/context/NamespaceContextProvider.tsx +++ b/workspaces/frontend/src/app/context/NamespaceContextProvider.tsx @@ -1,4 +1,4 @@ -import React, { useState, useContext, ReactNode, useMemo, useCallback } from 'react'; +import React, { ReactNode, useCallback, useContext, useMemo, useState } from 'react'; import useMount from '~/app/hooks/useMount'; import useNamespaces from '~/app/hooks/useNamespaces'; import { useStorage } from './BrowserStorageContext'; diff --git a/workspaces/frontend/src/app/context/NotebookContext.tsx b/workspaces/frontend/src/app/context/NotebookContext.tsx index df17db28..2f660c72 100644 --- a/workspaces/frontend/src/app/context/NotebookContext.tsx +++ b/workspaces/frontend/src/app/context/NotebookContext.tsx @@ -1,5 +1,4 @@ -import * as React from 'react'; -import { ReactNode } from 'react'; +import React, { ReactNode, useMemo } from 'react'; import { BFF_API_VERSION } from '~/app/const'; import EnsureAPIAvailability from '~/app/EnsureAPIAvailability'; import useNotebookAPIState, { NotebookAPIState } from './useNotebookAPIState'; @@ -26,7 +25,7 @@ export const NotebookContextProvider: React.FC = ( return ( ({ apiState, refreshAPIState, diff --git a/workspaces/frontend/src/app/context/useNotebookAPIState.tsx b/workspaces/frontend/src/app/context/useNotebookAPIState.tsx index 50bf64a7..ac0e9136 100644 --- a/workspaces/frontend/src/app/context/useNotebookAPIState.tsx +++ b/workspaces/frontend/src/app/context/useNotebookAPIState.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { useCallback } from 'react'; import { NotebookAPIs } from '~/shared/api/notebookApi'; import { createWorkspace, @@ -48,7 +48,7 @@ const MOCK_API_ENABLED = process.env.WEBPACK_REPLACE__mockApiEnabled === 'true'; const useNotebookAPIState = ( hostPath: string | null, ): [apiState: NotebookAPIState, refreshAPIState: () => void] => { - const createApi = React.useCallback( + const createApi = useCallback( (path: string): NotebookAPIs => ({ // Health getHealthCheck: getHealthCheck(path), @@ -75,7 +75,7 @@ const useNotebookAPIState = ( [], ); - const createMockApi = React.useCallback( + const createMockApi = useCallback( (path: string): NotebookAPIs => ({ // Health getHealthCheck: mockGetHealthCheck(path), diff --git a/workspaces/frontend/src/app/error/ErrorBoundary.tsx b/workspaces/frontend/src/app/error/ErrorBoundary.tsx index 3a5b20d1..29d8830c 100644 --- a/workspaces/frontend/src/app/error/ErrorBoundary.tsx +++ b/workspaces/frontend/src/app/error/ErrorBoundary.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { Link } from 'react-router-dom'; import { Button, Split, SplitItem, Title } from '@patternfly/react-core'; import { TimesIcon } from '@patternfly/react-icons'; diff --git a/workspaces/frontend/src/app/error/ErrorDetails.tsx b/workspaces/frontend/src/app/error/ErrorDetails.tsx index 66d84a20..61ac57a5 100644 --- a/workspaces/frontend/src/app/error/ErrorDetails.tsx +++ b/workspaces/frontend/src/app/error/ErrorDetails.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { ClipboardCopy, ClipboardCopyVariant, diff --git a/workspaces/frontend/src/app/error/UpdateState.tsx b/workspaces/frontend/src/app/error/UpdateState.tsx index dce1252b..ff19100b 100644 --- a/workspaces/frontend/src/app/error/UpdateState.tsx +++ b/workspaces/frontend/src/app/error/UpdateState.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { Button, EmptyState, diff --git a/workspaces/frontend/src/app/hooks/useGenericObjectState.ts b/workspaces/frontend/src/app/hooks/useGenericObjectState.ts index 852aedb1..a830cb7c 100644 --- a/workspaces/frontend/src/app/hooks/useGenericObjectState.ts +++ b/workspaces/frontend/src/app/hooks/useGenericObjectState.ts @@ -1,4 +1,4 @@ -import * as React from 'react'; +import { useCallback, useRef, useState } from 'react'; export type UpdateObjectAtPropAndValue = ( propKey: K, @@ -13,9 +13,9 @@ export type GenericObjectState = [ ]; const useGenericObjectState = (defaultData: T | (() => T)): GenericObjectState => { - const [value, setValue] = React.useState(defaultData); + const [value, setValue] = useState(defaultData); - const setPropValue = React.useCallback>((propKey, propValue) => { + const setPropValue = useCallback>((propKey, propValue) => { setValue((oldValue) => { if (oldValue[propKey] !== propValue) { return { ...oldValue, [propKey]: propValue }; @@ -24,12 +24,12 @@ const useGenericObjectState = (defaultData: T | (() => T)): GenericObjectStat }); }, []); - const defaultDataRef = React.useRef(value); - const resetToDefault = React.useCallback(() => { + const defaultDataRef = useRef(value); + const resetToDefault = useCallback(() => { setValue(defaultDataRef.current); }, []); - const replace = React.useCallback((newValue: T) => { + const replace = useCallback((newValue: T) => { setValue(newValue); }, []); diff --git a/workspaces/frontend/src/app/hooks/useNamespaces.ts b/workspaces/frontend/src/app/hooks/useNamespaces.ts index d07c0ba1..1f62afeb 100644 --- a/workspaces/frontend/src/app/hooks/useNamespaces.ts +++ b/workspaces/frontend/src/app/hooks/useNamespaces.ts @@ -1,4 +1,4 @@ -import * as React from 'react'; +import { useCallback } from 'react'; import useFetchState, { FetchState, FetchStateCallbackPromise, @@ -9,7 +9,7 @@ import { Namespace } from '~/shared/api/backendApiTypes'; const useNamespaces = (): FetchState => { const { api, apiAvailable } = useNotebookAPI(); - const call = React.useCallback>( + const call = useCallback>( (opts) => { if (!apiAvailable) { return Promise.reject(new Error('API not yet available')); diff --git a/workspaces/frontend/src/app/hooks/useNotebookAPI.ts b/workspaces/frontend/src/app/hooks/useNotebookAPI.ts index 468ed669..4cf620a7 100644 --- a/workspaces/frontend/src/app/hooks/useNotebookAPI.ts +++ b/workspaces/frontend/src/app/hooks/useNotebookAPI.ts @@ -1,4 +1,4 @@ -import * as React from 'react'; +import { useContext } from 'react'; import { NotebookAPIState } from '~/app/context/useNotebookAPIState'; import { NotebookContext } from '~/app/context/NotebookContext'; @@ -7,7 +7,7 @@ type UseNotebookAPI = NotebookAPIState & { }; export const useNotebookAPI = (): UseNotebookAPI => { - const { apiState, refreshAPIState: refreshAllAPI } = React.useContext(NotebookContext); + const { apiState, refreshAPIState: refreshAllAPI } = useContext(NotebookContext); return { refreshAllAPI, diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts b/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts index 7ff1aa4f..be25d8e4 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts @@ -1,4 +1,4 @@ -import * as React from 'react'; +import { useEffect, useState } from 'react'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { Workspace, WorkspaceKind } from '~/shared/api/backendApiTypes'; import { WorkspaceCountPerOption } from '~/app/types'; @@ -8,11 +8,9 @@ export type WorkspaceCountPerKind = Record { const { api } = useNotebookAPI(); - const [workspaceCountPerKind, setWorkspaceCountPerKind] = React.useState( - {}, - ); + const [workspaceCountPerKind, setWorkspaceCountPerKind] = useState({}); - React.useEffect(() => { + useEffect(() => { api.listAllWorkspaces({}).then((workspaces) => { const countPerKind = workspaces.reduce((acc: WorkspaceCountPerKind, workspace: Workspace) => { acc[workspace.workspaceKind.name] = acc[workspace.workspaceKind.name] ?? { diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts b/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts index 4dd755cf..85b00a8b 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts @@ -1,4 +1,4 @@ -import * as React from 'react'; +import { useCallback } from 'react'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { WorkspaceFormData } from '~/app/types'; import useFetchState, { @@ -25,7 +25,7 @@ const useWorkspaceFormData = (args: { }): FetchState => { const { api, apiAvailable } = useNotebookAPI(); - const call = React.useCallback>( + const call = useCallback>( async (opts) => { if (!apiAvailable) { throw new Error('API not yet available'); diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceKindByName.ts b/workspaces/frontend/src/app/hooks/useWorkspaceKindByName.ts index 942f24b6..1c575b24 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaceKindByName.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaceKindByName.ts @@ -1,4 +1,4 @@ -import * as React from 'react'; +import { useCallback } from 'react'; import useFetchState, { FetchState, FetchStateCallbackPromise, @@ -9,7 +9,7 @@ import { WorkspaceKind } from '~/shared/api/backendApiTypes'; const useWorkspaceKindByName = (kind: string): FetchState => { const { api, apiAvailable } = useNotebookAPI(); - const call = React.useCallback>( + const call = useCallback>( (opts) => { if (!apiAvailable) { return Promise.reject(new Error('API not yet available')); diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceKinds.ts b/workspaces/frontend/src/app/hooks/useWorkspaceKinds.ts index 4db6cb2a..d654bd92 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaceKinds.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaceKinds.ts @@ -1,4 +1,4 @@ -import * as React from 'react'; +import { useCallback } from 'react'; import useFetchState, { FetchState, FetchStateCallbackPromise, @@ -8,7 +8,7 @@ import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; const useWorkspaceKinds = (): FetchState => { const { api, apiAvailable } = useNotebookAPI(); - const call = React.useCallback>( + const call = useCallback>( (opts) => { if (!apiAvailable) { return Promise.reject(new Error('API not yet available')); diff --git a/workspaces/frontend/src/app/hooks/useWorkspaces.ts b/workspaces/frontend/src/app/hooks/useWorkspaces.ts index b6347f9d..00df848d 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaces.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaces.ts @@ -1,4 +1,4 @@ -import * as React from 'react'; +import { useCallback } from 'react'; import useFetchState, { FetchState, FetchStateCallbackPromise, @@ -9,7 +9,7 @@ import { Workspace } from '~/shared/api/backendApiTypes'; const useWorkspaces = (namespace: string): FetchState => { const { api, apiAvailable } = useNotebookAPI(); - const call = React.useCallback>( + const call = useCallback>( (opts) => { if (!apiAvailable) { return Promise.reject(new Error('API not yet available')); diff --git a/workspaces/frontend/src/app/pages/Debug/Debug.tsx b/workspaces/frontend/src/app/pages/Debug/Debug.tsx index 75894c7f..5c648316 100644 --- a/workspaces/frontend/src/app/pages/Debug/Debug.tsx +++ b/workspaces/frontend/src/app/pages/Debug/Debug.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { CubesIcon } from '@patternfly/react-icons'; import { Button, diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx index d2e2c04e..8358899c 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Drawer, DrawerContent, @@ -48,7 +48,7 @@ export enum ActionType { export const WorkspaceKinds: React.FunctionComponent = () => { // Table columns - const columns: WorkspaceKindsColumns = React.useMemo( + const columns: WorkspaceKindsColumns = useMemo( () => ({ icon: { name: '', label: 'Icon', id: 'icon' }, name: { name: 'Name', label: 'Name', id: 'name' }, @@ -65,16 +65,14 @@ export const WorkspaceKinds: React.FunctionComponent = () => { const [workspaceKinds, workspaceKindsLoaded, workspaceKindsError] = useWorkspaceKinds(); const workspaceCountPerKind = useWorkspaceCountPerKind(); - const [selectedWorkspaceKind, setSelectedWorkspaceKind] = React.useState( - null, - ); - const [activeActionType, setActiveActionType] = React.useState(null); + const [selectedWorkspaceKind, setSelectedWorkspaceKind] = useState(null); + const [activeActionType, setActiveActionType] = useState(null); // Column sorting - const [activeSortIndex, setActiveSortIndex] = React.useState(null); - const [activeSortDirection, setActiveSortDirection] = React.useState<'asc' | 'desc' | null>(null); + const [activeSortIndex, setActiveSortIndex] = useState(null); + const [activeSortDirection, setActiveSortDirection] = useState<'asc' | 'desc' | null>(null); - const getSortableRowValues = React.useCallback( + const getSortableRowValues = useCallback( (workspaceKind: WorkspaceKind): (string | boolean | number)[] => { const { icon, @@ -95,7 +93,7 @@ export const WorkspaceKinds: React.FunctionComponent = () => { [workspaceCountPerKind], ); - const sortedWorkspaceKinds = React.useMemo(() => { + const sortedWorkspaceKinds = useMemo(() => { if (activeSortIndex === null) { return workspaceKinds; } @@ -114,7 +112,7 @@ export const WorkspaceKinds: React.FunctionComponent = () => { }); }, [workspaceKinds, activeSortIndex, activeSortDirection, getSortableRowValues]); - const getSortParams = React.useCallback( + const getSortParams = useCallback( (columnIndex: number): ThProps['sort'] => ({ sortBy: { index: activeSortIndex || 0, @@ -131,19 +129,19 @@ export const WorkspaceKinds: React.FunctionComponent = () => { ); // Set up filter - Attribute search. - const [searchNameValue, setSearchNameValue] = React.useState(''); - const [searchDescriptionValue, setSearchDescriptionValue] = React.useState(''); - const [statusSelection, setStatusSelection] = React.useState(''); + const [searchNameValue, setSearchNameValue] = useState(''); + const [searchDescriptionValue, setSearchDescriptionValue] = useState(''); + const [statusSelection, setStatusSelection] = useState(''); - const onSearchNameChange = React.useCallback((value: string) => { + const onSearchNameChange = useCallback((value: string) => { setSearchNameValue(value); }, []); - const onSearchDescriptionChange = React.useCallback((value: string) => { + const onSearchDescriptionChange = useCallback((value: string) => { setSearchDescriptionValue(value); }, []); - const onFilter = React.useCallback( + const onFilter = useCallback( (workspaceKind: WorkspaceKind) => { let nameRegex: RegExp; let descriptionRegex: RegExp; @@ -178,24 +176,24 @@ export const WorkspaceKinds: React.FunctionComponent = () => { [searchNameValue, searchDescriptionValue, statusSelection], ); - const filteredWorkspaceKinds = React.useMemo( + const filteredWorkspaceKinds = useMemo( () => sortedWorkspaceKinds.filter(onFilter), [sortedWorkspaceKinds, onFilter], ); - const clearAllFilters = React.useCallback(() => { + const clearAllFilters = useCallback(() => { setSearchNameValue(''); setStatusSelection(''); setSearchDescriptionValue(''); }, []); // Set up status single select - const [isStatusMenuOpen, setIsStatusMenuOpen] = React.useState(false); - const statusToggleRef = React.useRef(null); - const statusMenuRef = React.useRef(null); - const statusContainerRef = React.useRef(null); + const [isStatusMenuOpen, setIsStatusMenuOpen] = useState(false); + const statusToggleRef = useRef(null); + const statusMenuRef = useRef(null); + const statusContainerRef = useRef(null); - const handleStatusMenuKeys = React.useCallback( + const handleStatusMenuKeys = useCallback( (event: KeyboardEvent) => { if (isStatusMenuOpen && statusMenuRef.current?.contains(event.target as Node)) { if (event.key === 'Escape' || event.key === 'Tab') { @@ -207,7 +205,7 @@ export const WorkspaceKinds: React.FunctionComponent = () => { [isStatusMenuOpen], ); - const handleStatusClickOutside = React.useCallback( + const handleStatusClickOutside = useCallback( (event: MouseEvent) => { if (isStatusMenuOpen && !statusMenuRef.current?.contains(event.target as Node)) { setIsStatusMenuOpen(false); @@ -216,7 +214,7 @@ export const WorkspaceKinds: React.FunctionComponent = () => { [isStatusMenuOpen], ); - React.useEffect(() => { + useEffect(() => { window.addEventListener('keydown', handleStatusMenuKeys); window.addEventListener('click', handleStatusClickOutside); return () => { @@ -225,7 +223,7 @@ export const WorkspaceKinds: React.FunctionComponent = () => { }; }, [isStatusMenuOpen, statusMenuRef, handleStatusClickOutside, handleStatusMenuKeys]); - const onStatusToggleClick = React.useCallback((ev: React.MouseEvent) => { + const onStatusToggleClick = useCallback((ev: React.MouseEvent) => { ev.stopPropagation(); setTimeout(() => { const firstElement = statusMenuRef.current?.querySelector('li > button:not(:disabled)'); @@ -236,7 +234,7 @@ export const WorkspaceKinds: React.FunctionComponent = () => { setIsStatusMenuOpen((prev) => !prev); }, []); - const onStatusSelect = React.useCallback( + const onStatusSelect = useCallback( (event: React.MouseEvent | undefined, itemId: string | number | undefined) => { if (typeof itemId === 'undefined') { return; @@ -248,7 +246,7 @@ export const WorkspaceKinds: React.FunctionComponent = () => { [], ); - const statusToggle = React.useMemo( + const statusToggle = useMemo( () => ( { [isStatusMenuOpen, onStatusToggleClick], ); - const statusMenu = React.useMemo( + const statusMenu = useMemo( () => ( { [statusSelection, onStatusSelect], ); - const statusSelect = React.useMemo( + const statusSelect = useMemo( () => (
{ ); // Set up attribute selector - const [activeAttributeMenu, setActiveAttributeMenu] = React.useState< - 'Name' | 'Description' | 'Status' - >('Name'); - const [isAttributeMenuOpen, setIsAttributeMenuOpen] = React.useState(false); - const attributeToggleRef = React.useRef(null); - const attributeMenuRef = React.useRef(null); - const attributeContainerRef = React.useRef(null); + const [activeAttributeMenu, setActiveAttributeMenu] = useState<'Name' | 'Description' | 'Status'>( + 'Name', + ); + const [isAttributeMenuOpen, setIsAttributeMenuOpen] = useState(false); + const attributeToggleRef = useRef(null); + const attributeMenuRef = useRef(null); + const attributeContainerRef = useRef(null); - const handleAttributeMenuKeys = React.useCallback( + const handleAttributeMenuKeys = useCallback( (event: KeyboardEvent) => { if (!isAttributeMenuOpen) { return; @@ -324,7 +322,7 @@ export const WorkspaceKinds: React.FunctionComponent = () => { [isAttributeMenuOpen], ); - const handleAttributeClickOutside = React.useCallback( + const handleAttributeClickOutside = useCallback( (event: MouseEvent) => { if (isAttributeMenuOpen && !attributeMenuRef.current?.contains(event.target as Node)) { setIsAttributeMenuOpen(false); @@ -333,7 +331,7 @@ export const WorkspaceKinds: React.FunctionComponent = () => { [isAttributeMenuOpen], ); - React.useEffect(() => { + useEffect(() => { window.addEventListener('keydown', handleAttributeMenuKeys); window.addEventListener('click', handleAttributeClickOutside); return () => { @@ -342,7 +340,7 @@ export const WorkspaceKinds: React.FunctionComponent = () => { }; }, [isAttributeMenuOpen, attributeMenuRef, handleAttributeMenuKeys, handleAttributeClickOutside]); - const onAttributeToggleClick = React.useCallback((ev: React.MouseEvent) => { + const onAttributeToggleClick = useCallback((ev: React.MouseEvent) => { ev.stopPropagation(); setTimeout(() => { @@ -355,7 +353,7 @@ export const WorkspaceKinds: React.FunctionComponent = () => { setIsAttributeMenuOpen((prev) => !prev); }, []); - const attributeToggle = React.useMemo( + const attributeToggle = useMemo( () => ( { [isAttributeMenuOpen, onAttributeToggleClick, activeAttributeMenu], ); - const attributeMenu = React.useMemo( + const attributeMenu = useMemo( () => ( { [], ); - const attributeDropdown = React.useMemo( + const attributeDropdown = useMemo( () => (
{ [attributeToggle, attributeMenu, isAttributeMenuOpen], ); - const emptyState = React.useMemo( + const emptyState = useMemo( () => , [clearAllFilters], ); // Actions - const viewDetailsClick = React.useCallback((workspaceKind: WorkspaceKind) => { + const viewDetailsClick = useCallback((workspaceKind: WorkspaceKind) => { setSelectedWorkspaceKind(workspaceKind); setActiveActionType(ActionType.ViewDetails); }, []); - const workspaceKindsDefaultActions = React.useCallback( + const workspaceKindsDefaultActions = useCallback( (workspaceKind: WorkspaceKind): IActions => [ { id: 'view-details', diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx index ba65dbd2..4e059e5c 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { DrawerActions, DrawerCloseButton, @@ -30,7 +30,7 @@ export const WorkspaceKindDetails: React.FunctionComponent { - const [activeTabKey, setActiveTabKey] = React.useState(0); + const [activeTabKey, setActiveTabKey] = useState(0); const handleTabClick = ( event: React.MouseEvent | React.KeyboardEvent | MouseEvent, diff --git a/workspaces/frontend/src/app/pages/Workspaces/DataVolumesList.tsx b/workspaces/frontend/src/app/pages/Workspaces/DataVolumesList.tsx index dd847eee..80f4e3c4 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/DataVolumesList.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/DataVolumesList.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { ClipboardCopy, ClipboardCopyVariant, @@ -11,7 +12,6 @@ import { Tooltip, } from '@patternfly/react-core'; import { DatabaseIcon, LockedIcon } from '@patternfly/react-icons'; -import * as React from 'react'; import { Workspace } from '~/shared/api/backendApiTypes'; interface DataVolumesListProps { diff --git a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetails.tsx index 6e7d8cc4..0a5f6052 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetails.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetails.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { DrawerActions, DrawerCloseButton, @@ -32,7 +32,7 @@ export const WorkspaceDetails: React.FunctionComponent = // onEditClick, onDeleteClick, }) => { - const [activeTabKey, setActiveTabKey] = React.useState(0); + const [activeTabKey, setActiveTabKey] = useState(0); const handleTabClick = ( event: React.MouseEvent | React.KeyboardEvent | MouseEvent, diff --git a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsActions.tsx b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsActions.tsx index 389df7ab..6b67b349 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsActions.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsActions.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React, { useState } from 'react'; import { Dropdown, DropdownList, @@ -18,7 +18,7 @@ export const WorkspaceDetailsActions: React.FC = ( // onEditClick, onDeleteClick, }) => { - const [isOpen, setOpen] = React.useState(false); + const [isOpen, setOpen] = useState(false); return ( diff --git a/workspaces/frontend/src/app/pages/Workspaces/ExpandedWorkspaceRow.tsx b/workspaces/frontend/src/app/pages/Workspaces/ExpandedWorkspaceRow.tsx index 5fd2e38c..aac97ffc 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/ExpandedWorkspaceRow.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/ExpandedWorkspaceRow.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { ExpandableRowContent, Td, Tr } from '@patternfly/react-table'; import { Workspace } from '~/shared/api/backendApiTypes'; import { DataVolumesList } from '~/app/pages/Workspaces/DataVolumesList'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx index bcf41b89..be13e07b 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Button, Content, @@ -10,7 +10,6 @@ import { ProgressStepper, Stack, } from '@patternfly/react-core'; -import { useCallback, useMemo, useState } from 'react'; import useGenericObjectState from '~/app/hooks/useGenericObjectState'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { WorkspaceFormImageSelection } from '~/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection'; @@ -48,13 +47,13 @@ const WorkspaceForm: React.FC = () => { workspaceName, }); - const [isSubmitting, setIsSubmitting] = React.useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); const [currentStep, setCurrentStep] = useState(WorkspaceFormSteps.KindSelection); const [data, setData, resetData, replaceData] = useGenericObjectState(initialFormData); - React.useEffect(() => { + useEffect(() => { if (!initialFormDataLoaded || mode === 'create') { return; } diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageList.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageList.tsx index b038291f..4f89d383 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageList.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageList.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { CardTitle, Gallery, @@ -28,7 +28,7 @@ export const WorkspaceFormImageList: React.FunctionComponent { const [workspaceImages, setWorkspaceImages] = useState(images); const [filters, setFilters] = useState([]); - const filterRef = React.useRef(null); + const filterRef = useRef(null); const filterableColumns = useMemo( () => ({ diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx index 11aaf74e..83ac839a 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState, useCallback } from 'react'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; import { Content, Split, SplitItem } from '@patternfly/react-core'; import { WorkspaceFormImageDetails } from '~/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails'; import { WorkspaceFormImageList } from '~/app/pages/Workspaces/Form/image/WorkspaceFormImageList'; @@ -19,7 +19,7 @@ const WorkspaceFormImageSelection: React.FunctionComponent { const [selectedLabels, setSelectedLabels] = useState>>(new Map()); const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = React.useRef(undefined); + const drawerRef = useRef(undefined); const onExpand = useCallback(() => { if (drawerRef.current) { diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx index e7a07f61..9eb3c84b 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; import { CardBody, CardTitle, @@ -25,7 +25,7 @@ export const WorkspaceFormKindList: React.FunctionComponent { const [workspaceKinds, setWorkspaceKinds] = useState(allWorkspaceKinds); - const filterRef = React.useRef(null); + const filterRef = useRef(null); const filterableColumns = useMemo( () => ({ diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx index a4385f3f..342a4825 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef, useMemo, useCallback } from 'react'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; import { Content } from '@patternfly/react-core'; import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList.tsx index a7024458..62d28cca 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { CardTitle, Gallery, @@ -26,7 +26,7 @@ export const WorkspaceFormPodConfigList: React.FunctionComponent< const [workspacePodConfigs, setWorkspacePodConfigs] = useState(podConfigs); const [filters, setFilters] = useState([]); - const filterRef = React.useRef(null); + const filterRef = useRef(null); const filterableColumns = useMemo( () => ({ diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx index 0d49c130..d3c04744 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; import { Content, Split, SplitItem } from '@patternfly/react-core'; import { WorkspaceFormPodConfigDetails } from '~/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigDetails'; import { WorkspaceFormPodConfigList } from '~/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList'; @@ -17,7 +17,7 @@ const WorkspaceFormPodConfigSelection: React.FunctionComponent< > = ({ podConfigs, selectedPodConfig, onSelect }) => { const [selectedLabels, setSelectedLabels] = useState>>(new Map()); const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = React.useRef(undefined); + const drawerRef = useRef(undefined); const onExpand = useCallback(() => { if (drawerRef.current) { diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSelection.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSelection.tsx index d5429e0a..fcc03a1b 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSelection.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSelection.tsx @@ -1,5 +1,4 @@ -import * as React from 'react'; -import { useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { Checkbox, Content, diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes.tsx index 0874c863..9feb7374 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes.tsx @@ -1,3 +1,4 @@ +import React, { useCallback, useState } from 'react'; import { Button, Dropdown, @@ -15,7 +16,6 @@ import { } from '@patternfly/react-core'; import { EllipsisVIcon } from '@patternfly/react-icons'; import { Table, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; -import React, { useCallback, useState } from 'react'; import { WorkspacePodVolumeMount } from '~/shared/api/backendApiTypes'; interface WorkspaceFormPropertiesVolumesProps { diff --git a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConnectAction.tsx b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConnectAction.tsx index fa87ea03..ab2decb2 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConnectAction.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConnectAction.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Dropdown, DropdownItem, @@ -16,7 +16,7 @@ type WorkspaceConnectActionProps = { export const WorkspaceConnectAction: React.FunctionComponent = ({ workspace, }) => { - const [open, setIsOpen] = React.useState(false); + const [open, setIsOpen] = useState(false); const onToggleClick = () => { setIsOpen(!open); diff --git a/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx b/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx index afbaba37..2dcb7098 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Drawer, DrawerContent, @@ -33,7 +33,6 @@ import { QuestionCircleIcon, CodeIcon, } from '@patternfly/react-icons'; -import { useState } from 'react'; import { formatDistanceToNow } from 'date-fns'; import { Workspace, WorkspaceState } from '~/shared/api/backendApiTypes'; import { WorkspaceDetails } from '~/app/pages/Workspaces/Details/WorkspaceDetails'; @@ -68,7 +67,7 @@ export enum ActionType { export const Workspaces: React.FunctionComponent = () => { const navigate = useTypedNavigate(); - const createWorkspace = React.useCallback(() => { + const createWorkspace = useCallback(() => { navigate('workspaceCreate'); }, [navigate]); @@ -83,7 +82,7 @@ export const Workspaces: React.FunctionComponent = () => { const workspaceRedirectStatus = buildWorkspaceRedirectStatus(workspaceKinds); // Table columns - const columnNames: WorkspacesColumnNames = React.useMemo( + const columnNames: WorkspacesColumnNames = useMemo( () => ({ redirectStatus: 'Redirect Status', name: 'Name', @@ -114,20 +113,20 @@ export const Workspaces: React.FunctionComponent = () => { const [initialWorkspaces, initialWorkspacesLoaded, , initialWorkspacesRefresh] = useWorkspaces(selectedNamespace); const [workspaces, setWorkspaces] = useState([]); - const [expandedWorkspacesNames, setExpandedWorkspacesNames] = React.useState([]); - const [selectedWorkspace, setSelectedWorkspace] = React.useState(null); - const [isActionAlertModalOpen, setIsActionAlertModalOpen] = React.useState(false); - const [activeActionType, setActiveActionType] = React.useState(null); - const filterRef = React.useRef(null); + const [expandedWorkspacesNames, setExpandedWorkspacesNames] = useState([]); + const [selectedWorkspace, setSelectedWorkspace] = useState(null); + const [isActionAlertModalOpen, setIsActionAlertModalOpen] = useState(false); + const [activeActionType, setActiveActionType] = useState(null); + const filterRef = useRef(null); - React.useEffect(() => { + useEffect(() => { if (!initialWorkspacesLoaded) { return; } setWorkspaces(initialWorkspaces ?? []); }, [initialWorkspaces, initialWorkspacesLoaded]); - React.useEffect(() => { + useEffect(() => { if (activeActionType !== ActionType.Edit || !selectedWorkspace) { return; } @@ -139,7 +138,7 @@ export const Workspaces: React.FunctionComponent = () => { }); }, [activeActionType, navigate, selectedWorkspace]); - const selectWorkspace = React.useCallback( + const selectWorkspace = useCallback( (newSelectedWorkspace: Workspace | null) => { if (selectedWorkspace?.name === newSelectedWorkspace?.name) { setSelectedWorkspace(null); @@ -162,7 +161,7 @@ export const Workspaces: React.FunctionComponent = () => { expandedWorkspacesNames.includes(workspace.name); // filter function to pass to the filter component - const onFilter = React.useCallback( + const onFilter = useCallback( (filters: FilteredColumn[]) => { // Search name with search value let filteredWorkspaces = initialWorkspaces ?? []; @@ -212,15 +211,15 @@ export const Workspaces: React.FunctionComponent = () => { [initialWorkspaces, columnNames], ); - const emptyState = React.useMemo( + const emptyState = useMemo( () => filterRef.current?.clearAll()} />, [], ); // Column sorting - const [activeSortIndex, setActiveSortIndex] = React.useState(null); - const [activeSortDirection, setActiveSortDirection] = React.useState<'asc' | 'desc' | null>(null); + const [activeSortIndex, setActiveSortIndex] = useState(null); + const [activeSortDirection, setActiveSortDirection] = useState<'asc' | 'desc' | null>(null); const getSortableRowValues = (workspace: Workspace): (string | number)[] => { const { redirectStatus, name, kind, image, podConfig, state, homeVol, cpu, ram, lastActivity } = @@ -274,7 +273,7 @@ export const Workspaces: React.FunctionComponent = () => { // Actions - const viewDetailsClick = React.useCallback((workspace: Workspace) => { + const viewDetailsClick = useCallback((workspace: Workspace) => { setSelectedWorkspace(workspace); setActiveActionType(ActionType.ViewDetails); }, []); @@ -285,7 +284,7 @@ export const Workspaces: React.FunctionComponent = () => { // setActiveActionType(ActionType.Edit); // }, []); - const deleteAction = React.useCallback(async () => { + const deleteAction = useCallback(async () => { if (!selectedWorkspace) { return; } @@ -301,19 +300,19 @@ export const Workspaces: React.FunctionComponent = () => { } }, [api, initialWorkspacesRefresh, selectedNamespace, selectedWorkspace]); - const startRestartAction = React.useCallback((workspace: Workspace, action: ActionType) => { + const startRestartAction = useCallback((workspace: Workspace, action: ActionType) => { setSelectedWorkspace(workspace); setActiveActionType(action); setIsActionAlertModalOpen(true); }, []); - const stopAction = React.useCallback((workspace: Workspace) => { + const stopAction = useCallback((workspace: Workspace) => { setSelectedWorkspace(workspace); setActiveActionType(ActionType.Stop); setIsActionAlertModalOpen(true); }, []); - const handleDeleteClick = React.useCallback((workspace: Workspace) => { + const handleDeleteClick = useCallback((workspace: Workspace) => { const buttonElement = document.activeElement as HTMLElement; buttonElement.blur(); // Remove focus from the currently focused button setSelectedWorkspace(workspace); @@ -482,8 +481,8 @@ export const Workspaces: React.FunctionComponent = () => { // Pagination - const [page, setPage] = React.useState(1); - const [perPage, setPerPage] = React.useState(10); + const [page, setPage] = useState(1); + const [perPage, setPerPage] = useState(10); const onSetPage = ( _event: React.MouseEvent | React.KeyboardEvent | MouseEvent, diff --git a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView.tsx b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView.tsx index cd277b27..a50f6d24 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView.tsx @@ -1,10 +1,10 @@ +import React, { useEffect, useState } from 'react'; import { ExpandableSection, Icon, Tab, Tabs, TabTitleText, Content } from '@patternfly/react-core'; import { ExclamationCircleIcon, ExclamationTriangleIcon, InfoCircleIcon, } from '@patternfly/react-icons'; -import * as React from 'react'; import useWorkspaceKindByName from '~/app/hooks/useWorkspaceKindByName'; import { WorkspaceKind } from '~/shared/api/backendApiTypes'; @@ -44,14 +44,14 @@ interface WorkspaceRedirectInformationViewProps { export const WorkspaceRedirectInformationView: React.FC = ({ kind, }) => { - const [activeKey, setActiveKey] = React.useState(0); + const [activeKey, setActiveKey] = useState(0); const [workspaceKind, workspaceKindLoaded] = useWorkspaceKindByName(kind); const [imageConfig, setImageConfig] = - React.useState(); + useState(); const [podConfig, setPodConfig] = - React.useState(); + useState(); - React.useEffect(() => { + useEffect(() => { if (!workspaceKindLoaded) { return; } diff --git a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRestartActionModal.tsx b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRestartActionModal.tsx index aa012a88..507d9fc6 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRestartActionModal.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRestartActionModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { Button, Content, diff --git a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal.tsx b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal.tsx index 41a200b4..976d5df7 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React, { useCallback, useState } from 'react'; import { Button, Modal, @@ -30,9 +30,9 @@ export const WorkspaceStartActionModal: React.FC = ({ onUpdateAndStart, onActionDone, }) => { - const [actionOnGoing, setActionOnGoing] = React.useState(null); + const [actionOnGoing, setActionOnGoing] = useState(null); - const executeAction = React.useCallback( + const executeAction = useCallback( async (args: { action: StartAction; callback: () => Promise }) => { setActionOnGoing(args.action); try { @@ -44,7 +44,7 @@ export const WorkspaceStartActionModal: React.FC = ({ [], ); - const handleStart = React.useCallback(async () => { + const handleStart = useCallback(async () => { try { await executeAction({ action: 'start', callback: onStart }); // TODO: alert user about success @@ -58,7 +58,7 @@ export const WorkspaceStartActionModal: React.FC = ({ }, [executeAction, onActionDone, onClose, onStart]); // TODO: combine handleStart and handleUpdateAndStart if they end up being similar - const handleUpdateAndStart = React.useCallback(async () => { + const handleUpdateAndStart = useCallback(async () => { try { await executeAction({ action: 'updateAndStart', callback: onUpdateAndStart }); // TODO: alert user about success @@ -71,7 +71,7 @@ export const WorkspaceStartActionModal: React.FC = ({ } }, [executeAction, onActionDone, onClose, onUpdateAndStart]); - const shouldShowActionButton = React.useCallback( + const shouldShowActionButton = useCallback( (action: StartAction) => !actionOnGoing || actionOnGoing === action, [actionOnGoing], ); diff --git a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal.tsx b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal.tsx index eb00e425..51560c7d 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React, { useCallback, useState } from 'react'; import { Button, Content, @@ -32,9 +32,9 @@ export const WorkspaceStopActionModal: React.FC = ({ onActionDone, }) => { const workspacePendingUpdate = workspace?.pendingRestart; - const [actionOnGoing, setActionOnGoing] = React.useState(null); + const [actionOnGoing, setActionOnGoing] = useState(null); - const executeAction = React.useCallback( + const executeAction = useCallback( async (args: { action: StopAction; callback: () => Promise }) => { setActionOnGoing(args.action); try { @@ -46,7 +46,7 @@ export const WorkspaceStopActionModal: React.FC = ({ [], ); - const handleStop = React.useCallback(async () => { + const handleStop = useCallback(async () => { try { await executeAction({ action: 'stop', callback: onStop }); // TODO: alert user about success @@ -60,7 +60,7 @@ export const WorkspaceStopActionModal: React.FC = ({ }, [executeAction, onActionDone, onClose, onStop]); // TODO: combine handleStop and handleUpdateAndStop if they end up being similar - const handleUpdateAndStop = React.useCallback(async () => { + const handleUpdateAndStop = useCallback(async () => { try { await executeAction({ action: 'updateAndStop', callback: onUpdateAndStop }); // TODO: alert user about success @@ -73,7 +73,7 @@ export const WorkspaceStopActionModal: React.FC = ({ } }, [executeAction, onActionDone, onClose, onUpdateAndStop]); - const shouldShowActionButton = React.useCallback( + const shouldShowActionButton = useCallback( (action: StopAction) => !actionOnGoing || actionOnGoing === action, [actionOnGoing], ); diff --git a/workspaces/frontend/src/app/pages/notFound/NotFound.tsx b/workspaces/frontend/src/app/pages/notFound/NotFound.tsx index 3cd8113c..e8a4c2db 100644 --- a/workspaces/frontend/src/app/pages/notFound/NotFound.tsx +++ b/workspaces/frontend/src/app/pages/notFound/NotFound.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { ExclamationTriangleIcon } from '@patternfly/react-icons'; import { Button, diff --git a/workspaces/frontend/src/shared/api/useAPIState.ts b/workspaces/frontend/src/shared/api/useAPIState.ts index e6c7ec87..507ffa82 100644 --- a/workspaces/frontend/src/shared/api/useAPIState.ts +++ b/workspaces/frontend/src/shared/api/useAPIState.ts @@ -1,17 +1,17 @@ -import * as React from 'react'; +import { useCallback, useMemo, useState } from 'react'; import { APIState } from '~/shared/api/types'; const useAPIState = ( hostPath: string | null, createAPI: (path: string) => T, ): [apiState: APIState, refreshAPIState: () => void] => { - const [internalAPIToggleState, setInternalAPIToggleState] = React.useState(false); + const [internalAPIToggleState, setInternalAPIToggleState] = useState(false); - const refreshAPIState = React.useCallback(() => { + const refreshAPIState = useCallback(() => { setInternalAPIToggleState((v) => !v); }, []); - const apiState = React.useMemo>(() => { + const apiState = useMemo>(() => { let path = hostPath; if (!path) { // TODO: we need to figure out maybe a stopgap or something diff --git a/workspaces/frontend/src/shared/components/ActionButton.tsx b/workspaces/frontend/src/shared/components/ActionButton.tsx index 95fa6a86..b740a561 100644 --- a/workspaces/frontend/src/shared/components/ActionButton.tsx +++ b/workspaces/frontend/src/shared/components/ActionButton.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React, { useCallback, useState } from 'react'; import { Button } from '@patternfly/react-core'; type ActionButtonProps = { @@ -13,9 +13,9 @@ export const ActionButton: React.FC = ({ onClick, ...props }) => { - const [isLoading, setIsLoading] = React.useState(false); + const [isLoading, setIsLoading] = useState(false); - const handleClick = React.useCallback(async () => { + const handleClick = useCallback(async () => { setIsLoading(true); try { await onClick(); diff --git a/workspaces/frontend/src/shared/components/CustomEmptyState.tsx b/workspaces/frontend/src/shared/components/CustomEmptyState.tsx index 0d88f47c..e1c365b6 100644 --- a/workspaces/frontend/src/shared/components/CustomEmptyState.tsx +++ b/workspaces/frontend/src/shared/components/CustomEmptyState.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { EmptyState, EmptyStateBody, diff --git a/workspaces/frontend/src/shared/components/DeleteModal.tsx b/workspaces/frontend/src/shared/components/DeleteModal.tsx index fa9d87fe..f97d7f7d 100644 --- a/workspaces/frontend/src/shared/components/DeleteModal.tsx +++ b/workspaces/frontend/src/shared/components/DeleteModal.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { Modal, ModalBody, @@ -34,7 +34,7 @@ const DeleteModal: React.FC = ({ onDelete, }) => { const [inputValue, setInputValue] = useState(''); - const [isDeleting, setIsDeleting] = React.useState(false); + const [isDeleting, setIsDeleting] = useState(false); useEffect(() => { if (!isOpen) { diff --git a/workspaces/frontend/src/shared/components/Filter.tsx b/workspaces/frontend/src/shared/components/Filter.tsx index 64984ef7..b9371438 100644 --- a/workspaces/frontend/src/shared/components/Filter.tsx +++ b/workspaces/frontend/src/shared/components/Filter.tsx @@ -1,4 +1,11 @@ -import * as React from 'react'; +import React, { + useCallback, + useEffect, + useImperativeHandle, + useMemo, + useRef, + useState, +} from 'react'; import { Menu, MenuContent, @@ -38,19 +45,19 @@ export interface FilterRef { const Filter = React.forwardRef( ({ id, onFilter, columnNames, toolbarActions }, ref) => { Filter.displayName = 'Filter'; - const [activeFilter, setActiveFilter] = React.useState({ + const [activeFilter, setActiveFilter] = useState({ columnName: Object.values(columnNames)[0], value: '', }); - const [searchValue, setSearchValue] = React.useState(''); - const [isFilterMenuOpen, setIsFilterMenuOpen] = React.useState(false); - const [filters, setFilters] = React.useState([]); + const [searchValue, setSearchValue] = useState(''); + const [isFilterMenuOpen, setIsFilterMenuOpen] = useState(false); + const [filters, setFilters] = useState([]); - const filterToggleRef = React.useRef(null); - const filterMenuRef = React.useRef(null); - const filterContainerRef = React.useRef(null); + const filterToggleRef = useRef(null); + const filterMenuRef = useRef(null); + const filterContainerRef = useRef(null); - const handleFilterMenuKeys = React.useCallback( + const handleFilterMenuKeys = useCallback( (event: KeyboardEvent) => { if (!isFilterMenuOpen) { return; @@ -68,7 +75,7 @@ const Filter = React.forwardRef( [isFilterMenuOpen, filterMenuRef, filterToggleRef], ); - const handleClickOutside = React.useCallback( + const handleClickOutside = useCallback( (event: MouseEvent) => { if (isFilterMenuOpen && !filterMenuRef.current?.contains(event.target as Node)) { setIsFilterMenuOpen(false); @@ -77,7 +84,7 @@ const Filter = React.forwardRef( [isFilterMenuOpen, filterMenuRef], ); - React.useEffect(() => { + useEffect(() => { window.addEventListener('keydown', handleFilterMenuKeys); window.addEventListener('click', handleClickOutside); return () => { @@ -86,7 +93,7 @@ const Filter = React.forwardRef( }; }, [isFilterMenuOpen, filterMenuRef, handleFilterMenuKeys, handleClickOutside]); - const onFilterToggleClick = React.useCallback( + const onFilterToggleClick = useCallback( (ev: React.MouseEvent) => { ev.stopPropagation(); // Stop handleClickOutside from handling setTimeout(() => { @@ -100,7 +107,7 @@ const Filter = React.forwardRef( [isFilterMenuOpen], ); - const updateFilters = React.useCallback( + const updateFilters = useCallback( (filterObj: FilteredColumn) => { setFilters((prevFilters) => { const index = prevFilters.findIndex( @@ -128,7 +135,7 @@ const Filter = React.forwardRef( [onFilter], ); - const onSearchChange = React.useCallback( + const onSearchChange = useCallback( (value: string) => { setSearchValue(value); setActiveFilter((prevActiveFilter) => { @@ -140,7 +147,7 @@ const Filter = React.forwardRef( [updateFilters], ); - const onDeleteLabelGroup = React.useCallback( + const onDeleteLabelGroup = useCallback( (filter: FilteredColumn) => { setFilters((prevFilters) => { const newFilters = prevFilters.filter( @@ -161,7 +168,7 @@ const Filter = React.forwardRef( ); // Expose the clearAllFilters logic via the ref - const clearAllInternal = React.useCallback(() => { + const clearAllInternal = useCallback(() => { setFilters([]); setSearchValue(''); setActiveFilter({ @@ -171,11 +178,11 @@ const Filter = React.forwardRef( onFilter([]); }, [columnNames, onFilter]); - React.useImperativeHandle(ref, () => ({ + useImperativeHandle(ref, () => ({ clearAll: clearAllInternal, })); - const onFilterSelect = React.useCallback( + const onFilterSelect = useCallback( (itemId: string | number | undefined) => { // Use the functional update form to toggle the state setIsFilterMenuOpen((prevIsMenuOpen) => !prevIsMenuOpen); // Fix is here @@ -195,7 +202,7 @@ const Filter = React.forwardRef( [columnNames, filters], ); - const filterMenuToggle = React.useMemo( + const filterMenuToggle = useMemo( () => ( ( [activeFilter.columnName, isFilterMenuOpen, onFilterToggleClick], ); - const filterMenu = React.useMemo( + const filterMenu = useMemo( () => ( onFilterSelect(itemId)}> @@ -226,7 +233,7 @@ const Filter = React.forwardRef( [columnNames, id, onFilterSelect], ); - const filterDropdown = React.useMemo( + const filterDropdown = useMemo( () => (
( /** Configurable features */ { refreshRate = 0, initialPromisePurity = false }: Partial = {}, ): FetchState => { - const initialDefaultStateRef = React.useRef(initialDefaultState); - const [result, setResult] = React.useState(initialDefaultState); - const [loaded, setLoaded] = React.useState(false); - const [loadError, setLoadError] = React.useState(undefined); - const abortCallbackRef = React.useRef<() => void>(() => undefined); - const changePendingRef = React.useRef(true); + const initialDefaultStateRef = useRef(initialDefaultState); + const [result, setResult] = useState(initialDefaultState); + const [loaded, setLoaded] = useState(false); + const [loadError, setLoadError] = useState(undefined); + const abortCallbackRef = useRef<() => void>(() => undefined); + const changePendingRef = useRef(true); /** Setup on initial hook a singular reset function. DefaultState & resetDataOnNewPromise are initial render states. */ - const cleanupRef = React.useRef(() => { + const cleanupRef = useRef(() => { if (initialPromisePurity) { setResult(initialDefaultState); setLoaded(false); @@ -136,11 +136,11 @@ const useFetchState = ( } }); - React.useEffect(() => { + useEffect(() => { cleanupRef.current(); }, [fetchCallbackPromise]); - const call = React.useCallback<() => [Promise, () => void]>(() => { + const call = useCallback<() => [Promise, () => void]>(() => { let alreadyAborted = false; const abortController = new AbortController(); @@ -208,13 +208,13 @@ const useFetchState = ( }, [fetchCallbackPromise]); // Use a memmo to update the `changePendingRef` immediately on change. - React.useMemo(() => { + useMemo(() => { changePendingRef.current = true; // React to changes to the `call` reference. // eslint-disable-next-line react-hooks/exhaustive-deps }, [call]); - React.useEffect(() => { + useEffect(() => { let interval: ReturnType; const callAndSave = () => { @@ -237,10 +237,10 @@ const useFetchState = ( }, [call, refreshRate]); // Use a reference for `call` to ensure a stable reference to `refresh` is always returned - const callRef = React.useRef(call); + const callRef = useRef(call); callRef.current = call; - const refresh = React.useCallback>(() => { + const refresh = useCallback>(() => { abortCallbackRef.current(); const [callPromise, unload] = callRef.current(); abortCallbackRef.current = unload; From 72b3ba90623ebf9d93b1c21f78cbefdb0ed13ea7 Mon Sep 17 00:00:00 2001 From: Paulo Rego <832830+paulovmr@users.noreply.github.com> Date: Mon, 16 Jun 2025 18:20:09 -0300 Subject: [PATCH 07/68] chore(ws): Upgrade vulnerable packages (#427) Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> --- workspaces/frontend/package-lock.json | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/workspaces/frontend/package-lock.json b/workspaces/frontend/package-lock.json index c50e6cbd..f95aa15e 100644 --- a/workspaces/frontend/package-lock.json +++ b/workspaces/frontend/package-lock.json @@ -5314,9 +5314,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/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==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "optional": true, "dependencies": { @@ -6637,9 +6637,10 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -16006,9 +16007,9 @@ } }, "node_modules/mocha/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==", + "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", "peer": true, @@ -19217,9 +19218,9 @@ } }, "node_modules/rimraf/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==", + "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": { From ca7f65697d0eb3eddaed98106aaf0ad1e4b2ef85 Mon Sep 17 00:00:00 2001 From: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> Date: Tue, 17 Jun 2025 08:46:09 -0300 Subject: [PATCH 08/68] feat(ws): add `WorkspaceKindSummary` page and other improvements around it (#415) * Minor refactorings and initial work for the Workspace Kind summary page Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> * feat(ws): added links from workspace kind details drawer to workspace kinds details page (#1) Signed-off-by: Paulo Rego <832830+paulovmr@users.noreply.github.com> * Enable workspace filtering by namespace in the WorkspaceKind summary page Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> * Update Pause/Start action response types according to backend Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> * Fix WorkspaceKind logo href Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> * Replace placeholders for GPU data with real values in WorkspaceKind summary page Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> * Allow columns to be hidden in the WorkspaceTable Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> * feat(ws): added links from workspace kind details drawer namespace tab to workspace kinds details page (#2) Signed-off-by: Paulo Rego <832830+paulovmr@users.noreply.github.com> * Improve types around Filter component Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> * feat: Add Workspace Actions Context and related components - Introduced WorkspaceActionsContext to manage workspace actions such as view, edit, delete, start, restart, and stop. - Created WorkspaceActionsContextProvider to encapsulate the context logic and provide it to child components. - Implemented WorkspaceKindSummary and Workspaces components to utilize the new context for handling workspace actions. - Added polling for refreshing workspaces at a default interval. - Enhanced WorkspaceTable to support row actions for workspaces. - Updated various components to include sortable and filterable data fields. - Refactored WorkspaceStartActionModal and WorkspaceStopActionModal to handle optional onActionDone callback. - Added loading and error handling components for better user experience. Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> * feat: Add buildWorkspaceList function and integrate into mockAllWorkspaces Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> * refactor: Update mock data and formatting for workspace activity timestamps Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> * feat: Implement usePolling hook and refactor workspace actions in Workspaces and WorkspaceKindSummary components Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> * refactor: Update column key usage in ExpandedWorkspaceRow and adjust workspace actions visibility in Workspaces component Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> * Make mocked workspace list deterministic Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> * feat: Enhance WorkspaceTable with additional columns and filtering capabilities - Added 'namespace', 'gpu', and 'idleGpu' columns to WorkspaceTable. - Updated filtering logic to support new columns in WorkspaceTable. - Refactored useWorkspaces hook to remove unnecessary parameters related to idle and GPU filtering. - Modified WorkspaceKindSummary and its expandable card to utilize new filtering functionality. - Updated WorkspaceUtils to include a method for formatting workspace idle state. - Adjusted Filter component to support generic filtered column types. - Updated Workspaces page to hide new columns as needed. Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> * refactor: Improve sorting functionality in WorkspaceTable by utilizing specific types for sortable columns Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> * Adjustments after rebase Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> * Format with prettier Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> --------- Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> Signed-off-by: Paulo Rego <832830+paulovmr@users.noreply.github.com> Co-authored-by: Paulo Rego <832830+paulovmr@users.noreply.github.com> --- workspaces/frontend/.eslintrc.js | 435 +++++------ .../no-react-hook-namespace.js | 23 +- workspaces/frontend/package-lock.json | 7 +- workspaces/frontend/package.json | 1 + .../cypress/tests/mocked/application.cy.ts | 5 + .../cypress/tests/mocked/workspace.mock.ts | 8 +- .../workspaces/WorkspaceDetailsActivity.cy.ts | 15 +- .../workspaces/filterWorkspacesTest.cy.ts | 18 +- workspaces/frontend/src/app/AppRoutes.tsx | 10 +- .../frontend/src/app/components/LoadError.tsx | 16 + .../src/app/components/LoadingSpinner.tsx | 10 + .../src/app/components/WorkspaceTable.tsx | 587 ++++++++++++++ workspaces/frontend/src/app/const.ts | 2 + .../app/context/WorkspaceActionsContext.tsx | 224 ++++++ .../frontend/src/app/filterableDataHelper.ts | 43 ++ .../app/hooks/__tests__/usePolling.spec.tsx | 47 ++ .../frontend/src/app/hooks/usePolling.tsx | 8 + .../src/app/hooks/useWorkspaceCountPerKind.ts | 1 + .../src/app/hooks/useWorkspaceFormData.ts | 7 +- .../src/app/hooks/useWorkspaceRowActions.ts | 87 +++ .../frontend/src/app/hooks/useWorkspaces.ts | 43 +- .../pages/WorkspaceKinds/WorkspaceKinds.tsx | 23 +- .../details/WorkspaceKindDetails.tsx | 39 +- .../details/WorkspaceKindDetailsImages.tsx | 52 +- .../WorkspaceKindDetailsNamespaces.tsx | 62 +- .../details/WorkspaceKindDetailsOverview.tsx | 2 +- .../WorkspaceKindDetailsPodConfigs.tsx | 52 +- .../summary/WorkspaceKindSummary.tsx | 104 +++ .../WorkspaceKindSummaryExpandableCard.tsx | 179 +++++ .../summary/WorkspaceKindSummaryWrapper.tsx | 9 + .../Details/WorkspaceDetailsActivity.tsx | 10 +- .../pages/Workspaces/ExpandedWorkspaceRow.tsx | 12 +- .../Form/image/WorkspaceFormImageList.tsx | 72 +- .../Form/kind/WorkspaceFormKindList.tsx | 82 +- .../podConfig/WorkspaceFormPodConfigList.tsx | 81 +- .../src/app/pages/Workspaces/Workspaces.tsx | 725 ++---------------- .../pages/Workspaces/WorkspacesWrapper.tsx | 9 + .../WorkspaceStartActionModal.tsx | 25 +- .../WorkspaceStopActionModal.tsx | 22 +- workspaces/frontend/src/app/routerHelper.ts | 31 +- workspaces/frontend/src/app/routes.ts | 10 + workspaces/frontend/src/app/types.ts | 13 - .../src/shared/api/backendApiTypes.ts | 9 +- .../frontend/src/shared/api/notebookApi.ts | 7 +- .../src/shared/api/notebookService.ts | 11 +- .../frontend/src/shared/components/Filter.tsx | 136 ++-- .../frontend/src/shared/mock/mockBuilder.ts | 131 +++- .../src/shared/mock/mockNotebookService.ts | 10 +- .../shared/mock/mockNotebookServiceData.ts | 24 +- .../src/shared/utilities/WorkspaceUtils.ts | 107 ++- .../utilities/__tests__/valueUnits.spec.ts | 226 ++++++ .../src/shared/utilities/valueUnits.ts | 204 +++++ 52 files changed, 2779 insertions(+), 1297 deletions(-) create mode 100644 workspaces/frontend/src/app/components/LoadError.tsx create mode 100644 workspaces/frontend/src/app/components/LoadingSpinner.tsx create mode 100644 workspaces/frontend/src/app/components/WorkspaceTable.tsx create mode 100644 workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx create mode 100644 workspaces/frontend/src/app/filterableDataHelper.ts create mode 100644 workspaces/frontend/src/app/hooks/__tests__/usePolling.spec.tsx create mode 100644 workspaces/frontend/src/app/hooks/usePolling.tsx create mode 100644 workspaces/frontend/src/app/hooks/useWorkspaceRowActions.ts create mode 100644 workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummary.tsx create mode 100644 workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard.tsx create mode 100644 workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryWrapper.tsx create mode 100644 workspaces/frontend/src/app/pages/Workspaces/WorkspacesWrapper.tsx create mode 100644 workspaces/frontend/src/shared/utilities/__tests__/valueUnits.spec.ts create mode 100644 workspaces/frontend/src/shared/utilities/valueUnits.ts diff --git a/workspaces/frontend/.eslintrc.js b/workspaces/frontend/.eslintrc.js index fc6719dd..53455a9c 100644 --- a/workspaces/frontend/.eslintrc.js +++ b/workspaces/frontend/.eslintrc.js @@ -1,276 +1,277 @@ const noReactHookNamespace = require('./eslint-local-rules/no-react-hook-namespace'); module.exports = { - "parser": "@typescript-eslint/parser", - "env": { - "browser": true, - "node": true + parser: '@typescript-eslint/parser', + env: { + browser: true, + node: true, }, // tell the TypeScript parser that we want to use JSX syntax - "parserOptions": { - "tsx": true, - "jsx": true, - "js": true, - "useJSXTextNode": true, - "project": "./tsconfig.json", - "tsconfigRootDir": "." + parserOptions: { + tsx: true, + jsx: true, + js: true, + useJSXTextNode: true, + project: './tsconfig.json', + tsconfigRootDir: '.', }, // includes the typescript specific rules found here: https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules - "plugins": [ - "@typescript-eslint", - "react-hooks", - "eslint-plugin-react-hooks", - "import", - "no-only-tests", - "no-relative-import-paths", - "prettier" + plugins: [ + '@typescript-eslint', + 'react-hooks', + 'eslint-plugin-react-hooks', + 'import', + 'no-only-tests', + 'no-relative-import-paths', + 'prettier', ], - "extends": [ - "eslint:recommended", - "plugin:jsx-a11y/recommended", - "plugin:react/recommended", - "plugin:@typescript-eslint/recommended", - "plugin:prettier/recommended", - "prettier" + extends: [ + 'eslint:recommended', + 'plugin:jsx-a11y/recommended', + 'plugin:react/recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + 'prettier', ], - "globals": { - "window": "readonly", - "describe": "readonly", - "test": "readonly", - "expect": "readonly", - "it": "readonly", - "process": "readonly", - "document": "readonly" + globals: { + window: 'readonly', + describe: 'readonly', + test: 'readonly', + expect: 'readonly', + it: 'readonly', + process: 'readonly', + document: 'readonly', }, - "settings": { - "react": { - "version": "detect" + settings: { + react: { + version: 'detect', }, - "import/parsers": { - "@typescript-eslint/parser": [".ts", ".tsx"] + 'import/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx'], + }, + 'import/resolver': { + typescript: { + project: '.', + }, }, - "import/resolver": { - "typescript": { - "project": "." - } - } }, - "rules": { - "jsx-a11y/no-autofocus": ["error", { "ignoreNonDOM": true }], - "jsx-a11y/anchor-is-valid": [ - "error", + rules: { + 'jsx-a11y/no-autofocus': ['error', { ignoreNonDOM: true }], + 'jsx-a11y/anchor-is-valid': [ + 'error', { - "components": ["Link"], - "specialLink": ["to"], - "aspects": ["noHref", "invalidHref", "preferButton"] - } + components: ['Link'], + specialLink: ['to'], + aspects: ['noHref', 'invalidHref', 'preferButton'], + }, ], - "react/jsx-boolean-value": "error", - "react/jsx-fragments": "error", - "react/jsx-no-constructed-context-values": "error", - "react/no-unused-prop-types": "error", - "arrow-body-style": "error", - "curly": "error", - "no-only-tests/no-only-tests": "error", - "@typescript-eslint/default-param-last": "error", - "@typescript-eslint/dot-notation": ["error", { "allowKeywords": true }], - "@typescript-eslint/method-signature-style": "error", - "@typescript-eslint/naming-convention": [ - "error", + 'react/jsx-boolean-value': 'error', + 'react/jsx-fragments': 'error', + 'react/jsx-no-constructed-context-values': 'error', + 'react/no-unused-prop-types': 'error', + 'arrow-body-style': 'error', + curly: 'error', + 'no-only-tests/no-only-tests': 'error', + '@typescript-eslint/default-param-last': 'error', + '@typescript-eslint/dot-notation': ['error', { allowKeywords: true }], + '@typescript-eslint/method-signature-style': 'error', + '@typescript-eslint/naming-convention': [ + 'error', { - "selector": "variable", - "format": ["camelCase", "PascalCase", "UPPER_CASE"] + selector: 'variable', + format: ['camelCase', 'PascalCase', 'UPPER_CASE'], }, { - "selector": "function", - "format": ["camelCase", "PascalCase"] + selector: 'function', + format: ['camelCase', 'PascalCase'], }, { - "selector": "typeLike", - "format": ["PascalCase"] - } + selector: 'typeLike', + format: ['PascalCase'], + }, ], - "@typescript-eslint/no-unused-expressions": [ - "error", + '@typescript-eslint/no-unused-expressions': [ + 'error', { - "allowShortCircuit": false, - "allowTernary": false, - "allowTaggedTemplates": false - } + allowShortCircuit: false, + allowTernary: false, + allowTaggedTemplates: false, + }, ], - "@typescript-eslint/no-redeclare": "error", - "@typescript-eslint/no-shadow": "error", - "@typescript-eslint/return-await": ["error", "in-try-catch"], - "camelcase": "warn", - "no-else-return": ["error", { "allowElseIf": false }], - "eqeqeq": ["error", "always", { "null": "ignore" }], - "react/jsx-curly-brace-presence": [2, { "props": "never", "children": "never" }], - "object-shorthand": ["error", "always"], - "no-param-reassign": [ - "error", + '@typescript-eslint/no-redeclare': 'error', + '@typescript-eslint/no-shadow': 'error', + '@typescript-eslint/return-await': ['error', 'in-try-catch'], + camelcase: 'warn', + 'no-else-return': ['error', { allowElseIf: false }], + eqeqeq: ['error', 'always', { null: 'ignore' }], + 'react/jsx-curly-brace-presence': [2, { props: 'never', children: 'never' }], + 'object-shorthand': ['error', 'always'], + 'no-param-reassign': [ + 'error', { - "props": true, - "ignorePropertyModificationsFor": ["acc", "e"], - "ignorePropertyModificationsForRegex": ["^assignable[A-Z]"] - } + props: true, + ignorePropertyModificationsFor: ['acc', 'e'], + ignorePropertyModificationsForRegex: ['^assignable[A-Z]'], + }, ], - "@typescript-eslint/no-base-to-string": "error", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/interface-name-prefix": "off", - "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/no-empty-function": "error", - "@typescript-eslint/no-inferrable-types": "error", - "@typescript-eslint/no-unused-vars": "error", - "@typescript-eslint/explicit-module-boundary-types": "error", - "react/self-closing-comp": "error", - "@typescript-eslint/no-unnecessary-condition": "error", - "react-hooks/exhaustive-deps": "error", - "prefer-destructuring": [ - "error", + '@typescript-eslint/no-base-to-string': 'error', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-empty-function': 'error', + '@typescript-eslint/no-inferrable-types': 'error', + '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/explicit-module-boundary-types': 'error', + 'react/self-closing-comp': 'error', + '@typescript-eslint/no-unnecessary-condition': 'error', + 'react-hooks/exhaustive-deps': 'error', + 'prefer-destructuring': [ + 'error', { - "VariableDeclarator": { - "array": false, - "object": true + VariableDeclarator: { + array: false, + object: true, + }, + AssignmentExpression: { + array: true, + object: false, }, - "AssignmentExpression": { - "array": true, - "object": false - } }, { - "enforceForRenamedProperties": false - } + enforceForRenamedProperties: false, + }, ], - "react-hooks/rules-of-hooks": "error", - "import/extensions": "off", - "import/no-unresolved": "off", - "import/order": [ - "error", + 'react-hooks/rules-of-hooks': 'error', + 'import/extensions': 'off', + 'import/no-unresolved': 'off', + 'import/order': [ + 'error', { - "pathGroups": [ + pathGroups: [ { - "pattern": "~/**", - "group": "external", - "position": "after" - } + pattern: '~/**', + group: 'external', + position: 'after', + }, ], - "pathGroupsExcludedImportTypes": ["builtin"], - "groups": [ - "builtin", - "external", - "internal", - "index", - "sibling", - "parent", - "object", - "unknown" - ] - } + pathGroupsExcludedImportTypes: ['builtin'], + groups: [ + 'builtin', + 'external', + 'internal', + 'index', + 'sibling', + 'parent', + 'object', + 'unknown', + ], + }, ], - "import/newline-after-import": "error", - "import/no-duplicates": "error", - "import/no-named-as-default": "error", - "import/no-extraneous-dependencies": [ - "error", + 'import/newline-after-import': 'error', + 'import/no-duplicates': 'error', + 'import/no-named-as-default': 'error', + 'import/no-extraneous-dependencies': [ + 'error', { - "devDependencies": true, - "optionalDependencies": true - } + devDependencies: true, + optionalDependencies: true, + }, ], - "no-relative-import-paths/no-relative-import-paths": [ - "warn", + 'no-relative-import-paths/no-relative-import-paths': [ + 'warn', { - "allowSameFolder": true, - "rootDir": "src", - "prefix": "~" - } + allowSameFolder: true, + rootDir: 'src', + prefix: '~', + }, ], - "prettier/prettier": [ - "error", + 'prettier/prettier': [ + 'error', { - "arrowParens": "always", - "singleQuote": true, - "trailingComma": "all", - "printWidth": 100 - } + arrowParens: 'always', + singleQuote: true, + trailingComma: 'all', + printWidth: 100, + }, ], - "react/prop-types": "off", - "array-callback-return": ["error", { "allowImplicit": true }], - "prefer-template": "error", - "no-lone-blocks": "error", - "no-lonely-if": "error", - "no-promise-executor-return": "error", - "no-restricted-globals": [ - "error", + 'react/prop-types': 'off', + 'array-callback-return': ['error', { allowImplicit: true }], + 'prefer-template': 'error', + 'no-lone-blocks': 'error', + 'no-lonely-if': 'error', + 'no-promise-executor-return': 'error', + 'no-restricted-globals': [ + 'error', { - "name": "isFinite", - "message": "Use Number.isFinite instead https://github.com/airbnb/javascript#standard-library--isfinite" + name: 'isFinite', + message: + 'Use Number.isFinite instead https://github.com/airbnb/javascript#standard-library--isfinite', }, { - "name": "isNaN", - "message": "Use Number.isNaN instead https://github.com/airbnb/javascript#standard-library--isnan" - } + name: 'isNaN', + message: + 'Use Number.isNaN instead https://github.com/airbnb/javascript#standard-library--isnan', + }, ], - "no-sequences": "error", - "no-undef-init": "error", - "no-unneeded-ternary": ["error", { "defaultAssignment": false }], - "no-useless-computed-key": "error", - "no-useless-return": "error", - "symbol-description": "error", - "yoda": "error", - "func-names": "warn", - "no-react-hook-namespace": "error" + 'no-sequences': 'error', + 'no-undef-init': 'error', + 'no-unneeded-ternary': ['error', { defaultAssignment: false }], + 'no-useless-computed-key': 'error', + 'no-useless-return': 'error', + 'symbol-description': 'error', + yoda: 'error', + 'func-names': 'warn', + 'no-react-hook-namespace': 'error', }, - "overrides": [ + overrides: [ { - "files": ["./src/api/**"], - "rules": { - "no-restricted-imports": [ - "off", + files: ['./src/api/**'], + rules: { + 'no-restricted-imports': [ + 'off', { - "patterns": ["~/api/**"] - } - ] - } - }, - { - "files": ["./src/__tests__/cypress/**/*.ts"], - "parserOptions": { - "project": ["./src/__tests__/cypress/tsconfig.json"] + patterns: ['~/api/**'], + }, + ], }, - "extends": [ - "eslint:recommended", - "plugin:react/recommended", - "plugin:@typescript-eslint/recommended", - "plugin:prettier/recommended", - "prettier", - "plugin:cypress/recommended" - ] }, { - "files": ["src/__tests__/cypress/**"], - "rules": { - "@typescript-eslint/consistent-type-imports": "error", - "no-restricted-imports": [ - "error", + files: ['./src/__tests__/cypress/**/*.ts'], + parserOptions: { + project: ['./src/__tests__/cypress/tsconfig.json'], + }, + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + 'prettier', + 'plugin:cypress/recommended', + ], + }, + { + files: ['src/__tests__/cypress/**'], + rules: { + '@typescript-eslint/consistent-type-imports': 'error', + 'no-restricted-imports': [ + 'error', { - "patterns": [ + patterns: [ { - "group": [ - "@patternfly/**" - ], - "message": "Cypress tests should only import mocks and types from outside the Cypress test directory." - } - ] - } - ] - } + group: ['@patternfly/**'], + message: + 'Cypress tests should only import mocks and types from outside the Cypress test directory.', + }, + ], + }, + ], + }, }, { files: ['**/*.{js,jsx,ts,tsx}'], rules: { 'no-react-hook-namespace': 'error', }, - } - ] + }, + ], }; diff --git a/workspaces/frontend/eslint-local-rules/no-react-hook-namespace.js b/workspaces/frontend/eslint-local-rules/no-react-hook-namespace.js index 031e312f..6542702a 100644 --- a/workspaces/frontend/eslint-local-rules/no-react-hook-namespace.js +++ b/workspaces/frontend/eslint-local-rules/no-react-hook-namespace.js @@ -11,17 +11,24 @@ module.exports = { }, create(context) { const hooks = new Set([ - 'useState', 'useEffect', 'useContext', 'useReducer', - 'useCallback', 'useMemo', 'useRef', 'useLayoutEffect', - 'useImperativeHandle', 'useDebugValue', 'useDeferredValue', - 'useTransition', 'useId', 'useSyncExternalStore', + 'useState', + 'useEffect', + 'useContext', + 'useReducer', + 'useCallback', + 'useMemo', + 'useRef', + 'useLayoutEffect', + 'useImperativeHandle', + 'useDebugValue', + 'useDeferredValue', + 'useTransition', + 'useId', + 'useSyncExternalStore', ]); return { MemberExpression(node) { - if ( - node.object?.name === 'React' && - hooks.has(node.property?.name) - ) { + if (node.object?.name === 'React' && hooks.has(node.property?.name)) { context.report({ node, messageId: 'avoidNamespaceHook', diff --git a/workspaces/frontend/package-lock.json b/workspaces/frontend/package-lock.json index f95aa15e..dea71525 100644 --- a/workspaces/frontend/package-lock.json +++ b/workspaces/frontend/package-lock.json @@ -15,6 +15,7 @@ "@patternfly/react-icons": "^6.2.0", "@patternfly/react-styles": "^6.2.0", "@patternfly/react-table": "^6.2.0", + "@patternfly/react-tokens": "^6.2.0", "@types/js-yaml": "^4.0.9", "date-fns": "^4.1.0", "js-yaml": "^4.1.0", @@ -4180,9 +4181,9 @@ } }, "node_modules/@patternfly/react-tokens": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-6.2.0.tgz", - "integrity": "sha512-KyzbsQYXTCxTmwkLlN4GdmTCNlOKnPUpY389loaC4/B0wHNq8Vw4OMIsAPVi4RSSvTaSxitlPAwt3xBTjNIzFA==" + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-6.2.2.tgz", + "integrity": "sha512-2GRWDPBTrcTlGNFc5NPJjrjEVU90RpgcGX/CIe2MplLgM32tpVIkeUtqIoJPLRk5GrbhyFuHJYRU+O93gU4o3Q==" }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", diff --git a/workspaces/frontend/package.json b/workspaces/frontend/package.json index e699465a..438f59d2 100644 --- a/workspaces/frontend/package.json +++ b/workspaces/frontend/package.json @@ -103,6 +103,7 @@ "@patternfly/react-icons": "^6.2.0", "@patternfly/react-styles": "^6.2.0", "@patternfly/react-table": "^6.2.0", + "@patternfly/react-tokens": "^6.2.0", "@types/js-yaml": "^4.0.9", "date-fns": "^4.1.0", "js-yaml": "^4.1.0", diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts index b784a2e2..ce5a4ba3 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts @@ -2,6 +2,7 @@ import { pageNotfound } from '~/__tests__/cypress/cypress/pages/pageNotFound'; import { home } from '~/__tests__/cypress/cypress/pages/home'; import { mockNamespaces } from '~/__mocks__/mockNamespaces'; import { mockBFFResponse } from '~/__mocks__/utils'; +import { mockWorkspace1 } from '~/shared/mock/mockNotebookServiceData'; describe('Application', () => { beforeEach(() => { @@ -9,8 +10,12 @@ describe('Application', () => { cy.intercept('GET', '/api/v1/namespaces', { body: mockBFFResponse(mockNamespaces), }).as('getNamespaces'); + cy.intercept('GET', `/api/v1/workspaces/${mockNamespaces[0].name}`, { + body: mockBFFResponse({ mockWorkspace1 }), + }).as('getWorkspaces'); cy.visit('/'); cy.wait('@getNamespaces'); + cy.wait('@getWorkspaces'); }); it('Page not found should render', () => { diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspace.mock.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspace.mock.ts index 62d0f005..0444639b 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspace.mock.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspace.mock.ts @@ -12,9 +12,9 @@ const generateMockWorkspace = ( podConfigDisplayName: string, pvcName: string, ): Workspace => { - const currentTime = Date.now(); - const lastActivityTime = currentTime - Math.floor(Math.random() * 1000000); - const lastUpdateTime = currentTime - Math.floor(Math.random() * 100000); + const pausedTime = new Date(2025, 0, 1).getTime(); + const lastActivityTime = new Date(2025, 0, 2).getTime(); + const lastUpdateTime = new Date(2025, 0, 3).getTime(); return { name, @@ -22,7 +22,7 @@ const generateMockWorkspace = ( workspaceKind: { name: 'jupyterlab' } as WorkspaceKindInfo, deferUpdates: paused, paused, - pausedTime: paused ? currentTime - Math.floor(Math.random() * 1000000) : 0, + pausedTime, pendingRestart: Math.random() < 0.5, //to generate randomly True/False value state, stateMessage: diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/WorkspaceDetailsActivity.cy.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/WorkspaceDetailsActivity.cy.ts index 06307a5e..e268984d 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/WorkspaceDetailsActivity.cy.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/WorkspaceDetailsActivity.cy.ts @@ -1,6 +1,5 @@ import { mockBFFResponse } from '~/__mocks__/utils'; import { mockWorkspaces } from '~/__tests__/cypress/cypress/tests/mocked/workspace.mock'; -import { formatTimestamp } from '~/shared/utilities/WorkspaceUtils'; describe('WorkspaceDetailsActivity Component', () => { beforeEach(() => { @@ -19,22 +18,16 @@ describe('WorkspaceDetailsActivity Component', () => { throw new Error('Intercepted response is undefined or empty'); } const workspace = interception.response.body.data[0]; - cy.findByTestId('action-view-details').click(); + cy.findByTestId('action-viewDetails').click(); cy.findByTestId('activityTab').click(); cy.findByTestId('lastActivity') .invoke('text') .then((text) => { console.log('Rendered lastActivity:', text); }); - cy.findByTestId('lastActivity').should( - 'have.text', - formatTimestamp(workspace.activity.lastActivity), - ); - cy.findByTestId('lastUpdate').should( - 'have.text', - formatTimestamp(workspace.activity.lastUpdate), - ); - cy.findByTestId('pauseTime').should('have.text', formatTimestamp(workspace.pausedTime)); + cy.findByTestId('pauseTime').should('have.text', 'Jan 1, 2025, 12:00:00 AM'); + cy.findByTestId('lastActivity').should('have.text', 'Jan 2, 2025, 12:00:00 AM'); + cy.findByTestId('lastUpdate').should('have.text', 'Jan 3, 2025, 12:00:00 AM'); cy.findByTestId('pendingRestart').should( 'have.text', workspace.pendingRestart ? 'Yes' : 'No', diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/filterWorkspacesTest.cy.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/filterWorkspacesTest.cy.ts index 1a2a5357..9b468b13 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/filterWorkspacesTest.cy.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/filterWorkspacesTest.cy.ts @@ -3,9 +3,9 @@ import { mockWorkspaces } from '~/__mocks__/mockWorkspaces'; import { mockBFFResponse } from '~/__mocks__/utils'; import { home } from '~/__tests__/cypress/cypress/pages/home'; -const useFilter = (filterName: string, searchValue: string) => { +const useFilter = (filterKey: string, filterName: string, searchValue: string) => { cy.get("[id$='filter-workspaces-dropdown']").click(); - cy.get(`[id$='filter-workspaces-dropdown-${filterName}']`).click(); + cy.get(`[id$='filter-workspaces-dropdown-${filterKey}']`).click(); cy.get("[data-testid='filter-workspaces-search-input']").type(searchValue); cy.get("[class$='pf-v6-c-toolbar__group']").contains(filterName); cy.get("[class$='pf-v6-c-toolbar__group']").contains(searchValue); @@ -23,7 +23,7 @@ describe('Application', () => { }); it('filter rows with single filter', () => { home.visit(); - useFilter('Name', 'My'); + useFilter('name', 'Name', 'My'); cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 2); cy.get("[id$='workspaces-table-row-1']").contains('My First Jupyter Notebook'); cy.get("[id$='workspaces-table-row-2']").contains('My Second Jupyter Notebook'); @@ -31,16 +31,16 @@ describe('Application', () => { it('filter rows with multiple filters', () => { home.visit(); - useFilter('Name', 'My'); - useFilter('Pod Config', 'Tiny'); + useFilter('name', 'Name', 'My'); + useFilter('podConfig', 'Pod Config', 'Tiny'); cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 1); cy.get("[id$='workspaces-table-row-1']").contains('My First Jupyter Notebook'); }); it('filter rows with multiple filters and remove one', () => { home.visit(); - useFilter('Name', 'My'); - useFilter('Pod Config', 'Tiny'); + useFilter('name', 'Name', 'My'); + useFilter('podConfig', 'Pod Config', 'Tiny'); cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 1); cy.get("[id$='workspaces-table-row-1']").contains('My First Jupyter Notebook'); cy.get("[class$='pf-v6-c-label-group__close']").eq(1).click(); @@ -52,8 +52,8 @@ describe('Application', () => { it('filter rows with multiple filters and remove all', () => { home.visit(); - useFilter('Name', 'My'); - useFilter('Pod Config', 'Tiny'); + useFilter('name', 'Name', 'My'); + useFilter('podConfig', 'Pod Config', 'Tiny'); cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 1); cy.get("[id$='workspaces-table-row-1']").contains('My First Jupyter Notebook'); cy.get('*').contains('Clear all filters').click(); diff --git a/workspaces/frontend/src/app/AppRoutes.tsx b/workspaces/frontend/src/app/AppRoutes.tsx index e64f1878..f87ad52c 100644 --- a/workspaces/frontend/src/app/AppRoutes.tsx +++ b/workspaces/frontend/src/app/AppRoutes.tsx @@ -1,12 +1,13 @@ import React from 'react'; import { Route, Routes, Navigate } from 'react-router-dom'; import { AppRoutePaths } from '~/app/routes'; +import { WorkspaceKindSummaryWrapper } from '~/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryWrapper'; import { WorkspaceForm } from '~/app/pages/Workspaces/Form/WorkspaceForm'; -import { NotFound } from './pages/notFound/NotFound'; import { Debug } from './pages/Debug/Debug'; -import { Workspaces } from './pages/Workspaces/Workspaces'; -import '~/shared/style/MUI-theme.scss'; +import { NotFound } from './pages/notFound/NotFound'; import { WorkspaceKinds } from './pages/WorkspaceKinds/WorkspaceKinds'; +import { WorkspacesWrapper } from './pages/Workspaces/WorkspacesWrapper'; +import '~/shared/style/MUI-theme.scss'; export const isNavDataGroup = (navItem: NavDataItem): navItem is NavDataGroup => 'children' in navItem; @@ -62,7 +63,8 @@ const AppRoutes: React.FC = () => { } /> } /> - } /> + } /> + } /> } /> } /> } /> diff --git a/workspaces/frontend/src/app/components/LoadError.tsx b/workspaces/frontend/src/app/components/LoadError.tsx new file mode 100644 index 00000000..60114e99 --- /dev/null +++ b/workspaces/frontend/src/app/components/LoadError.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { Alert, Bullseye } from '@patternfly/react-core'; + +interface LoadErrorProps { + error: Error; +} + +// TODO: simple LoadError component -- we should improve this later + +export const LoadError: React.FC = ({ error }) => ( + + + Error details: {error.message} + + +); diff --git a/workspaces/frontend/src/app/components/LoadingSpinner.tsx b/workspaces/frontend/src/app/components/LoadingSpinner.tsx new file mode 100644 index 00000000..9750a392 --- /dev/null +++ b/workspaces/frontend/src/app/components/LoadingSpinner.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; +import { Bullseye, Spinner } from '@patternfly/react-core'; + +// TODO: simple LoadingSpinner component -- we should improve this later + +export const LoadingSpinner: React.FC = () => ( + + + +); diff --git a/workspaces/frontend/src/app/components/WorkspaceTable.tsx b/workspaces/frontend/src/app/components/WorkspaceTable.tsx new file mode 100644 index 00000000..20e20c6e --- /dev/null +++ b/workspaces/frontend/src/app/components/WorkspaceTable.tsx @@ -0,0 +1,587 @@ +import React, { useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react'; +import { + PageSection, + TimestampTooltipVariant, + Timestamp, + Label, + PaginationVariant, + Pagination, + Content, + Brand, + Tooltip, + Bullseye, + Button, +} from '@patternfly/react-core'; +import { + Table, + Thead, + Tr, + Th, + Tbody, + Td, + ThProps, + ActionsColumn, + IActions, +} from '@patternfly/react-table'; +import { + InfoCircleIcon, + ExclamationTriangleIcon, + TimesCircleIcon, + QuestionCircleIcon, + CodeIcon, +} from '@patternfly/react-icons'; +import { formatDistanceToNow } from 'date-fns'; +import { Workspace, WorkspaceState } from '~/shared/api/backendApiTypes'; +import { + DataFieldKey, + defineDataFields, + FilterableDataFieldKey, + SortableDataFieldKey, +} from '~/app/filterableDataHelper'; +import { ExpandedWorkspaceRow } from '~/app/pages/Workspaces/ExpandedWorkspaceRow'; +import { useTypedNavigate } from '~/app/routerHelper'; +import { + buildKindLogoDictionary, + buildWorkspaceRedirectStatus, +} from '~/app/actions/WorkspaceKindsActions'; +import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds'; +import { WorkspaceConnectAction } from '~/app/pages/Workspaces/WorkspaceConnectAction'; +import CustomEmptyState from '~/shared/components/CustomEmptyState'; +import Filter, { FilteredColumn, FilterRef } from '~/shared/components/Filter'; +import { + formatResourceFromWorkspace, + formatWorkspaceIdleState, +} from '~/shared/utilities/WorkspaceUtils'; + +const { + fields: wsTableColumns, + keyArray: wsTableColumnKeyArray, + sortableKeyArray: sortableWsTableColumnKeyArray, + filterableKeyArray: filterableWsTableColumnKeyArray, +} = defineDataFields({ + redirectStatus: { label: 'Redirect Status', isFilterable: false, isSortable: false }, + name: { label: 'Name', isFilterable: true, isSortable: true }, + kind: { label: 'Kind', isFilterable: true, isSortable: true }, + namespace: { label: 'Namespace', isFilterable: true, isSortable: true }, + image: { label: 'Image', isFilterable: true, isSortable: true }, + podConfig: { label: 'Pod Config', isFilterable: true, isSortable: true }, + state: { label: 'State', isFilterable: true, isSortable: true }, + homeVol: { label: 'Home Vol', isFilterable: true, isSortable: true }, + cpu: { label: 'CPU', isFilterable: false, isSortable: true }, + ram: { label: 'Memory', isFilterable: false, isSortable: true }, + gpu: { label: 'GPU', isFilterable: true, isSortable: true }, + idleGpu: { label: 'Idle GPU', isFilterable: true, isSortable: true }, + lastActivity: { label: 'Last Activity', isFilterable: false, isSortable: true }, + connect: { label: '', isFilterable: false, isSortable: false }, + actions: { label: '', isFilterable: false, isSortable: false }, +}); + +export type WorkspaceTableColumnKeys = DataFieldKey; +type WorkspaceTableFilterableColumnKeys = FilterableDataFieldKey; +type WorkspaceTableSortableColumnKeys = SortableDataFieldKey; +export type WorkspaceTableFilteredColumn = FilteredColumn; + +interface WorkspaceTableProps { + workspaces: Workspace[]; + canCreateWorkspaces?: boolean; + canExpandRows?: boolean; + initialFilters?: WorkspaceTableFilteredColumn[]; + hiddenColumns?: WorkspaceTableColumnKeys[]; + rowActions?: (workspace: Workspace) => IActions; +} + +export interface WorkspaceTableRef { + addFilter: (filter: WorkspaceTableFilteredColumn) => void; +} + +const WorkspaceTable = React.forwardRef( + ( + { + workspaces, + canCreateWorkspaces = true, + canExpandRows = true, + initialFilters = [], + hiddenColumns = [], + rowActions = () => [], + }, + ref, + ) => { + const [workspaceKinds] = useWorkspaceKinds(); + const [expandedWorkspacesNames, setExpandedWorkspacesNames] = useState([]); + const [filters, setFilters] = useState(initialFilters); + const [activeSortColumnKey, setActiveSortColumnKey] = + useState(null); + const [activeSortDirection, setActiveSortDirection] = useState<'asc' | 'desc' | null>(null); + const [page, setPage] = useState(1); + const [perPage, setPerPage] = useState(10); + + const navigate = useTypedNavigate(); + const filterRef = useRef(null); + const kindLogoDict = buildKindLogoDictionary(workspaceKinds); + const workspaceRedirectStatus = buildWorkspaceRedirectStatus(workspaceKinds); + + const visibleColumnKeys: WorkspaceTableColumnKeys[] = useMemo( + () => + hiddenColumns.length + ? wsTableColumnKeyArray.filter((col) => !hiddenColumns.includes(col)) + : wsTableColumnKeyArray, + [hiddenColumns], + ); + + const visibleSortableColumnKeys: WorkspaceTableSortableColumnKeys[] = useMemo( + () => sortableWsTableColumnKeyArray.filter((col) => visibleColumnKeys.includes(col)), + [visibleColumnKeys], + ); + + const visibleFilterableColumnKeys: WorkspaceTableFilterableColumnKeys[] = useMemo( + () => filterableWsTableColumnKeyArray.filter((col) => visibleColumnKeys.includes(col)), + [visibleColumnKeys], + ); + + const visibleFilterableColumnMap = useMemo( + () => + Object.fromEntries( + visibleFilterableColumnKeys.map((key) => [key, wsTableColumns[key].label]), + ) as Record, + [visibleFilterableColumnKeys], + ); + + useImperativeHandle(ref, () => ({ + addFilter: (newFilter: WorkspaceTableFilteredColumn) => { + if (!visibleFilterableColumnKeys.includes(newFilter.columnKey)) { + return; + } + + setFilters((prev) => { + const existingIndex = prev.findIndex((f) => f.columnKey === newFilter.columnKey); + if (existingIndex !== -1) { + return prev.map((f, i) => (i === existingIndex ? newFilter : f)); + } + return [...prev, newFilter]; + }); + }, + })); + + const createWorkspace = useCallback(() => { + navigate('workspaceCreate'); + }, [navigate]); + + const setWorkspaceExpanded = (workspace: Workspace, isExpanding = true) => + setExpandedWorkspacesNames((prevExpanded) => { + const newExpandedWorkspacesNames = prevExpanded.filter( + (wsName) => wsName !== workspace.name, + ); + return isExpanding + ? [...newExpandedWorkspacesNames, workspace.name] + : newExpandedWorkspacesNames; + }); + + const isWorkspaceExpanded = (workspace: Workspace) => + expandedWorkspacesNames.includes(workspace.name); + + const filteredWorkspaces = useMemo(() => { + if (workspaces.length === 0) { + return []; + } + + return filters.reduce((result, filter) => { + let searchValueInput: RegExp; + try { + searchValueInput = new RegExp(filter.value, 'i'); + } catch { + searchValueInput = new RegExp(filter.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'); + } + + return result.filter((ws) => { + switch (filter.columnKey as WorkspaceTableFilterableColumnKeys) { + case 'name': + return ws.name.match(searchValueInput); + case 'kind': + return ws.workspaceKind.name.match(searchValueInput); + case 'namespace': + return ws.namespace.match(searchValueInput); + case 'image': + return ws.podTemplate.options.imageConfig.current.displayName.match(searchValueInput); + case 'podConfig': + return ws.podTemplate.options.podConfig.current.displayName.match(searchValueInput); + case 'state': + return ws.state.match(searchValueInput); + case 'gpu': + return formatResourceFromWorkspace(ws, 'gpu').match(searchValueInput); + case 'idleGpu': + return formatWorkspaceIdleState(ws).match(searchValueInput); + case 'homeVol': + return ws.podTemplate.volumes.home?.mountPath.match(searchValueInput); + default: + return true; + } + }); + }, workspaces); + }, [workspaces, filters]); + + // Column sorting + + const getSortableRowValues = ( + workspace: Workspace, + ): Record => ({ + name: workspace.name, + kind: workspace.workspaceKind.name, + namespace: workspace.namespace, + image: workspace.podTemplate.options.imageConfig.current.displayName, + podConfig: workspace.podTemplate.options.podConfig.current.displayName, + state: workspace.state, + homeVol: workspace.podTemplate.volumes.home?.pvcName ?? '', + cpu: formatResourceFromWorkspace(workspace, 'cpu'), + ram: formatResourceFromWorkspace(workspace, 'memory'), + gpu: formatResourceFromWorkspace(workspace, 'gpu'), + idleGpu: formatWorkspaceIdleState(workspace), + lastActivity: workspace.activity.lastActivity, + }); + + const sortedWorkspaces = useMemo(() => { + if (activeSortColumnKey === null) { + return filteredWorkspaces; + } + + return [...filteredWorkspaces].sort((a, b) => { + const aValue = getSortableRowValues(a)[activeSortColumnKey]; + const bValue = getSortableRowValues(b)[activeSortColumnKey]; + + if (typeof aValue === 'number' && typeof bValue === 'number') { + // Numeric sort + return activeSortDirection === 'asc' ? aValue - bValue : bValue - aValue; + } + // String sort + return activeSortDirection === 'asc' + ? String(aValue).localeCompare(String(bValue)) + : String(bValue).localeCompare(String(aValue)); + }); + }, [filteredWorkspaces, activeSortColumnKey, activeSortDirection]); + + const getSortParams = (columnKey: WorkspaceTableColumnKeys): ThProps['sort'] => { + const sortableColumnKey = columnKey as WorkspaceTableSortableColumnKeys; + if (!visibleSortableColumnKeys.includes(sortableColumnKey)) { + return undefined; + } + const activeSortColumnIndex = activeSortColumnKey + ? visibleSortableColumnKeys.indexOf(activeSortColumnKey) + : undefined; + return { + sortBy: { + index: activeSortColumnIndex, + direction: activeSortDirection || 'asc', + defaultDirection: 'asc', // starting sort direction when first sorting a column. Defaults to 'asc' + }, + onSort: (_event, _index, direction) => { + setActiveSortColumnKey(sortableColumnKey); + setActiveSortDirection(direction); + }, + columnIndex: visibleSortableColumnKeys.indexOf(sortableColumnKey), + }; + }; + + const extractStateColor = (state: WorkspaceState) => { + switch (state) { + case WorkspaceState.WorkspaceStateRunning: + return 'green'; + case WorkspaceState.WorkspaceStatePending: + return 'orange'; + case WorkspaceState.WorkspaceStateTerminating: + return 'yellow'; + case WorkspaceState.WorkspaceStateError: + return 'red'; + case WorkspaceState.WorkspaceStatePaused: + return 'purple'; + case WorkspaceState.WorkspaceStateUnknown: + default: + return 'grey'; + } + }; + + // Redirect Status Icons + + const getRedirectStatusIcon = (level: string | undefined, message: string) => { + switch (level) { + case 'Info': + return ( + + + ); + case 'Warning': + return ( + + + ); + case 'Danger': + return ( + + + ); + case undefined: + return ( + + + ); + default: + return ( + + + ); + } + }; + + // Pagination + + const onSetPage = ( + _event: React.MouseEvent | React.KeyboardEvent | MouseEvent, + newPage: number, + ) => { + setPage(newPage); + }; + + const onPerPageSelect = ( + _event: React.MouseEvent | React.KeyboardEvent | MouseEvent, + newPerPage: number, + newPage: number, + ) => { + setPerPage(newPerPage); + setPage(newPage); + }; + + return ( + + + + Create Workspace + + ) + } + /> + + + + + {canExpandRows && + ))} + + + {sortedWorkspaces.length > 0 && + sortedWorkspaces.map((workspace, rowIndex) => ( + + + {canExpandRows && ( + + ); + case 'name': + return ( + + ); + case 'kind': + return ( + + ); + case 'namespace': + return ( + + ); + case 'image': + return ( + + ); + case 'podConfig': + return ( + + ); + case 'state': + return ( + + ); + case 'homeVol': + return ( + + ); + case 'cpu': + return ( + + ); + case 'ram': + return ( + + ); + case 'gpu': + return ( + + ); + case 'idleGpu': + return ( + + ); + case 'lastActivity': + return ( + + ); + case 'connect': + return ( + + ); + case 'actions': + return ( + + ); + default: + return null; + } + })} + + {isWorkspaceExpanded(workspace) && ( + + )} + + ))} + {sortedWorkspaces.length === 0 && ( + + + + + + )} +
} + {visibleColumnKeys.map((columnKey) => ( + + {wsTableColumns[columnKey].label} +
+ setWorkspaceExpanded(workspace, !isWorkspaceExpanded(workspace)), + }} + /> + )} + {visibleColumnKeys.map((columnKey) => { + switch (columnKey) { + case 'redirectStatus': + return ( + + {workspaceRedirectStatus[workspace.workspaceKind.name] + ? getRedirectStatusIcon( + workspaceRedirectStatus[workspace.workspaceKind.name]?.message + ?.level, + workspaceRedirectStatus[workspace.workspaceKind.name]?.message + ?.text || 'No API response available', + ) + : getRedirectStatusIcon(undefined, 'No API response available')} + + {workspace.name} + + {kindLogoDict[workspace.workspaceKind.name] ? ( + + + + ) : ( + + + + )} + + {workspace.namespace} + + {workspace.podTemplate.options.imageConfig.current.displayName} + + {workspace.podTemplate.options.podConfig.current.displayName} + + + + {workspace.podTemplate.volumes.home?.pvcName ?? ''} + + {formatResourceFromWorkspace(workspace, 'cpu')} + + {formatResourceFromWorkspace(workspace, 'memory')} + + {formatResourceFromWorkspace(workspace, 'gpu')} + + {formatWorkspaceIdleState(workspace)} + + + {formatDistanceToNow(new Date(workspace.activity.lastActivity), { + addSuffix: true, + })} + + + + + ({ + ...action, + 'data-testid': `action-${action.id || ''}`, + }))} + /> +
+ + filterRef.current?.clearAll()} /> + +
+ +
+ ); + }, +); + +WorkspaceTable.displayName = 'WorkspaceTable'; + +export default WorkspaceTable; diff --git a/workspaces/frontend/src/app/const.ts b/workspaces/frontend/src/app/const.ts index 6cdec1dd..d0661b7f 100644 --- a/workspaces/frontend/src/app/const.ts +++ b/workspaces/frontend/src/app/const.ts @@ -10,3 +10,5 @@ export const isMUITheme = (): boolean => STYLE_THEME === Theme.MUI; const STYLE_THEME = process.env.STYLE_THEME || Theme.MUI; export const LOGO_LIGHT = process.env.LOGO || 'logo.svg'; + +export const DEFAULT_POLLING_RATE_MS = 10000; diff --git a/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx b/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx new file mode 100644 index 00000000..7a01737e --- /dev/null +++ b/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx @@ -0,0 +1,224 @@ +import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { Drawer, DrawerContent, DrawerContentBody } from '@patternfly/react-core'; +import { useNamespaceContext } from '~/app/context/NamespaceContextProvider'; +import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; +import { WorkspaceDetails } from '~/app/pages/Workspaces/Details/WorkspaceDetails'; +import { useTypedNavigate } from '~/app/routerHelper'; +import { Workspace } from '~/shared/api/backendApiTypes'; +import DeleteModal from '~/shared/components/DeleteModal'; +import { WorkspaceStartActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal'; +import { WorkspaceRestartActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRestartActionModal'; +import { WorkspaceStopActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal'; + +export enum ActionType { + ViewDetails = 'ViewDetails', + Edit = 'Edit', + Delete = 'Delete', + Start = 'Start', + Restart = 'Restart', + Stop = 'Stop', +} + +export interface WorkspaceAction { + action: ActionType; + workspace: Workspace; + onActionDone?: () => void; +} + +type RequestAction = (args: Pick) => void; + +export type WorkspaceActionsContextType = { + requestViewDetailsAction: RequestAction; + requestEditAction: RequestAction; + requestDeleteAction: RequestAction; + requestStartAction: RequestAction; + requestRestartAction: RequestAction; + requestStopAction: RequestAction; +}; + +export const WorkspaceActionsContext = React.createContext( + undefined, +); + +export const useWorkspaceActionsContext = (): WorkspaceActionsContextType => { + const context = useContext(WorkspaceActionsContext); + if (!context) { + throw new Error( + 'useWorkspaceActionsContext must be used within a WorkspaceActionsContextProvider', + ); + } + return context; +}; + +interface WorkspaceActionsContextProviderProps { + children: React.ReactNode; +} + +export const WorkspaceActionsContextProvider: React.FC = ({ + children, +}) => { + const navigate = useTypedNavigate(); + const { api } = useNotebookAPI(); + const { selectedNamespace } = useNamespaceContext(); + const [activeWsAction, setActiveWsAction] = useState(null); + + const drawerContent = ( + <> + {activeWsAction && ( + setActiveWsAction(null)} + onDeleteClick={() => requestDeleteAction({ workspace: activeWsAction.workspace })} + // TODO: Uncomment when edit action is fully supported + // onEditClick={() => executeEditAction()} + /> + )} + + ); + + const onCloseActionAlertDialog = useCallback(() => { + setActiveWsAction(null); + }, []); + + const createActionRequester = + (actionType: ActionType) => (args: { workspace: Workspace; onActionDone?: () => void }) => + setActiveWsAction({ action: actionType, ...args }); + + const requestViewDetailsAction = createActionRequester(ActionType.ViewDetails); + const requestEditAction = createActionRequester(ActionType.Edit); + const requestDeleteAction = createActionRequester(ActionType.Delete); + const requestStartAction = createActionRequester(ActionType.Start); + const requestRestartAction = createActionRequester(ActionType.Restart); + const requestStopAction = createActionRequester(ActionType.Stop); + + const executeEditAction = useCallback(() => { + if (!activeWsAction || activeWsAction.action !== ActionType.Edit) { + return; + } + navigate('workspaceEdit', { + state: { + namespace: activeWsAction.workspace.namespace, + workspaceName: activeWsAction.workspace.name, + }, + }); + }, [navigate, activeWsAction]); + + const executeDeleteAction = useCallback(async () => { + if (!activeWsAction || activeWsAction.action !== ActionType.Delete) { + return; + } + + try { + await api.deleteWorkspace({}, selectedNamespace, activeWsAction.workspace.name); + // TODO: alert user about success + console.info(`Workspace '${activeWsAction.workspace.name}' deleted successfully`); + activeWsAction.onActionDone?.(); + } catch (err) { + // TODO: alert user about error + console.error(`Error deleting workspace '${activeWsAction.workspace.name}': ${err}`); + } + }, [api, selectedNamespace, activeWsAction]); + + useEffect(() => { + if (!activeWsAction) { + return; + } + + const { action } = activeWsAction; + switch (action) { + case ActionType.Edit: + executeEditAction(); + break; + case ActionType.Delete: + case ActionType.ViewDetails: + case ActionType.Start: + case ActionType.Restart: + case ActionType.Stop: + break; + default: { + const value: never = action; + console.error('Unreachable code', value); + } + } + }, [activeWsAction, executeEditAction]); + + const contextValue = useMemo( + () => ({ + requestViewDetailsAction, + requestEditAction, + requestDeleteAction, + requestStartAction, + requestRestartAction, + requestStopAction, + }), + [ + requestViewDetailsAction, + requestEditAction, + requestDeleteAction, + requestStartAction, + requestRestartAction, + requestStopAction, + ], + ); + + return ( + + + + + {children} + {activeWsAction && ( + <> + {activeWsAction.action === ActionType.Start && ( + + api.startWorkspace({}, selectedNamespace, activeWsAction.workspace.name) + } + onActionDone={activeWsAction.onActionDone} + onUpdateAndStart={async () => { + // TODO: implement update and stop + }} + /> + )} + {activeWsAction.action === ActionType.Restart && ( + + )} + {activeWsAction.action === ActionType.Stop && ( + + api.pauseWorkspace({}, selectedNamespace, activeWsAction.workspace.name) + } + onActionDone={activeWsAction.onActionDone} + onUpdateAndStop={async () => { + // TODO: implement update and stop + }} + /> + )} + {activeWsAction.action === ActionType.Delete && ( + setActiveWsAction(null)} + onDelete={async () => executeDeleteAction()} + /> + )} + + )} + + + + + ); +}; diff --git a/workspaces/frontend/src/app/filterableDataHelper.ts b/workspaces/frontend/src/app/filterableDataHelper.ts new file mode 100644 index 00000000..5f3c3704 --- /dev/null +++ b/workspaces/frontend/src/app/filterableDataHelper.ts @@ -0,0 +1,43 @@ +export interface DataFieldDefinition { + label: string; + isSortable: boolean; + isFilterable: boolean; +} + +export type FilterableDataFieldKey> = { + [K in keyof T]: T[K]['isFilterable'] extends true ? K : never; +}[keyof T]; + +export type SortableDataFieldKey> = { + [K in keyof T]: T[K]['isSortable'] extends true ? K : never; +}[keyof T]; + +export type DataFieldKey = keyof T; + +export function defineDataFields>( + fields: T, +): { + fields: T; + keyArray: (keyof T)[]; + sortableKeyArray: SortableDataFieldKey[]; + filterableKeyArray: FilterableDataFieldKey[]; + filterableLabelMap: Record, string>; +} { + type Key = keyof T; + + const keyArray = Object.keys(fields) as Key[]; + + const sortableKeyArray = keyArray.filter( + (key): key is SortableDataFieldKey => (fields[key] as DataFieldDefinition).isSortable, + ); + + const filterableKeyArray = keyArray.filter( + (key): key is FilterableDataFieldKey => (fields[key] as DataFieldDefinition).isFilterable, + ); + + const filterableLabelMap = Object.fromEntries( + filterableKeyArray.map((key) => [key, fields[key].label]), + ) as Record, string>; + + return { fields, keyArray, sortableKeyArray, filterableKeyArray, filterableLabelMap }; +} diff --git a/workspaces/frontend/src/app/hooks/__tests__/usePolling.spec.tsx b/workspaces/frontend/src/app/hooks/__tests__/usePolling.spec.tsx new file mode 100644 index 00000000..1f510224 --- /dev/null +++ b/workspaces/frontend/src/app/hooks/__tests__/usePolling.spec.tsx @@ -0,0 +1,47 @@ +import { act } from 'react-dom/test-utils'; +import { renderHook } from '~/__tests__/unit/testUtils/hooks'; +import { usePolling } from '~/app/hooks/usePolling'; + +jest.useFakeTimers(); + +describe('usePolling', () => { + it('should call the callback at the specified interval', () => { + const callback = jest.fn(); + + renderHook(() => usePolling(callback, 1000)); + + expect(callback).not.toHaveBeenCalled(); + + act(() => { + jest.advanceTimersByTime(1000); + }); + + expect(callback).toHaveBeenCalledTimes(1); + + act(() => { + jest.advanceTimersByTime(2000); + }); + + expect(callback).toHaveBeenCalledTimes(3); + }); + + it('should clean up on unmount', () => { + const callback = jest.fn(); + + const { unmount } = renderHook(() => usePolling(callback, 500)); + + act(() => { + jest.advanceTimersByTime(1000); + }); + + expect(callback).toHaveBeenCalledTimes(2); + + unmount(); + + act(() => { + jest.advanceTimersByTime(1000); + }); + + expect(callback).toHaveBeenCalledTimes(2); + }); +}); diff --git a/workspaces/frontend/src/app/hooks/usePolling.tsx b/workspaces/frontend/src/app/hooks/usePolling.tsx new file mode 100644 index 00000000..e5507fa8 --- /dev/null +++ b/workspaces/frontend/src/app/hooks/usePolling.tsx @@ -0,0 +1,8 @@ +import { useEffect } from 'react'; + +export function usePolling(callback: () => void, intervalMs: number): void { + useEffect(() => { + const interval = setInterval(callback, intervalMs); + return () => clearInterval(interval); + }, [callback, intervalMs]); +} diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts b/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts index be25d8e4..ccc1fbca 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts @@ -5,6 +5,7 @@ import { WorkspaceCountPerOption } from '~/app/types'; export type WorkspaceCountPerKind = Record; +// TODO: This hook is temporary; we should get counts from the API directly export const useWorkspaceCountPerKind = (): WorkspaceCountPerKind => { const { api } = useNotebookAPI(); diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts b/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts index 85b00a8b..4a741b7a 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts @@ -23,6 +23,7 @@ const useWorkspaceFormData = (args: { namespace: string | undefined; workspaceName: string | undefined; }): FetchState => { + const { namespace, workspaceName } = args; const { api, apiAvailable } = useNotebookAPI(); const call = useCallback>( @@ -31,11 +32,11 @@ const useWorkspaceFormData = (args: { throw new Error('API not yet available'); } - if (!args.namespace || !args.workspaceName) { + if (!namespace || !workspaceName) { return EMPTY_FORM_DATA; } - const workspace = await api.getWorkspace(opts, args.namespace, args.workspaceName); + const workspace = await api.getWorkspace(opts, namespace, workspaceName); const workspaceKind = await api.getWorkspaceKind(opts, workspace.workspaceKind.name); const imageConfig = workspace.podTemplate.options.imageConfig.current; const podConfig = workspace.podTemplate.options.podConfig.current; @@ -65,7 +66,7 @@ const useWorkspaceFormData = (args: { }, }; }, - [api, apiAvailable, args.namespace, args.workspaceName], + [api, apiAvailable, namespace, workspaceName], ); return useFetchState(call, EMPTY_FORM_DATA); diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceRowActions.ts b/workspaces/frontend/src/app/hooks/useWorkspaceRowActions.ts new file mode 100644 index 00000000..06190656 --- /dev/null +++ b/workspaces/frontend/src/app/hooks/useWorkspaceRowActions.ts @@ -0,0 +1,87 @@ +import { useCallback } from 'react'; +import { IActions } from '@patternfly/react-table'; +import { Workspace } from '~/shared/api/backendApiTypes'; +import { useWorkspaceActionsContext, WorkspaceAction } from '~/app/context/WorkspaceActionsContext'; + +export type WorkspaceRowActionId = 'viewDetails' | 'edit' | 'delete' | 'start' | 'stop' | 'restart'; + +interface WorkspaceRowAction { + id: WorkspaceRowActionId; + onActionDone?: WorkspaceAction['onActionDone']; + isVisible?: boolean | ((workspace: Workspace) => boolean); +} + +type WorkspaceRowActionItem = WorkspaceRowAction | { id: 'separator' }; + +export const useWorkspaceRowActions = ( + actionsToInclude: WorkspaceRowActionItem[], +): ((workspace: Workspace) => IActions) => { + const actionsContext = useWorkspaceActionsContext(); + + return useCallback( + (workspace: Workspace): IActions => { + const actions: IActions = []; + + for (const item of actionsToInclude) { + if (item.id === 'separator') { + actions.push({ isSeparator: true }); + continue; + } + + if ( + item.isVisible === false || + (typeof item.isVisible === 'function' && !item.isVisible(workspace)) + ) { + continue; + } + + actions.push(buildAction(item.id, item.onActionDone, workspace, actionsContext)); + } + + return actions; + }, + [actionsContext, actionsToInclude], + ); +}; + +function buildAction( + id: WorkspaceRowActionId, + onActionDone: WorkspaceAction['onActionDone'] | undefined, + workspace: Workspace, + actionsContext: ReturnType, +): IActions[number] { + const map: Record IActions[number]> = { + viewDetails: () => ({ + id, + title: 'View Details', + onClick: () => actionsContext.requestViewDetailsAction({ workspace }), + }), + edit: () => ({ + id, + title: 'Edit', + onClick: () => actionsContext.requestEditAction({ workspace }), + }), + delete: () => ({ + id, + title: 'Delete', + onClick: () => actionsContext.requestDeleteAction({ workspace, onActionDone }), + }), + start: () => ({ + id, + title: 'Start', + onClick: () => actionsContext.requestStartAction({ workspace, onActionDone }), + }), + stop: () => ({ + id, + title: 'Stop', + onClick: () => actionsContext.requestStopAction({ workspace, onActionDone }), + }), + restart: () => ({ + id, + title: 'Restart', + onClick: () => actionsContext.requestRestartAction({ workspace, onActionDone }), + }), + }; + + return map[id](); +} diff --git a/workspaces/frontend/src/app/hooks/useWorkspaces.ts b/workspaces/frontend/src/app/hooks/useWorkspaces.ts index 00df848d..998f586c 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaces.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaces.ts @@ -6,10 +6,10 @@ import useFetchState, { import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { Workspace } from '~/shared/api/backendApiTypes'; -const useWorkspaces = (namespace: string): FetchState => { +export const useWorkspacesByNamespace = (namespace: string): FetchState => { const { api, apiAvailable } = useNotebookAPI(); - const call = useCallback>( + const call = useCallback>( (opts) => { if (!apiAvailable) { return Promise.reject(new Error('API not yet available')); @@ -20,7 +20,42 @@ const useWorkspaces = (namespace: string): FetchState => { [api, apiAvailable, namespace], ); - return useFetchState(call, null); + return useFetchState(call, []); }; -export default useWorkspaces; +export const useWorkspacesByKind = (args: { + kind: string; + namespace?: string; + imageId?: string; + podConfigId?: string; +}): FetchState => { + const { kind, namespace, imageId, podConfigId } = args; + const { api, apiAvailable } = useNotebookAPI(); + const call = useCallback>( + async (opts) => { + if (!apiAvailable) { + throw new Error('API not yet available'); + } + if (!kind) { + throw new Error('Workspace kind is required'); + } + + const workspaces = await api.listAllWorkspaces(opts); + + return workspaces.filter((workspace) => { + const matchesKind = workspace.workspaceKind.name === kind; + const matchesNamespace = namespace ? workspace.namespace === namespace : true; + const matchesImage = imageId + ? workspace.podTemplate.options.imageConfig.current.id === imageId + : true; + const matchesPodConfig = podConfigId + ? workspace.podTemplate.options.podConfig.current.id === podConfigId + : true; + + return matchesKind && matchesNamespace && matchesImage && matchesPodConfig; + }); + }, + [apiAvailable, api, kind, namespace, imageId, podConfigId], + ); + return useFetchState(call, []); +}; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx index 8358899c..84f63f36 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx @@ -21,6 +21,7 @@ import { ToolbarFilter, ToolbarToggleGroup, Bullseye, + Button, } from '@patternfly/react-core'; import { Table, @@ -40,6 +41,7 @@ import { useWorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind'; import { WorkspaceKindsColumns } from '~/app/types'; import ThemeAwareSearchInput from '~/app/components/ThemeAwareSearchInput'; import CustomEmptyState from '~/shared/components/CustomEmptyState'; +import { useTypedNavigate } from '~/app/routerHelper'; import { WorkspaceKindDetails } from './details/WorkspaceKindDetails'; export enum ActionType { @@ -63,6 +65,7 @@ export const WorkspaceKinds: React.FunctionComponent = () => { [], ); + const navigate = useTypedNavigate(); const [workspaceKinds, workspaceKindsLoaded, workspaceKindsError] = useWorkspaceKinds(); const workspaceCountPerKind = useWorkspaceCountPerKind(); const [selectedWorkspaceKind, setSelectedWorkspaceKind] = useState(null); @@ -580,10 +583,22 @@ export const WorkspaceKinds: React.FunctionComponent = () => { )} - { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - workspaceCountPerKind[workspaceKind.name]?.count ?? 0 - } + diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx index 4e059e5c..43e711e1 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx @@ -30,7 +30,12 @@ export const WorkspaceKindDetails: React.FunctionComponent { - const [activeTabKey, setActiveTabKey] = useState(0); + const overviewTabKey = 0; + const imagesTabKey = 1; + const podConfigsTabKey = 2; + const namespacesTabKey = 3; + + const [activeTabKey, setActiveTabKey] = useState(overviewTabKey); const handleTabClick = ( event: React.MouseEvent | React.KeyboardEvent | MouseEvent, @@ -51,25 +56,25 @@ export const WorkspaceKindDetails: React.FunctionComponent Overview} tabContentId="overviewTabContent" aria-label="Overview" /> Images} tabContentId="imagesTabContent" aria-label="Images" /> Pod Configs} tabContentId="podConfigsTabContent" aria-label="Pod Configs" /> Namespaces} tabContentId="namespacesTabContent" aria-label="Namespaces" @@ -79,22 +84,22 @@ export const WorkspaceKindDetails: React.FunctionComponent
+ } + aria-label="-controlled-check" + onChange={() => setImage({ ...image, hidden: !image.hidden })} + id="workspace-kind-image-hidden" + name="workspace-kind-image-hidden-switch" + /> + + setImage({ ...image, labels })} + /> + + + setImage({ ...image, imagePullPolicy: value as ImagePullPolicy }) + } + aria-label="FormSelect Input" + id="workspace-kind-image-pull-policy" + ouiaId="BasicFormSelect" + > + {options.map((option, index) => ( + + ))} + + + setImage({ ...image, ports })} + /> + {mode === 'edit' && ( + setImage({ ...image, redirect })} + /> + )} + + + + + + + + ); +}; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImagePort.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImagePort.tsx new file mode 100644 index 00000000..0b542fb7 --- /dev/null +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImagePort.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { + FormFieldGroupExpandable, + FormFieldGroupHeader, + FormGroup, + Grid, + GridItem, + TextInput, +} from '@patternfly/react-core'; +import { WorkspaceKindImagePort } from '~/app/types'; + +interface WorkspaceKindFormImagePortProps { + ports: WorkspaceKindImagePort[]; + setPorts: (ports: WorkspaceKindImagePort[]) => void; +} + +export const WorkspaceKindFormImagePort: React.FC = ({ + ports, + setPorts, +}) => ( + + } + > + + + + setPorts([{ ...ports[0], id: val }])} + id="port-id" + /> + + + + + setPorts([{ ...ports[0], displayName: val }])} + id="port-display-name" + /> + + + + + setPorts([{ ...ports[0], port: Number(val) }])} + id="port-number" + /> + + + + + + + + + +); diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageRedirect.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageRedirect.tsx new file mode 100644 index 00000000..1f456b7f --- /dev/null +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageRedirect.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import { + FormFieldGroupExpandable, + FormFieldGroupHeader, + FormGroup, + FormSelect, + FormSelectOption, + TextInput, +} from '@patternfly/react-core'; +import { + WorkspaceOptionRedirect, + WorkspaceRedirectMessageLevel, +} from '~/shared/api/backendApiTypes'; + +interface WorkspaceKindFormImageRedirectProps { + redirect: WorkspaceOptionRedirect; + setRedirect: (obj: WorkspaceOptionRedirect) => void; +} + +export const WorkspaceKindFormImageRedirect: React.FC = ({ + redirect, + setRedirect, +}) => { + const redirectMsgOptions = [ + { value: 'please choose', label: 'Select one', disabled: true }, + { + value: WorkspaceRedirectMessageLevel.RedirectMessageLevelInfo, + label: WorkspaceRedirectMessageLevel.RedirectMessageLevelInfo, + disabled: false, + }, + { + value: WorkspaceRedirectMessageLevel.RedirectMessageLevelWarning, + label: WorkspaceRedirectMessageLevel.RedirectMessageLevelWarning, + disabled: false, + }, + { + value: WorkspaceRedirectMessageLevel.RedirectMessageLevelDanger, + label: WorkspaceRedirectMessageLevel.RedirectMessageLevelDanger, + disabled: false, + }, + ]; + return ( + + } + > + + setRedirect({ ...redirect, to: val })} + id="redirect-to-id" + /> + + + + setRedirect({ + ...redirect, + message: { + text: redirect.message?.level || '', + level: val as WorkspaceRedirectMessageLevel, + }, + }) + } + aria-label="Redirect Message Select Input" + id="redirect-msg-lvl" + ouiaId="BasicFormSelect" + > + {redirectMsgOptions.map((option, index) => ( + + ))} + {' '} + + + + setRedirect({ + ...redirect, + message: { + level: + redirect.message?.level || WorkspaceRedirectMessageLevel.RedirectMessageLevelInfo, + text: val, + }, + }) + } + id="redirect-message-text" + /> + + + ); +}; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/properties/WorkspaceKindFormProperties.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/properties/WorkspaceKindFormProperties.tsx new file mode 100644 index 00000000..6d3c9b93 --- /dev/null +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/properties/WorkspaceKindFormProperties.tsx @@ -0,0 +1,131 @@ +import React, { useState } from 'react'; +import { + Content, + ExpandableSection, + Form, + FormGroup, + HelperText, + Switch, + TextInput, +} from '@patternfly/react-core'; +import { WorkspaceKindProperties } from '~/app/types'; + +interface WorkspaceKindFormPropertiesProps { + mode: string; + properties: WorkspaceKindProperties; + updateField: (properties: WorkspaceKindProperties) => void; +} + +export const WorkspaceKindFormProperties: React.FC = ({ + mode, + properties, + updateField, +}) => { + const [isExpanded, setIsExpanded] = useState(false); + return ( + +
+ setIsExpanded((prev) => !prev)} + isExpanded={isExpanded} + isIndented + > +
+ + updateField({ ...properties, displayName: value })} + id="workspace-kind-name" + /> + + + updateField({ ...properties, description: value })} + id="workspace-kind-description" + /> + + {mode === 'edit' && ( + + +
Deprecated
+ + Flag this workspace kind as deprecated and optionally set a deprecation + message + +
+ } + onChange={() => + updateField({ ...properties, deprecated: !properties.deprecated }) + } + id="workspace-kind-deprecated" + name="workspace-kind-deprecated-switch" + /> + + )} + {mode === 'edit' && properties.deprecated && ( + + updateField({ ...properties, deprecationMessage: value })} + id="workspace-kind-deprecated-msg" + /> + + )} + + updateField({ ...properties, hidden: !properties.hidden })} + id="workspace-kind-hidden" + name="workspace-kind-hidden-switch" + aria-label="workspace-kind-hidden" + label={ +
+
Hidden
+ Hide this workspace kind from users +
+ } + /> +
+ + updateField({ ...properties, icon: { url: value } })} + id="workspace-kind-icon" + /> + + + updateField({ ...properties, logo: { url: value } })} + id="workspace-kind-logo" + /> + + + +
+ + ); +}; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx index 84f63f36..b16ecb65 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx @@ -66,6 +66,10 @@ export const WorkspaceKinds: React.FunctionComponent = () => { ); const navigate = useTypedNavigate(); + // TODO: Uncomment when WorkspaceKindForm is complete + // const createWorkspaceKind = useCallback(() => { + // navigate('workspaceKindCreate'); + // }, [navigate]); const [workspaceKinds, workspaceKindsLoaded, workspaceKindsError] = useWorkspaceKinds(); const workspaceCountPerKind = useWorkspaceCountPerKind(); const [selectedWorkspaceKind, setSelectedWorkspaceKind] = useState(null); @@ -519,12 +523,13 @@ export const WorkspaceKinds: React.FunctionComponent = () => { {statusSelect} + {/* //TODO: Uncomment when WorkspaceKind Form is finished + */} - {/* */} diff --git a/workspaces/frontend/src/app/routes.ts b/workspaces/frontend/src/app/routes.ts index 0eab5cc5..ee0cf4d5 100644 --- a/workspaces/frontend/src/app/routes.ts +++ b/workspaces/frontend/src/app/routes.ts @@ -5,6 +5,7 @@ export const AppRoutePaths = { workspaceEdit: '/workspaces/edit', workspaceKinds: '/workspacekinds', workspaceKindSummary: '/workspacekinds/:kind/summary', + workspaceKindCreate: '/workspacekinds/create', } satisfies Record; export type AppRoute = (typeof AppRoutePaths)[keyof typeof AppRoutePaths]; @@ -29,6 +30,7 @@ export type RouteParamsMap = { workspaceKindSummary: { kind: string; }; + workspaceKindCreate: undefined; }; /** @@ -57,6 +59,9 @@ export type RouteStateMap = { imageId?: string; podConfigId?: string; }; + workspaceKindCreate: { + namespace: string; + }; }; /** @@ -76,4 +81,5 @@ export type RouteSearchParamsMap = { workspaceEdit: undefined; workspaceKinds: undefined; workspaceKindSummary: undefined; + workspaceKindCreate: undefined; }; diff --git a/workspaces/frontend/src/app/types.ts b/workspaces/frontend/src/app/types.ts index ecf41078..ccc1523b 100644 --- a/workspaces/frontend/src/app/types.ts +++ b/workspaces/frontend/src/app/types.ts @@ -5,6 +5,7 @@ import { WorkspacePodVolumeMount, WorkspacePodSecretMount, Workspace, + WorkspaceImageRef, } from '~/shared/api/backendApiTypes'; export interface WorkspaceColumnDefinition { @@ -41,3 +42,42 @@ export interface WorkspaceCountPerOption { countByPodConfig: Record; countByNamespace: Record; } + +export interface WorkspaceKindProperties { + displayName: string; + description: string; + deprecated: boolean; + deprecationMessage: string; + hidden: boolean; + icon: WorkspaceImageRef; + logo: WorkspaceImageRef; +} + +export interface WorkspaceKindImageConfigValue extends WorkspaceImageConfigValue { + imagePullPolicy: ImagePullPolicy.IfNotPresent | ImagePullPolicy.Always | ImagePullPolicy.Never; + ports: WorkspaceKindImagePort[]; + image: string; +} + +export enum ImagePullPolicy { + IfNotPresent = 'IfNotPresent', + Always = 'Always', + Never = 'Never', +} + +export interface WorkspaceKindImagePort { + id: string; + displayName: string; + port: number; + protocol: 'HTTP'; // ONLY HTTP is supported at the moment, per https://github.com/thesuperzapper/kubeflow-notebooks-v2-design/blob/main/crds/workspace-kind.yaml#L275 +} + +export interface WorkspaceKindImageConfigData { + default: string; + values: WorkspaceKindImageConfigValue[]; +} + +export interface WorkspaceKindFormData { + properties: WorkspaceKindProperties; + imageConfig: WorkspaceKindImageConfigData; +} diff --git a/workspaces/frontend/src/shared/style/MUI-theme.scss b/workspaces/frontend/src/shared/style/MUI-theme.scss index 6184369c..ec9aa157 100644 --- a/workspaces/frontend/src/shared/style/MUI-theme.scss +++ b/workspaces/frontend/src/shared/style/MUI-theme.scss @@ -102,6 +102,7 @@ --mui-spacing-8px: var(--mui-spacing); --mui-spacing-16px: calc(2 * var(--mui-spacing)); + --pf-t--global--spacer--gap--group-to-group--vertical--default: var(--pf-t--global--spacer--sm); --pf-t--global--border--width--box--status--default: 1px; --pf-t--global--border--radius--pill: var(--mui-shape-borderRadius); --pf-t--global--text--color--brand--default: var(--mui-palette-primary-main); @@ -267,6 +268,10 @@ resize: none; --pf-v6-c-form-control--PaddingBlockStart: var(--mui-spacing-16px); --pf-v6-c-form-control--PaddingBlockEnd: var(--mui-spacing-16px); + #text-file-simple-filename { + --pf-v6-c-form-control--PaddingBlockStart: var(--mui-spacing-8px); + --pf-v6-c-form-control--PaddingBlockEnd: var(--mui-spacing-8px);; + } } @@ -432,6 +437,10 @@ --pf-v6-c-form-control--after--BorderColor: transparent !important; } +.pf-v6-c-form__field-group-body { + --pf-v6-c-form__field-group-body--PaddingBlockStart: 8px; +} + .pf-v6-c-form__group .pf-v6-c-form-control:focus-within+.pf-v6-c-form__label, .pf-v6-c-form__group .pf-v6-c-form-control:not(:placeholder-shown)+.pf-v6-c-form__label { color: var(--mui-palette-primary-main); @@ -623,7 +632,10 @@ transform-origin: center center; align-self: start; } - +/* CSS workaround for spacing in labels in Workspace Kind */ +.form-label-fieldgroup .pf-v6-c-table tr:where(.pf-v6-c-table__tr) > :where(th, td) { + padding-block-start: 0px; +} /* CSS workaround to use MUI icon for sort icon */ .mui-theme .pf-v6-c-table__sort-indicator::before { display: block; @@ -830,4 +842,21 @@ --pf-v6-c-form-control--PaddingBlockEnd: var(--mui-spacing-8px); -} \ No newline at end of file +} + +.mui-theme .pf-v6-c-expandable-section .pf-v6-c-expandable-section__content { + margin-block-end: var(--mui-spacing-16px); +} + +.workspacekind-file-upload { + height: 100%; + .pf-v6-c-file-upload__file-details { + flex-grow: 1; + } +} + +/* Workaround for Toggle group header in Workspace Kind Form */ +.workspace-kind-form-header .pf-v6-c-toggle-group__button.pf-m-selected { + background-color: #E0F0FF; + color: var(--pf-t--color--black); +} From c0b8f7b3004725f125d6556978b242cacdfe8456 Mon Sep 17 00:00:00 2001 From: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> Date: Wed, 25 Jun 2025 07:51:16 -0300 Subject: [PATCH 11/68] chore(ws): lint frontend on each commit (#440) Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> --- workspaces/frontend/.husky/pre-commit | 3 +++ workspaces/frontend/package-lock.json | 16 ++++++++++++++++ workspaces/frontend/package.json | 4 +++- 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100755 workspaces/frontend/.husky/pre-commit diff --git a/workspaces/frontend/.husky/pre-commit b/workspaces/frontend/.husky/pre-commit new file mode 100755 index 00000000..ab36af88 --- /dev/null +++ b/workspaces/frontend/.husky/pre-commit @@ -0,0 +1,3 @@ +echo "Running husky pre-commit hook..." +cd workspaces/frontend +npm run test:lint diff --git a/workspaces/frontend/package-lock.json b/workspaces/frontend/package-lock.json index dea71525..a3b9b84d 100644 --- a/workspaces/frontend/package-lock.json +++ b/workspaces/frontend/package-lock.json @@ -56,6 +56,7 @@ "expect": "^29.7.0", "fork-ts-checker-webpack-plugin": "^9.0.3", "html-webpack-plugin": "^5.6.0", + "husky": "^9.1.7", "imagemin": "^8.0.1", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", @@ -12194,6 +12195,21 @@ "node": ">=10.17.0" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/hyperdyperid": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", diff --git a/workspaces/frontend/package.json b/workspaces/frontend/package.json index 55680521..ca879d9e 100644 --- a/workspaces/frontend/package.json +++ b/workspaces/frontend/package.json @@ -33,7 +33,8 @@ "cypress:server:build": "POLL_INTERVAL=9999999 FAST_POLL_INTERVAL=9999999 npm run build", "cypress:server": "serve ./dist -p 9001 -s -L", "prettier": "prettier --ignore-path .gitignore --write \"**/*{.ts,.tsx,.js,.jsx,.css,.json}\"", - "prettier:check": "prettier --ignore-path .gitignore --check \"**/*{.ts,.tsx,.js,.jsx,.css,.json}\"" + "prettier:check": "prettier --ignore-path .gitignore --check \"**/*{.ts,.tsx,.js,.jsx,.css,.json}\"", + "prepare": "cd ../../ && husky workspaces/frontend/.husky" }, "devDependencies": { "@cypress/code-coverage": "^3.13.5", @@ -66,6 +67,7 @@ "expect": "^29.7.0", "fork-ts-checker-webpack-plugin": "^9.0.3", "html-webpack-plugin": "^5.6.0", + "husky": "^9.1.7", "imagemin": "^8.0.1", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", From eb8d3acb934fb51067ba74eae22adb55e2251d54 Mon Sep 17 00:00:00 2001 From: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> Date: Wed, 25 Jun 2025 07:52:16 -0300 Subject: [PATCH 12/68] chore(ws): show ESLint errors from local rules on IDE (#439) Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> --- workspaces/frontend/.eslintrc.js | 34 ++++++++++++-- .../frontend/eslint-local-rules/index.js | 4 ++ .../no-raw-react-router-hook.js | 46 +++++++++++++++++++ workspaces/frontend/package-lock.json | 6 +++ workspaces/frontend/package.json | 5 +- workspaces/frontend/src/app/NavSidebar.tsx | 5 +- .../src/app/hooks/useCurrentRouteKey.ts | 5 +- workspaces/frontend/src/app/routerHelper.ts | 2 + 8 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 workspaces/frontend/eslint-local-rules/index.js create mode 100644 workspaces/frontend/eslint-local-rules/no-raw-react-router-hook.js diff --git a/workspaces/frontend/.eslintrc.js b/workspaces/frontend/.eslintrc.js index 53455a9c..09f4cb08 100644 --- a/workspaces/frontend/.eslintrc.js +++ b/workspaces/frontend/.eslintrc.js @@ -1,5 +1,3 @@ -const noReactHookNamespace = require('./eslint-local-rules/no-react-hook-namespace'); - module.exports = { parser: '@typescript-eslint/parser', env: { @@ -13,7 +11,7 @@ module.exports = { js: true, useJSXTextNode: true, project: './tsconfig.json', - tsconfigRootDir: '.', + tsconfigRootDir: __dirname, }, // includes the typescript specific rules found here: https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules plugins: [ @@ -24,6 +22,7 @@ module.exports = { 'no-only-tests', 'no-relative-import-paths', 'prettier', + 'local-rules', ], extends: [ 'eslint:recommended', @@ -200,6 +199,17 @@ module.exports = { 'no-lone-blocks': 'error', 'no-lonely-if': 'error', 'no-promise-executor-return': 'error', + 'no-restricted-imports': [ + 'error', + { + paths: [ + { + name: 'react-router', + message: 'Use react-router-dom instead.', + }, + ], + }, + ], 'no-restricted-globals': [ 'error', { @@ -221,7 +231,8 @@ module.exports = { 'symbol-description': 'error', yoda: 'error', 'func-names': 'warn', - 'no-react-hook-namespace': 'error', + 'local-rules/no-react-hook-namespace': 'error', + 'local-rules/no-raw-react-router-hook': 'error', }, overrides: [ { @@ -270,7 +281,20 @@ module.exports = { { files: ['**/*.{js,jsx,ts,tsx}'], rules: { - 'no-react-hook-namespace': 'error', + 'local-rules/no-react-hook-namespace': 'error', + 'local-rules/no-raw-react-router-hook': 'error', + }, + }, + { + files: ['.eslintrc.js'], + parserOptions: { + project: null, + }, + }, + { + files: ['eslint-local-rules/**/*.js'], + rules: { + '@typescript-eslint/no-require-imports': 'off', }, }, ], diff --git a/workspaces/frontend/eslint-local-rules/index.js b/workspaces/frontend/eslint-local-rules/index.js new file mode 100644 index 00000000..7643244e --- /dev/null +++ b/workspaces/frontend/eslint-local-rules/index.js @@ -0,0 +1,4 @@ +module.exports = { + 'no-react-hook-namespace': require('./no-react-hook-namespace'), + 'no-raw-react-router-hook': require('./no-raw-react-router-hook'), +}; diff --git a/workspaces/frontend/eslint-local-rules/no-raw-react-router-hook.js b/workspaces/frontend/eslint-local-rules/no-raw-react-router-hook.js new file mode 100644 index 00000000..40b07448 --- /dev/null +++ b/workspaces/frontend/eslint-local-rules/no-raw-react-router-hook.js @@ -0,0 +1,46 @@ +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'Disallow use of raw react-router-dom hooks. Use typed wrappers instead.', + }, + messages: { + avoidRawHook: + 'Use "{{typedHook}}" from `~/app/routerHelper` instead of raw React Router hook "{{rawHook}}".', + }, + schema: [], + }, + + create(context) { + const forbiddenHooks = { + useNavigate: 'useTypedNavigate', + useParams: 'useTypedParams', + useSearchParams: 'useTypedSearchParams', + useLocation: 'useTypedLocation', + }; + + return { + ImportDeclaration(node) { + if (node.source.value !== 'react-router-dom') { + return; + } + + for (const specifier of node.specifiers) { + if ( + specifier.type === 'ImportSpecifier' && + Object.prototype.hasOwnProperty.call(forbiddenHooks, specifier.imported.name) + ) { + context.report({ + node: specifier, + messageId: 'avoidRawHook', + data: { + rawHook: specifier.imported.name, + typedHook: forbiddenHooks[specifier.imported.name], + }, + }); + } + } + }, + }; + }, +}; diff --git a/workspaces/frontend/package-lock.json b/workspaces/frontend/package-lock.json index a3b9b84d..feb1e0eb 100644 --- a/workspaces/frontend/package-lock.json +++ b/workspaces/frontend/package-lock.json @@ -18,6 +18,7 @@ "@patternfly/react-tokens": "^6.2.0", "@types/js-yaml": "^4.0.9", "date-fns": "^4.1.0", + "eslint-plugin-local-rules": "^3.0.2", "js-yaml": "^4.1.0", "npm-run-all": "^4.1.5", "react": "^18", @@ -9966,6 +9967,11 @@ "license": "MIT", "optional": true }, + "node_modules/eslint-plugin-local-rules": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-local-rules/-/eslint-plugin-local-rules-3.0.2.tgz", + "integrity": "sha512-IWME7GIYHXogTkFsToLdBCQVJ0U4kbSuVyDT+nKoR4UgtnVrrVeNWuAZkdEu1nxkvi9nsPccGehEEF6dgA28IQ==" + }, "node_modules/eslint-plugin-no-only-tests": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-3.3.0.tgz", diff --git a/workspaces/frontend/package.json b/workspaces/frontend/package.json index ca879d9e..8e08b930 100644 --- a/workspaces/frontend/package.json +++ b/workspaces/frontend/package.json @@ -24,8 +24,8 @@ "test:unit": "npm run test:jest -- --silent", "test:watch": "jest --watch", "test:coverage": "jest --coverage", - "test:fix": "eslint --rulesdir eslint-local-rules --ext .js,.ts,.jsx,.tsx ./src --fix", - "test:lint": "eslint --rulesdir eslint-local-rules --max-warnings 0 --ext .js,.ts,.jsx,.tsx ./src", + "test:fix": "eslint --ext .js,.ts,.jsx,.tsx ./src --fix", + "test:lint": "eslint --max-warnings 0 --ext .js,.ts,.jsx,.tsx ./src", "cypress:open": "cypress open --project src/__tests__/cypress", "cypress:open:mock": "CY_MOCK=1 CY_WS_PORT=9002 npm run cypress:open -- ", "cypress:run": "cypress run -b chrome --project src/__tests__/cypress", @@ -108,6 +108,7 @@ "@patternfly/react-tokens": "^6.2.0", "@types/js-yaml": "^4.0.9", "date-fns": "^4.1.0", + "eslint-plugin-local-rules": "^3.0.2", "js-yaml": "^4.1.0", "npm-run-all": "^4.1.5", "react": "^18", diff --git a/workspaces/frontend/src/app/NavSidebar.tsx b/workspaces/frontend/src/app/NavSidebar.tsx index e9b7195b..f1deb892 100644 --- a/workspaces/frontend/src/app/NavSidebar.tsx +++ b/workspaces/frontend/src/app/NavSidebar.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { NavLink, useLocation } from 'react-router-dom'; +import { NavLink } from 'react-router-dom'; import { Brand, Nav, @@ -9,11 +9,12 @@ import { PageSidebar, PageSidebarBody, } from '@patternfly/react-core'; +import { useTypedLocation } from '~/app/routerHelper'; import { useNavData, isNavDataGroup, NavDataHref, NavDataGroup } from './AppRoutes'; import { isMUITheme, LOGO_LIGHT } from './const'; const NavHref: React.FC<{ item: NavDataHref }> = ({ item }) => { - const location = useLocation(); + const location = useTypedLocation(); // With the redirect in place, we can now use a simple path comparison. const isActive = location.pathname === item.path; diff --git a/workspaces/frontend/src/app/hooks/useCurrentRouteKey.ts b/workspaces/frontend/src/app/hooks/useCurrentRouteKey.ts index 34077823..69da59cf 100644 --- a/workspaces/frontend/src/app/hooks/useCurrentRouteKey.ts +++ b/workspaces/frontend/src/app/hooks/useCurrentRouteKey.ts @@ -1,8 +1,9 @@ -import { useLocation, matchPath } from 'react-router-dom'; +import { matchPath } from 'react-router-dom'; import { AppRouteKey, AppRoutePaths } from '~/app/routes'; +import { useTypedLocation } from '~/app/routerHelper'; export function useCurrentRouteKey(): AppRouteKey | undefined { - const location = useLocation(); + const location = useTypedLocation(); const { pathname } = location; const matchEntries = Object.entries(AppRoutePaths) as [AppRouteKey, string][]; diff --git a/workspaces/frontend/src/app/routerHelper.ts b/workspaces/frontend/src/app/routerHelper.ts index 4b600c46..332b473d 100644 --- a/workspaces/frontend/src/app/routerHelper.ts +++ b/workspaces/frontend/src/app/routerHelper.ts @@ -1,3 +1,5 @@ +/* eslint-disable local-rules/no-raw-react-router-hook */ + import { generatePath, Location, From 4c91627eb01293aa81ce9144e9797f2677993b05 Mon Sep 17 00:00:00 2001 From: Mathew Wicks <5735406+thesuperzapper@users.noreply.github.com> Date: Thu, 26 Jun 2025 11:46:17 -0700 Subject: [PATCH 13/68] feat(ws): validate podMetadata for ws and wsk in webhook (#436) Signed-off-by: Mathew Wicks <5735406+thesuperzapper@users.noreply.github.com> --- .../controller/internal/webhook/suite_test.go | 22 +++ .../internal/webhook/workspace_webhook.go | 131 ++++++++++++------ .../webhook/workspace_webhook_test.go | 82 +++++++++++ .../internal/webhook/workspacekind_webhook.go | 38 ++++- .../webhook/workspacekind_webhook_test.go | 36 +++++ 5 files changed, 268 insertions(+), 41 deletions(-) diff --git a/workspaces/controller/internal/webhook/suite_test.go b/workspaces/controller/internal/webhook/suite_test.go index ba438e76..b0a652cf 100644 --- a/workspaces/controller/internal/webhook/suite_test.go +++ b/workspaces/controller/internal/webhook/suite_test.go @@ -511,6 +511,28 @@ func NewExampleWorkspaceKind(name string) *kubefloworgv1beta1.WorkspaceKind { } } +// NewExampleWorkspaceKindWithInvalidPodMetadataLabelKey returns a WorkspaceKind with an invalid PodMetadata label key. +func NewExampleWorkspaceKindWithInvalidPodMetadataLabelKey(name string) *kubefloworgv1beta1.WorkspaceKind { + workspaceKind := NewExampleWorkspaceKind(name) + workspaceKind.Spec.PodTemplate.PodMetadata = &kubefloworgv1beta1.WorkspaceKindPodMetadata{ + Labels: map[string]string{ + "!bad_key!": "value", + }, + } + return workspaceKind +} + +// NewExampleWorkspaceKindWithInvalidPodMetadataAnnotationKey returns a WorkspaceKind with an invalid PodMetadata annotation key. +func NewExampleWorkspaceKindWithInvalidPodMetadataAnnotationKey(name string) *kubefloworgv1beta1.WorkspaceKind { + workspaceKind := NewExampleWorkspaceKind(name) + workspaceKind.Spec.PodTemplate.PodMetadata = &kubefloworgv1beta1.WorkspaceKindPodMetadata{ + Annotations: map[string]string{ + "!bad_key!": "value", + }, + } + return workspaceKind +} + // NewExampleWorkspaceKindWithImageConfigCycle returns a WorkspaceKind with a cycle in the ImageConfig options. func NewExampleWorkspaceKindWithImageConfigCycle(name string) *kubefloworgv1beta1.WorkspaceKind { workspaceKind := NewExampleWorkspaceKind(name) diff --git a/workspaces/controller/internal/webhook/workspace_webhook.go b/workspaces/controller/internal/webhook/workspace_webhook.go index ca666438..5143fc6f 100644 --- a/workspaces/controller/internal/webhook/workspace_webhook.go +++ b/workspaces/controller/internal/webhook/workspace_webhook.go @@ -20,7 +20,10 @@ import ( "context" "fmt" + "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" + apivalidation "k8s.io/apimachinery/pkg/api/validation" + v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" @@ -76,12 +79,12 @@ func (v *WorkspaceValidator) ValidateCreate(ctx context.Context, obj runtime.Obj } // validate the Workspace - if err := v.validateImageConfig(workspace, workspaceKind); err != nil { - allErrs = append(allErrs, err) - } - if err := v.validatePodConfig(workspace, workspaceKind); err != nil { - allErrs = append(allErrs, err) - } + // NOTE: we do this after fetching the WorkspaceKind as there will be multiple types in the future, + // and we need to know which one the Workspace is using to validate it correctly. + allErrs = append(allErrs, v.validatePodTemplatePodMetadata(workspace)...) + allErrs = append(allErrs, v.validateImageConfig(workspace, workspaceKind)...) + allErrs = append(allErrs, v.validatePodConfig(workspace, workspaceKind)...) + if len(allErrs) == 0 { return nil, nil } @@ -113,11 +116,15 @@ func (v *WorkspaceValidator) ValidateUpdate(ctx context.Context, oldObj, newObj // check if workspace kind related fields have changed var workspaceKindChange = false + var podMetadataChange = false var imageConfigChange = false var podConfigChange = false if newWorkspace.Spec.Kind != oldWorkspace.Spec.Kind { workspaceKindChange = true } + if !equality.Semantic.DeepEqual(newWorkspace.Spec.PodTemplate.PodMetadata, oldWorkspace.Spec.PodTemplate.PodMetadata) { + podMetadataChange = true + } if newWorkspace.Spec.PodTemplate.Options.ImageConfig != oldWorkspace.Spec.PodTemplate.Options.ImageConfig { imageConfigChange = true } @@ -126,7 +133,7 @@ func (v *WorkspaceValidator) ValidateUpdate(ctx context.Context, oldObj, newObj } // if any of the workspace kind related fields have changed, revalidate the workspace - if workspaceKindChange || imageConfigChange || podConfigChange { + if workspaceKindChange || podMetadataChange || imageConfigChange || podConfigChange { // fetch the WorkspaceKind workspaceKind, err := v.validateWorkspaceKind(ctx, newWorkspace) if err != nil { @@ -140,18 +147,19 @@ func (v *WorkspaceValidator) ValidateUpdate(ctx context.Context, oldObj, newObj ) } + // validate the new podTemplate podMetadata + if podMetadataChange { + allErrs = append(allErrs, v.validatePodTemplatePodMetadata(newWorkspace)...) + } + // validate the new imageConfig if imageConfigChange { - if err := v.validateImageConfig(newWorkspace, workspaceKind); err != nil { - allErrs = append(allErrs, err) - } + allErrs = append(allErrs, v.validateImageConfig(newWorkspace, workspaceKind)...) } // validate the new podConfig if podConfigChange { - if err := v.validatePodConfig(newWorkspace, workspaceKind); err != nil { - allErrs = append(allErrs, err) - } + allErrs = append(allErrs, v.validatePodConfig(newWorkspace, workspaceKind)...) } } @@ -197,36 +205,79 @@ func (v *WorkspaceValidator) validateWorkspaceKind(ctx context.Context, workspac return workspaceKind, nil } -// validateImageConfig checks if the imageConfig selected by a Workspace exists a WorkspaceKind -func (v *WorkspaceValidator) validateImageConfig(workspace *kubefloworgv1beta1.Workspace, workspaceKind *kubefloworgv1beta1.WorkspaceKind) *field.Error { - imageConfig := workspace.Spec.PodTemplate.Options.ImageConfig - for _, value := range workspaceKind.Spec.PodTemplate.Options.ImageConfig.Values { - if imageConfig == value.Id { - // imageConfig found - return nil - } +// validatePodTemplatePodMetadata validates the podMetadata of a Workspace's PodTemplate +func (v *WorkspaceValidator) validatePodTemplatePodMetadata(workspace *kubefloworgv1beta1.Workspace) []*field.Error { + var errs []*field.Error + + podMetadata := workspace.Spec.PodTemplate.PodMetadata + podMetadataPath := field.NewPath("spec", "podTemplate", "podMetadata") + + // if podMetadata is nil, we cannot validate it + if podMetadata == nil { + return nil } - imageConfigPath := field.NewPath("spec", "podTemplate", "options", "imageConfig") - return field.Invalid( - imageConfigPath, - imageConfig, - fmt.Sprintf("imageConfig with id %q not found in workspace kind %q", imageConfig, workspaceKind.Name), - ) + + // validate labels + labels := podMetadata.Labels + labelsPath := podMetadataPath.Child("labels") + errs = append(errs, v1validation.ValidateLabels(labels, labelsPath)...) + + // validate annotations + annotations := podMetadata.Annotations + annotationsPath := podMetadataPath.Child("annotations") + errs = append(errs, apivalidation.ValidateAnnotations(annotations, annotationsPath)...) + + return errs } -// validatePodConfig checks if the podConfig selected by a Workspace exists a WorkspaceKind -func (v *WorkspaceValidator) validatePodConfig(workspace *kubefloworgv1beta1.Workspace, workspaceKind *kubefloworgv1beta1.WorkspaceKind) *field.Error { - podConfig := workspace.Spec.PodTemplate.Options.PodConfig - for _, value := range workspaceKind.Spec.PodTemplate.Options.PodConfig.Values { - if podConfig == value.Id { - // podConfig found - return nil +// validateImageConfig validates the imageConfig selected by a Workspace +func (v *WorkspaceValidator) validateImageConfig(workspace *kubefloworgv1beta1.Workspace, workspaceKind *kubefloworgv1beta1.WorkspaceKind) []*field.Error { + var errs []*field.Error + + imageConfig := workspace.Spec.PodTemplate.Options.ImageConfig + imageConfigPath := field.NewPath("spec", "podTemplate", "options", "imageConfig") + + // ensure the imageConfig exists in the WorkspaceKind + foundImageConfig := false + for _, value := range workspaceKind.Spec.PodTemplate.Options.ImageConfig.Values { + if imageConfig == value.Id { + foundImageConfig = true + break } } - podConfigPath := field.NewPath("spec", "podTemplate", "options", "podConfig") - return field.Invalid( - podConfigPath, - podConfig, - fmt.Sprintf("podConfig with id %q not found in workspace kind %q", podConfig, workspaceKind.Name), - ) + if !foundImageConfig { + errs = append(errs, field.Invalid( + imageConfigPath, + imageConfig, + fmt.Sprintf("imageConfig with id %q not found in workspace kind %q", imageConfig, workspaceKind.Name), + )) + } + + return errs +} + +// validatePodConfig validates the podConfig selected by a Workspace +func (v *WorkspaceValidator) validatePodConfig(workspace *kubefloworgv1beta1.Workspace, workspaceKind *kubefloworgv1beta1.WorkspaceKind) []*field.Error { + var errs []*field.Error + + podConfig := workspace.Spec.PodTemplate.Options.PodConfig + podConfigPath := field.NewPath("spec", "podTemplate", "options", "podConfig") + + // ensure the podConfig exists in the WorkspaceKind + foundPodConfig := false + for _, value := range workspaceKind.Spec.PodTemplate.Options.PodConfig.Values { + if podConfig == value.Id { + foundPodConfig = true + break + } + } + if !foundPodConfig { + errs = append(errs, field.Invalid( + podConfigPath, + podConfig, + fmt.Sprintf("podConfig with id %q not found in workspace kind %q", podConfig, workspaceKind.Name), + )) + } + + return errs } diff --git a/workspaces/controller/internal/webhook/workspace_webhook_test.go b/workspaces/controller/internal/webhook/workspace_webhook_test.go index e6efc213..64ebd7e6 100644 --- a/workspaces/controller/internal/webhook/workspace_webhook_test.go +++ b/workspaces/controller/internal/webhook/workspace_webhook_test.go @@ -71,6 +71,34 @@ var _ = Describe("Workspace Webhook", func() { Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("workspace kind %q not found", invalidWorkspaceKindName))) }) + It("should reject an invalid podMetadata.labels key", func() { + invalidLabelKey := "!bad-key!" + + By("creating the Workspace") + workspace := NewExampleWorkspace(workspaceName, namespaceName, workspaceKindName) + workspace.Spec.PodTemplate.PodMetadata = &kubefloworgv1beta1.WorkspacePodMetadata{} + workspace.Spec.PodTemplate.PodMetadata.Labels = map[string]string{ + invalidLabelKey: "value", + } + err := k8sClient.Create(ctx, workspace) + Expect(err).NotTo(Succeed()) + Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("Invalid value: %q", invalidLabelKey))) + }) + + It("should reject an invalid podMetadata.annotations key", func() { + invalidAnnotationKey := "!bad-key!" + + By("creating the Workspace") + workspace := NewExampleWorkspace(workspaceName, namespaceName, workspaceKindName) + workspace.Spec.PodTemplate.PodMetadata = &kubefloworgv1beta1.WorkspacePodMetadata{} + workspace.Spec.PodTemplate.PodMetadata.Annotations = map[string]string{ + invalidAnnotationKey: "value", + } + err := k8sClient.Create(ctx, workspace) + Expect(err).NotTo(Succeed()) + Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("Invalid value: %q", invalidAnnotationKey))) + }) + It("should reject an invalid imageConfig", func() { invalidImageConfig := "invalid_image_config" @@ -156,6 +184,60 @@ var _ = Describe("Workspace Webhook", func() { Expect(k8sClient.Patch(ctx, newWorkspace, patch)).NotTo(Succeed()) }) + It("should handle podMetadata.labels updates", func() { + By("getting the Workspace") + workspace := &kubefloworgv1beta1.Workspace{} + Expect(k8sClient.Get(ctx, workspaceKey, workspace)).To(Succeed()) + patch := client.MergeFrom(workspace.DeepCopy()) + + By("failing to update `spec.podTemplate.podMetadata.labels` with an invalid key") + invalidLabelKey := "!bad-key!" + newWorkspace := workspace.DeepCopy() + newWorkspace.Spec.PodTemplate.PodMetadata = &kubefloworgv1beta1.WorkspacePodMetadata{} + newWorkspace.Spec.PodTemplate.PodMetadata.Labels = map[string]string{ + invalidLabelKey: "value", + } + err := k8sClient.Patch(ctx, newWorkspace, patch) + Expect(err).NotTo(Succeed()) + Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("Invalid value: %q", invalidLabelKey))) + + By("updating `spec.podTemplate.podMetadata.labels` with a valid key") + validLabelKey := "good-key" + newWorkspace = workspace.DeepCopy() + newWorkspace.Spec.PodTemplate.PodMetadata = &kubefloworgv1beta1.WorkspacePodMetadata{} + newWorkspace.Spec.PodTemplate.PodMetadata.Labels = map[string]string{ + validLabelKey: "value", + } + Expect(k8sClient.Patch(ctx, newWorkspace, patch)).To(Succeed()) + }) + + It("should handle podMetadata.annotations updates", func() { + By("getting the Workspace") + workspace := &kubefloworgv1beta1.Workspace{} + Expect(k8sClient.Get(ctx, workspaceKey, workspace)).To(Succeed()) + patch := client.MergeFrom(workspace.DeepCopy()) + + By("failing to update `spec.podTemplate.podMetadata.annotations` with an invalid key") + invalidAnnotationKey := "!bad-key!" + newWorkspace := workspace.DeepCopy() + newWorkspace.Spec.PodTemplate.PodMetadata = &kubefloworgv1beta1.WorkspacePodMetadata{} + newWorkspace.Spec.PodTemplate.PodMetadata.Annotations = map[string]string{ + invalidAnnotationKey: "value", + } + err := k8sClient.Patch(ctx, newWorkspace, patch) + Expect(err).NotTo(Succeed()) + Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("Invalid value: %q", invalidAnnotationKey))) + + By("updating `spec.podTemplate.podMetadata.annotations` with a valid key") + validAnnotationKey := "good-key" + newWorkspace = workspace.DeepCopy() + newWorkspace.Spec.PodTemplate.PodMetadata = &kubefloworgv1beta1.WorkspacePodMetadata{} + newWorkspace.Spec.PodTemplate.PodMetadata.Annotations = map[string]string{ + validAnnotationKey: "value", + } + Expect(k8sClient.Patch(ctx, newWorkspace, patch)).To(Succeed()) + }) + It("should handle imageConfig updates", func() { By("getting the Workspace") workspace := &kubefloworgv1beta1.Workspace{} diff --git a/workspaces/controller/internal/webhook/workspacekind_webhook.go b/workspaces/controller/internal/webhook/workspacekind_webhook.go index 4c591731..e7fd49a3 100644 --- a/workspaces/controller/internal/webhook/workspacekind_webhook.go +++ b/workspaces/controller/internal/webhook/workspacekind_webhook.go @@ -23,7 +23,10 @@ import ( "reflect" "sync" + "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" + apivalidation "k8s.io/apimachinery/pkg/api/validation" + v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -69,6 +72,9 @@ func (v *WorkspaceKindValidator) ValidateCreate(ctx context.Context, obj runtime var allErrs field.ErrorList + // validate the pod metadata + allErrs = append(allErrs, v.validatePodTemplatePodMetadata(workspaceKind)...) + // validate the extra environment variables allErrs = append(allErrs, validateExtraEnv(workspaceKind)...) @@ -140,8 +146,13 @@ func (v *WorkspaceKindValidator) ValidateUpdate(ctx context.Context, oldObj, new // NOTE: the cluster is only queried when either function is called for the first time getImageConfigUsageCount, getPodConfigUsageCount := v.getLazyOptionUsageCountFuncs(ctx, oldWorkspaceKind) + // validate the pod metadata + if !equality.Semantic.DeepEqual(newWorkspaceKind.Spec.PodTemplate.PodMetadata, oldWorkspaceKind.Spec.PodTemplate.PodMetadata) { + allErrs = append(allErrs, v.validatePodTemplatePodMetadata(newWorkspaceKind)...) + } + // validate the extra environment variables - if !reflect.DeepEqual(newWorkspaceKind.Spec.PodTemplate.ExtraEnv, oldWorkspaceKind.Spec.PodTemplate.ExtraEnv) { + if !equality.Semantic.DeepEqual(newWorkspaceKind.Spec.PodTemplate.ExtraEnv, oldWorkspaceKind.Spec.PodTemplate.ExtraEnv) { allErrs = append(allErrs, validateExtraEnv(newWorkspaceKind)...) } @@ -480,6 +491,31 @@ func (v *WorkspaceKindValidator) getOptionsUsageCounts(ctx context.Context, work return imageConfigUsageCount, podConfigUsageCount, nil } +// validatePodTemplatePodMetadata validates the podMetadata of a WorkspaceKind's PodTemplate +func (v *WorkspaceKindValidator) validatePodTemplatePodMetadata(workspaceKind *kubefloworgv1beta1.WorkspaceKind) []*field.Error { + var errs []*field.Error + + podMetadata := workspaceKind.Spec.PodTemplate.PodMetadata + podMetadataPath := field.NewPath("spec", "podTemplate", "podMetadata") + + // if podMetadata is nil, we cannot validate it + if podMetadata == nil { + return errs + } + + // validate labels + labels := podMetadata.Labels + labelsPath := podMetadataPath.Child("labels") + errs = append(errs, v1validation.ValidateLabels(labels, labelsPath)...) + + // validate annotations + annotations := podMetadata.Annotations + annotationsPath := podMetadataPath.Child("annotations") + errs = append(errs, apivalidation.ValidateAnnotations(annotations, annotationsPath)...) + + return errs +} + // validateExtraEnv validates the extra environment variables in a WorkspaceKind func validateExtraEnv(workspaceKind *kubefloworgv1beta1.WorkspaceKind) []*field.Error { var errs []*field.Error diff --git a/workspaces/controller/internal/webhook/workspacekind_webhook_test.go b/workspaces/controller/internal/webhook/workspacekind_webhook_test.go index f53e34f2..dd2c321a 100644 --- a/workspaces/controller/internal/webhook/workspacekind_webhook_test.go +++ b/workspaces/controller/internal/webhook/workspacekind_webhook_test.go @@ -53,6 +53,16 @@ var _ = Describe("WorkspaceKind Webhook", func() { workspaceKind: NewExampleWorkspaceKind("wsk-webhook-create--valid"), shouldSucceed: true, }, + { + description: "should reject creation with invalid podMetadata label key", + workspaceKind: NewExampleWorkspaceKindWithInvalidPodMetadataLabelKey("wsk-webhook-create--invalid-pod-metadata--label-key"), + shouldSucceed: false, + }, + { + description: "should reject creation with invalid podMetadata annotation key", + workspaceKind: NewExampleWorkspaceKindWithInvalidPodMetadataAnnotationKey("wsk-webhook-create--invalid-pod-metadata--annotation-key"), + shouldSucceed: false, + }, { description: "should reject creation with cycle in imageConfig redirects", workspaceKind: NewExampleWorkspaceKindWithImageConfigCycle("wsk-webhook-create--image-config-cycle"), @@ -498,6 +508,32 @@ var _ = Describe("WorkspaceKind Webhook", func() { return ContainSubstring("port %d is defined more than once", duplicatePortNumber) }, }, + { + description: "should reject updating a podMetadata.labels key to an invalid value", + shouldSucceed: false, + + workspaceKind: NewExampleWorkspaceKind(workspaceKindName), + modifyKindFn: func(wsk *kubefloworgv1beta1.WorkspaceKind) gomegaTypes.GomegaMatcher { + invalidKey := "!bad-key!" + wsk.Spec.PodTemplate.PodMetadata.Labels = map[string]string{ + invalidKey: "some-value", + } + return ContainSubstring("Invalid value: %q", invalidKey) + }, + }, + { + description: "should reject updating a podMetadata.annotations key to an invalid value", + shouldSucceed: false, + + workspaceKind: NewExampleWorkspaceKind(workspaceKindName), + modifyKindFn: func(wsk *kubefloworgv1beta1.WorkspaceKind) gomegaTypes.GomegaMatcher { + invalidAnnotationKey := "!bad-key!" + wsk.Spec.PodTemplate.PodMetadata.Annotations = map[string]string{ + invalidAnnotationKey: "some-value", + } + return ContainSubstring("Invalid value: %q", invalidAnnotationKey) + }, + }, { description: "should reject updating an extraEnv[].value to an invalid Go template", shouldSucceed: false, From eb70a942742ca61b2e72547d42f07bc047bdc719 Mon Sep 17 00:00:00 2001 From: Liav Weiss <74174727+liavweiss@users.noreply.github.com> Date: Thu, 26 Jun 2025 22:02:17 +0300 Subject: [PATCH 14/68] feat(ws): fix swagger warnings and only generate json output (#424) * feat(ws): Clean and fix swagger warnings and errors Signed-off-by: Liav Weiss (EXT-Nokia) * feat(ws): Clean and fix swagger warnings and errors Signed-off-by: Liav Weiss (EXT-Nokia) --------- Signed-off-by: Liav Weiss (EXT-Nokia) Co-authored-by: Liav Weiss (EXT-Nokia) --- workspaces/backend/Makefile | 2 +- .../backend/api/workspacekinds_handler.go | 4 +- workspaces/backend/api/workspaces_handler.go | 16 +- workspaces/backend/openapi/docs.go | 52 +- workspaces/backend/openapi/swagger.json | 52 +- workspaces/backend/openapi/swagger.yaml | 934 ------------------ 6 files changed, 26 insertions(+), 1034 deletions(-) delete mode 100644 workspaces/backend/openapi/swagger.yaml diff --git a/workspaces/backend/Makefile b/workspaces/backend/Makefile index 959dbe3d..fc0869b9 100644 --- a/workspaces/backend/Makefile +++ b/workspaces/backend/Makefile @@ -74,7 +74,7 @@ SWAG_DIRS := cmd,$(ALL_GO_DIRS_NO_CMD) .PHONY: swag swag: SWAGGER $(SWAGGER) fmt -g main.go -d $(SWAG_DIRS) - $(SWAGGER) init --parseDependency -q -g main.go -d $(SWAG_DIRS) --output openapi + $(SWAGGER) init --parseDependency -q -g main.go -d $(SWAG_DIRS) --output openapi --outputTypes go,json ##@ Build diff --git a/workspaces/backend/api/workspacekinds_handler.go b/workspaces/backend/api/workspacekinds_handler.go index b19fc327..c8e0b6af 100644 --- a/workspaces/backend/api/workspacekinds_handler.go +++ b/workspaces/backend/api/workspacekinds_handler.go @@ -42,7 +42,7 @@ type WorkspaceKindEnvelope Envelope[models.WorkspaceKind] // @Tags workspacekinds // @Accept json // @Produce json -// @Param name path string true "Name of the workspace kind" example(jupyterlab) +// @Param name path string true "Name of the workspace kind" extensions(x-example=jupyterlab) // @Success 200 {object} WorkspaceKindEnvelope "Successful operation. Returns the requested workspace kind details." // @Failure 400 {object} ErrorEnvelope "Bad Request. Invalid workspace kind name format." // @Failure 401 {object} ErrorEnvelope "Unauthorized. Authentication is required." @@ -50,7 +50,6 @@ type WorkspaceKindEnvelope Envelope[models.WorkspaceKind] // @Failure 404 {object} ErrorEnvelope "Not Found. Workspace kind does not exist." // @Failure 500 {object} ErrorEnvelope "Internal server error. An unexpected error occurred on the server." // @Router /workspacekinds/{name} [get] -// @Security ApiKeyAuth func (a *App) GetWorkspaceKindHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { name := ps.ByName(ResourceNamePathParam) @@ -102,7 +101,6 @@ func (a *App) GetWorkspaceKindHandler(w http.ResponseWriter, r *http.Request, ps // @Failure 403 {object} ErrorEnvelope "Forbidden. User does not have permission to list workspace kinds." // @Failure 500 {object} ErrorEnvelope "Internal server error. An unexpected error occurred on the server." // @Router /workspacekinds [get] -// @Security ApiKeyAuth func (a *App) GetWorkspaceKindsHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { // =========================== AUTH =========================== authPolicies := []*auth.ResourcePolicy{ diff --git a/workspaces/backend/api/workspaces_handler.go b/workspaces/backend/api/workspaces_handler.go index a64c04c7..2cf909fd 100644 --- a/workspaces/backend/api/workspaces_handler.go +++ b/workspaces/backend/api/workspaces_handler.go @@ -46,8 +46,8 @@ type WorkspaceEnvelope Envelope[models.Workspace] // @Tags workspaces // @Accept json // @Produce json -// @Param namespace path string true "Namespace of the workspace" example(kubeflow-user-example-com) -// @Param workspace_name path string true "Name of the workspace" example(my-workspace) +// @Param namespace path string true "Namespace of the workspace" extensions(x-example=kubeflow-user-example-com) +// @Param workspace_name path string true "Name of the workspace" extensions(x-example=my-workspace) // @Success 200 {object} WorkspaceEnvelope "Successful operation. Returns the requested workspace details." // @Failure 400 {object} ErrorEnvelope "Bad Request. Invalid namespace or workspace name format." // @Failure 401 {object} ErrorEnvelope "Unauthorized. Authentication is required." @@ -55,7 +55,6 @@ type WorkspaceEnvelope Envelope[models.Workspace] // @Failure 404 {object} ErrorEnvelope "Not Found. Workspace does not exist." // @Failure 500 {object} ErrorEnvelope "Internal server error. An unexpected error occurred on the server." // @Router /workspaces/{namespace}/{workspace_name} [get] -// @Security ApiKeyAuth func (a *App) GetWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { namespace := ps.ByName(NamespacePathParam) workspaceName := ps.ByName(ResourceNamePathParam) @@ -109,7 +108,7 @@ func (a *App) GetWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps htt // @Tags workspaces // @Accept json // @Produce json -// @Param namespace path string false "Namespace to filter workspaces. If not provided, returns all workspaces across all namespaces." example(kubeflow-user-example-com) +// @Param namespace path string true "Namespace to filter workspaces. If not provided, returns all workspaces across all namespaces." extensions(x-example=kubeflow-user-example-com) // @Success 200 {object} WorkspaceListEnvelope "Successful operation. Returns a list of workspaces." // @Failure 400 {object} ErrorEnvelope "Bad Request. Invalid namespace format." // @Failure 401 {object} ErrorEnvelope "Unauthorized. Authentication is required." @@ -117,7 +116,6 @@ func (a *App) GetWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps htt // @Failure 500 {object} ErrorEnvelope "Internal server error. An unexpected error occurred on the server." // @Router /workspaces [get] // @Router /workspaces/{namespace} [get] -// @Security ApiKeyAuth func (a *App) GetWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { namespace := ps.ByName(NamespacePathParam) @@ -171,7 +169,7 @@ func (a *App) GetWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps ht // @Tags workspaces // @Accept json // @Produce json -// @Param namespace path string true "Namespace for the workspace" example(kubeflow-user-example-com) +// @Param namespace path string true "Namespace for the workspace" extensions(x-example=kubeflow-user-example-com) // @Param body body WorkspaceCreateEnvelope true "Workspace creation configuration" // @Success 201 {object} WorkspaceEnvelope "Workspace created successfully" // @Failure 400 {object} ErrorEnvelope "Bad Request. Invalid request body or namespace format." @@ -180,7 +178,6 @@ func (a *App) GetWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps ht // @Failure 409 {object} ErrorEnvelope "Conflict. Workspace with the same name already exists." // @Failure 500 {object} ErrorEnvelope "Internal server error. An unexpected error occurred on the server." // @Router /workspaces/{namespace} [post] -// @Security ApiKeyAuth func (a *App) CreateWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { namespace := ps.ByName(NamespacePathParam) @@ -266,8 +263,8 @@ func (a *App) CreateWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps // @Tags workspaces // @Accept json // @Produce json -// @Param namespace path string true "Namespace of the workspace" example(kubeflow-user-example-com) -// @Param workspace_name path string true "Name of the workspace" example(my-workspace) +// @Param namespace path string true "Namespace of the workspace" extensions(x-example=kubeflow-user-example-com) +// @Param workspace_name path string true "Name of the workspace" extensions(x-example=my-workspace) // @Success 204 {object} nil "Workspace deleted successfully" // @Failure 400 {object} ErrorEnvelope "Bad Request. Invalid namespace or workspace name format." // @Failure 401 {object} ErrorEnvelope "Unauthorized. Authentication is required." @@ -275,7 +272,6 @@ func (a *App) CreateWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps // @Failure 404 {object} ErrorEnvelope "Not Found. Workspace does not exist." // @Failure 500 {object} ErrorEnvelope "Internal server error. An unexpected error occurred on the server." // @Router /workspaces/{namespace}/{workspace_name} [delete] -// @Security ApiKeyAuth func (a *App) DeleteWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { namespace := ps.ByName(NamespacePathParam) workspaceName := ps.ByName(ResourceNamePathParam) diff --git a/workspaces/backend/openapi/docs.go b/workspaces/backend/openapi/docs.go index f255e86d..e0fc73e4 100644 --- a/workspaces/backend/openapi/docs.go +++ b/workspaces/backend/openapi/docs.go @@ -85,11 +85,6 @@ const docTemplate = `{ }, "/workspacekinds": { "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], "description": "Returns a list of all available workspace kinds. Workspace kinds define the different types of workspaces that can be created in the system.", "consumes": [ "application/json" @@ -131,11 +126,6 @@ const docTemplate = `{ }, "/workspacekinds/{name}": { "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], "description": "Returns details of a specific workspace kind identified by its name. Workspace kinds define the available types of workspaces that can be created.", "consumes": [ "application/json" @@ -150,7 +140,7 @@ const docTemplate = `{ "parameters": [ { "type": "string", - "example": "jupyterlab", + "x-example": "jupyterlab", "description": "Name of the workspace kind", "name": "name", "in": "path", @@ -199,11 +189,6 @@ const docTemplate = `{ }, "/workspaces": { "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], "description": "Returns a list of workspaces. The endpoint supports two modes:\n1. List all workspaces across all namespaces (when no namespace is provided)\n2. List workspaces in a specific namespace (when namespace is provided)", "consumes": [ "application/json" @@ -251,11 +236,6 @@ const docTemplate = `{ }, "/workspaces/{namespace}": { "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], "description": "Returns a list of workspaces. The endpoint supports two modes:\n1. List all workspaces across all namespaces (when no namespace is provided)\n2. List workspaces in a specific namespace (when namespace is provided)", "consumes": [ "application/json" @@ -270,10 +250,11 @@ const docTemplate = `{ "parameters": [ { "type": "string", - "example": "kubeflow-user-example-com", + "x-example": "kubeflow-user-example-com", "description": "Namespace to filter workspaces. If not provided, returns all workspaces across all namespaces.", "name": "namespace", - "in": "path" + "in": "path", + "required": true } ], "responses": { @@ -310,11 +291,6 @@ const docTemplate = `{ } }, "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], "description": "Creates a new workspace in the specified namespace.", "consumes": [ "application/json" @@ -329,7 +305,7 @@ const docTemplate = `{ "parameters": [ { "type": "string", - "example": "kubeflow-user-example-com", + "x-example": "kubeflow-user-example-com", "description": "Namespace for the workspace", "name": "namespace", "in": "path", @@ -387,11 +363,6 @@ const docTemplate = `{ }, "/workspaces/{namespace}/{workspace_name}": { "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], "description": "Returns details of a specific workspace identified by namespace and workspace name.", "consumes": [ "application/json" @@ -406,7 +377,7 @@ const docTemplate = `{ "parameters": [ { "type": "string", - "example": "kubeflow-user-example-com", + "x-example": "kubeflow-user-example-com", "description": "Namespace of the workspace", "name": "namespace", "in": "path", @@ -414,7 +385,7 @@ const docTemplate = `{ }, { "type": "string", - "example": "my-workspace", + "x-example": "my-workspace", "description": "Name of the workspace", "name": "workspace_name", "in": "path", @@ -461,11 +432,6 @@ const docTemplate = `{ } }, "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], "description": "Deletes a specific workspace identified by namespace and workspace name.", "consumes": [ "application/json" @@ -480,7 +446,7 @@ const docTemplate = `{ "parameters": [ { "type": "string", - "example": "kubeflow-user-example-com", + "x-example": "kubeflow-user-example-com", "description": "Namespace of the workspace", "name": "namespace", "in": "path", @@ -488,7 +454,7 @@ const docTemplate = `{ }, { "type": "string", - "example": "my-workspace", + "x-example": "my-workspace", "description": "Name of the workspace", "name": "workspace_name", "in": "path", diff --git a/workspaces/backend/openapi/swagger.json b/workspaces/backend/openapi/swagger.json index ef67382c..4f9fdeff 100644 --- a/workspaces/backend/openapi/swagger.json +++ b/workspaces/backend/openapi/swagger.json @@ -83,11 +83,6 @@ }, "/workspacekinds": { "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], "description": "Returns a list of all available workspace kinds. Workspace kinds define the different types of workspaces that can be created in the system.", "consumes": [ "application/json" @@ -129,11 +124,6 @@ }, "/workspacekinds/{name}": { "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], "description": "Returns details of a specific workspace kind identified by its name. Workspace kinds define the available types of workspaces that can be created.", "consumes": [ "application/json" @@ -148,7 +138,7 @@ "parameters": [ { "type": "string", - "example": "jupyterlab", + "x-example": "jupyterlab", "description": "Name of the workspace kind", "name": "name", "in": "path", @@ -197,11 +187,6 @@ }, "/workspaces": { "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], "description": "Returns a list of workspaces. The endpoint supports two modes:\n1. List all workspaces across all namespaces (when no namespace is provided)\n2. List workspaces in a specific namespace (when namespace is provided)", "consumes": [ "application/json" @@ -249,11 +234,6 @@ }, "/workspaces/{namespace}": { "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], "description": "Returns a list of workspaces. The endpoint supports two modes:\n1. List all workspaces across all namespaces (when no namespace is provided)\n2. List workspaces in a specific namespace (when namespace is provided)", "consumes": [ "application/json" @@ -268,10 +248,11 @@ "parameters": [ { "type": "string", - "example": "kubeflow-user-example-com", + "x-example": "kubeflow-user-example-com", "description": "Namespace to filter workspaces. If not provided, returns all workspaces across all namespaces.", "name": "namespace", - "in": "path" + "in": "path", + "required": true } ], "responses": { @@ -308,11 +289,6 @@ } }, "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], "description": "Creates a new workspace in the specified namespace.", "consumes": [ "application/json" @@ -327,7 +303,7 @@ "parameters": [ { "type": "string", - "example": "kubeflow-user-example-com", + "x-example": "kubeflow-user-example-com", "description": "Namespace for the workspace", "name": "namespace", "in": "path", @@ -385,11 +361,6 @@ }, "/workspaces/{namespace}/{workspace_name}": { "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], "description": "Returns details of a specific workspace identified by namespace and workspace name.", "consumes": [ "application/json" @@ -404,7 +375,7 @@ "parameters": [ { "type": "string", - "example": "kubeflow-user-example-com", + "x-example": "kubeflow-user-example-com", "description": "Namespace of the workspace", "name": "namespace", "in": "path", @@ -412,7 +383,7 @@ }, { "type": "string", - "example": "my-workspace", + "x-example": "my-workspace", "description": "Name of the workspace", "name": "workspace_name", "in": "path", @@ -459,11 +430,6 @@ } }, "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], "description": "Deletes a specific workspace identified by namespace and workspace name.", "consumes": [ "application/json" @@ -478,7 +444,7 @@ "parameters": [ { "type": "string", - "example": "kubeflow-user-example-com", + "x-example": "kubeflow-user-example-com", "description": "Namespace of the workspace", "name": "namespace", "in": "path", @@ -486,7 +452,7 @@ }, { "type": "string", - "example": "my-workspace", + "x-example": "my-workspace", "description": "Name of the workspace", "name": "workspace_name", "in": "path", diff --git a/workspaces/backend/openapi/swagger.yaml b/workspaces/backend/openapi/swagger.yaml deleted file mode 100644 index 9cae7636..00000000 --- a/workspaces/backend/openapi/swagger.yaml +++ /dev/null @@ -1,934 +0,0 @@ -basePath: /api/v1 -definitions: - api.ErrorCause: - properties: - validation_errors: - items: - $ref: '#/definitions/api.ValidationError' - type: array - type: object - api.ErrorEnvelope: - properties: - error: - $ref: '#/definitions/api.HTTPError' - type: object - api.HTTPError: - properties: - cause: - $ref: '#/definitions/api.ErrorCause' - code: - type: string - message: - type: string - type: object - api.NamespaceListEnvelope: - properties: - data: - items: - $ref: '#/definitions/namespaces.Namespace' - type: array - type: object - api.ValidationError: - properties: - field: - type: string - message: - type: string - type: - $ref: '#/definitions/field.ErrorType' - type: object - api.WorkspaceCreateEnvelope: - properties: - data: - $ref: '#/definitions/workspaces.WorkspaceCreate' - type: object - api.WorkspaceEnvelope: - properties: - data: - $ref: '#/definitions/workspaces.Workspace' - type: object - api.WorkspaceKindEnvelope: - properties: - data: - $ref: '#/definitions/workspacekinds.WorkspaceKind' - type: object - api.WorkspaceKindListEnvelope: - properties: - data: - items: - $ref: '#/definitions/workspacekinds.WorkspaceKind' - type: array - type: object - api.WorkspaceListEnvelope: - properties: - data: - items: - $ref: '#/definitions/workspaces.Workspace' - type: array - type: object - field.ErrorType: - enum: - - FieldValueNotFound - - FieldValueRequired - - FieldValueDuplicate - - FieldValueInvalid - - FieldValueNotSupported - - FieldValueForbidden - - FieldValueTooLong - - FieldValueTooMany - - InternalError - - FieldValueTypeInvalid - type: string - x-enum-varnames: - - ErrorTypeNotFound - - ErrorTypeRequired - - ErrorTypeDuplicate - - ErrorTypeInvalid - - ErrorTypeNotSupported - - ErrorTypeForbidden - - ErrorTypeTooLong - - ErrorTypeTooMany - - ErrorTypeInternal - - ErrorTypeTypeInvalid - health_check.HealthCheck: - properties: - status: - $ref: '#/definitions/health_check.ServiceStatus' - systemInfo: - $ref: '#/definitions/health_check.SystemInfo' - type: object - health_check.ServiceStatus: - enum: - - Healthy - - Unhealthy - type: string - x-enum-varnames: - - ServiceStatusHealthy - - ServiceStatusUnhealthy - health_check.SystemInfo: - properties: - version: - type: string - type: object - namespaces.Namespace: - properties: - name: - type: string - type: object - workspacekinds.ImageConfig: - properties: - default: - type: string - values: - items: - $ref: '#/definitions/workspacekinds.ImageConfigValue' - type: array - type: object - workspacekinds.ImageConfigValue: - properties: - description: - type: string - displayName: - type: string - hidden: - type: boolean - id: - type: string - labels: - items: - $ref: '#/definitions/workspacekinds.OptionLabel' - type: array - redirect: - $ref: '#/definitions/workspacekinds.OptionRedirect' - type: object - workspacekinds.ImageRef: - properties: - url: - type: string - type: object - workspacekinds.OptionLabel: - properties: - key: - type: string - value: - type: string - type: object - workspacekinds.OptionRedirect: - properties: - message: - $ref: '#/definitions/workspacekinds.RedirectMessage' - to: - type: string - type: object - workspacekinds.PodConfig: - properties: - default: - type: string - values: - items: - $ref: '#/definitions/workspacekinds.PodConfigValue' - type: array - type: object - workspacekinds.PodConfigValue: - properties: - description: - type: string - displayName: - type: string - hidden: - type: boolean - id: - type: string - labels: - items: - $ref: '#/definitions/workspacekinds.OptionLabel' - type: array - redirect: - $ref: '#/definitions/workspacekinds.OptionRedirect' - type: object - workspacekinds.PodMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - workspacekinds.PodTemplate: - properties: - options: - $ref: '#/definitions/workspacekinds.PodTemplateOptions' - podMetadata: - $ref: '#/definitions/workspacekinds.PodMetadata' - volumeMounts: - $ref: '#/definitions/workspacekinds.PodVolumeMounts' - type: object - workspacekinds.PodTemplateOptions: - properties: - imageConfig: - $ref: '#/definitions/workspacekinds.ImageConfig' - podConfig: - $ref: '#/definitions/workspacekinds.PodConfig' - type: object - workspacekinds.PodVolumeMounts: - properties: - home: - type: string - type: object - workspacekinds.RedirectMessage: - properties: - level: - $ref: '#/definitions/workspacekinds.RedirectMessageLevel' - text: - type: string - type: object - workspacekinds.RedirectMessageLevel: - enum: - - Info - - Warning - - Danger - type: string - x-enum-varnames: - - RedirectMessageLevelInfo - - RedirectMessageLevelWarning - - RedirectMessageLevelDanger - workspacekinds.WorkspaceKind: - properties: - deprecated: - type: boolean - deprecationMessage: - type: string - description: - type: string - displayName: - type: string - hidden: - type: boolean - icon: - $ref: '#/definitions/workspacekinds.ImageRef' - logo: - $ref: '#/definitions/workspacekinds.ImageRef' - name: - type: string - podTemplate: - $ref: '#/definitions/workspacekinds.PodTemplate' - type: object - workspaces.Activity: - properties: - lastActivity: - description: Unix Epoch time - type: integer - lastProbe: - $ref: '#/definitions/workspaces.LastProbeInfo' - lastUpdate: - description: Unix Epoch time - type: integer - type: object - workspaces.HttpService: - properties: - displayName: - type: string - httpPath: - type: string - type: object - workspaces.ImageConfig: - properties: - current: - $ref: '#/definitions/workspaces.OptionInfo' - desired: - $ref: '#/definitions/workspaces.OptionInfo' - redirectChain: - items: - $ref: '#/definitions/workspaces.RedirectStep' - type: array - type: object - workspaces.ImageRef: - properties: - url: - type: string - type: object - workspaces.LastProbeInfo: - properties: - endTimeMs: - description: Unix Epoch time in milliseconds - type: integer - message: - type: string - result: - $ref: '#/definitions/workspaces.ProbeResult' - startTimeMs: - description: Unix Epoch time in milliseconds - type: integer - type: object - workspaces.OptionInfo: - properties: - description: - type: string - displayName: - type: string - id: - type: string - labels: - items: - $ref: '#/definitions/workspaces.OptionLabel' - type: array - type: object - workspaces.OptionLabel: - properties: - key: - type: string - value: - type: string - type: object - workspaces.PodConfig: - properties: - current: - $ref: '#/definitions/workspaces.OptionInfo' - desired: - $ref: '#/definitions/workspaces.OptionInfo' - redirectChain: - items: - $ref: '#/definitions/workspaces.RedirectStep' - type: array - type: object - workspaces.PodMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - workspaces.PodMetadataMutate: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - workspaces.PodSecretInfo: - properties: - defaultMode: - type: integer - mountPath: - type: string - secretName: - type: string - type: object - workspaces.PodSecretMount: - properties: - defaultMode: - type: integer - mountPath: - type: string - secretName: - type: string - type: object - workspaces.PodTemplate: - properties: - options: - $ref: '#/definitions/workspaces.PodTemplateOptions' - podMetadata: - $ref: '#/definitions/workspaces.PodMetadata' - volumes: - $ref: '#/definitions/workspaces.PodVolumes' - type: object - workspaces.PodTemplateMutate: - properties: - options: - $ref: '#/definitions/workspaces.PodTemplateOptionsMutate' - podMetadata: - $ref: '#/definitions/workspaces.PodMetadataMutate' - volumes: - $ref: '#/definitions/workspaces.PodVolumesMutate' - type: object - workspaces.PodTemplateOptions: - properties: - imageConfig: - $ref: '#/definitions/workspaces.ImageConfig' - podConfig: - $ref: '#/definitions/workspaces.PodConfig' - type: object - workspaces.PodTemplateOptionsMutate: - properties: - imageConfig: - type: string - podConfig: - type: string - type: object - workspaces.PodVolumeInfo: - properties: - mountPath: - type: string - pvcName: - type: string - readOnly: - type: boolean - type: object - workspaces.PodVolumeMount: - properties: - mountPath: - type: string - pvcName: - type: string - readOnly: - type: boolean - type: object - workspaces.PodVolumes: - properties: - data: - items: - $ref: '#/definitions/workspaces.PodVolumeInfo' - type: array - home: - $ref: '#/definitions/workspaces.PodVolumeInfo' - secrets: - items: - $ref: '#/definitions/workspaces.PodSecretInfo' - type: array - type: object - workspaces.PodVolumesMutate: - properties: - data: - items: - $ref: '#/definitions/workspaces.PodVolumeMount' - type: array - home: - type: string - secrets: - items: - $ref: '#/definitions/workspaces.PodSecretMount' - type: array - type: object - workspaces.ProbeResult: - enum: - - Success - - Failure - - Timeout - type: string - x-enum-varnames: - - ProbeResultSuccess - - ProbeResultFailure - - ProbeResultTimeout - workspaces.RedirectMessage: - properties: - level: - $ref: '#/definitions/workspaces.RedirectMessageLevel' - text: - type: string - type: object - workspaces.RedirectMessageLevel: - enum: - - Info - - Warning - - Danger - type: string - x-enum-varnames: - - RedirectMessageLevelInfo - - RedirectMessageLevelWarning - - RedirectMessageLevelDanger - workspaces.RedirectStep: - properties: - message: - $ref: '#/definitions/workspaces.RedirectMessage' - sourceId: - type: string - targetId: - type: string - type: object - workspaces.Service: - properties: - httpService: - $ref: '#/definitions/workspaces.HttpService' - type: object - workspaces.Workspace: - properties: - activity: - $ref: '#/definitions/workspaces.Activity' - deferUpdates: - type: boolean - name: - type: string - namespace: - type: string - paused: - type: boolean - pausedTime: - type: integer - pendingRestart: - type: boolean - podTemplate: - $ref: '#/definitions/workspaces.PodTemplate' - services: - items: - $ref: '#/definitions/workspaces.Service' - type: array - state: - $ref: '#/definitions/workspaces.WorkspaceState' - stateMessage: - type: string - workspaceKind: - $ref: '#/definitions/workspaces.WorkspaceKindInfo' - type: object - workspaces.WorkspaceCreate: - properties: - deferUpdates: - type: boolean - kind: - type: string - name: - type: string - paused: - type: boolean - podTemplate: - $ref: '#/definitions/workspaces.PodTemplateMutate' - type: object - workspaces.WorkspaceKindInfo: - properties: - icon: - $ref: '#/definitions/workspaces.ImageRef' - logo: - $ref: '#/definitions/workspaces.ImageRef' - missing: - type: boolean - name: - type: string - type: object - workspaces.WorkspaceState: - enum: - - Running - - Terminating - - Paused - - Pending - - Error - - Unknown - type: string - x-enum-varnames: - - WorkspaceStateRunning - - WorkspaceStateTerminating - - WorkspaceStatePaused - - WorkspaceStatePending - - WorkspaceStateError - - WorkspaceStateUnknown -host: localhost:4000 -info: - contact: {} - description: |- - This API provides endpoints to manage notebooks in a Kubernetes cluster. - For more information, visit https://www.kubeflow.org/docs/components/notebooks/ - license: - name: Apache 2.0 - url: http://www.apache.org/licenses/LICENSE-2.0.html - title: Kubeflow Notebooks API - version: 1.0.0 -paths: - /healthcheck: - get: - description: Provides a healthcheck response indicating the status of key services. - produces: - - application/json - responses: - "200": - description: Successful healthcheck response - schema: - $ref: '#/definitions/health_check.HealthCheck' - "500": - description: Internal server error - schema: - $ref: '#/definitions/api.ErrorEnvelope' - summary: Returns the health status of the application - tags: - - healthcheck - /namespaces: - get: - description: Provides a list of all namespaces that the user has access to - produces: - - application/json - responses: - "200": - description: Successful namespaces response - schema: - $ref: '#/definitions/api.NamespaceListEnvelope' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "403": - description: Forbidden - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "500": - description: Internal server error - schema: - $ref: '#/definitions/api.ErrorEnvelope' - summary: Returns a list of all namespaces - tags: - - namespaces - /workspacekinds: - get: - consumes: - - application/json - description: Returns a list of all available workspace kinds. Workspace kinds - define the different types of workspaces that can be created in the system. - produces: - - application/json - responses: - "200": - description: Successful operation. Returns a list of all available workspace - kinds. - schema: - $ref: '#/definitions/api.WorkspaceKindListEnvelope' - "401": - description: Unauthorized. Authentication is required. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "403": - description: Forbidden. User does not have permission to list workspace - kinds. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "500": - description: Internal server error. An unexpected error occurred on the - server. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - security: - - ApiKeyAuth: [] - summary: List workspace kinds - tags: - - workspacekinds - /workspacekinds/{name}: - get: - consumes: - - application/json - description: Returns details of a specific workspace kind identified by its - name. Workspace kinds define the available types of workspaces that can be - created. - parameters: - - description: Name of the workspace kind - example: jupyterlab - in: path - name: name - required: true - type: string - produces: - - application/json - responses: - "200": - description: Successful operation. Returns the requested workspace kind - details. - schema: - $ref: '#/definitions/api.WorkspaceKindEnvelope' - "400": - description: Bad Request. Invalid workspace kind name format. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "401": - description: Unauthorized. Authentication is required. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "403": - description: Forbidden. User does not have permission to access the workspace - kind. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "404": - description: Not Found. Workspace kind does not exist. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "500": - description: Internal server error. An unexpected error occurred on the - server. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - security: - - ApiKeyAuth: [] - summary: Get workspace kind - tags: - - workspacekinds - /workspaces: - get: - consumes: - - application/json - description: |- - Returns a list of workspaces. The endpoint supports two modes: - 1. List all workspaces across all namespaces (when no namespace is provided) - 2. List workspaces in a specific namespace (when namespace is provided) - produces: - - application/json - responses: - "200": - description: Successful operation. Returns a list of workspaces. - schema: - $ref: '#/definitions/api.WorkspaceListEnvelope' - "400": - description: Bad Request. Invalid namespace format. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "401": - description: Unauthorized. Authentication is required. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "403": - description: Forbidden. User does not have permission to list workspaces. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "500": - description: Internal server error. An unexpected error occurred on the - server. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - security: - - ApiKeyAuth: [] - summary: List workspaces - tags: - - workspaces - /workspaces/{namespace}: - get: - consumes: - - application/json - description: |- - Returns a list of workspaces. The endpoint supports two modes: - 1. List all workspaces across all namespaces (when no namespace is provided) - 2. List workspaces in a specific namespace (when namespace is provided) - parameters: - - description: Namespace to filter workspaces. If not provided, returns all - workspaces across all namespaces. - example: kubeflow-user-example-com - in: path - name: namespace - type: string - produces: - - application/json - responses: - "200": - description: Successful operation. Returns a list of workspaces. - schema: - $ref: '#/definitions/api.WorkspaceListEnvelope' - "400": - description: Bad Request. Invalid namespace format. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "401": - description: Unauthorized. Authentication is required. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "403": - description: Forbidden. User does not have permission to list workspaces. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "500": - description: Internal server error. An unexpected error occurred on the - server. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - security: - - ApiKeyAuth: [] - summary: List workspaces - tags: - - workspaces - post: - consumes: - - application/json - description: Creates a new workspace in the specified namespace. - parameters: - - description: Namespace for the workspace - example: kubeflow-user-example-com - in: path - name: namespace - required: true - type: string - - description: Workspace creation configuration - in: body - name: body - required: true - schema: - $ref: '#/definitions/api.WorkspaceCreateEnvelope' - produces: - - application/json - responses: - "201": - description: Workspace created successfully - schema: - $ref: '#/definitions/api.WorkspaceEnvelope' - "400": - description: Bad Request. Invalid request body or namespace format. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "401": - description: Unauthorized. Authentication is required. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "403": - description: Forbidden. User does not have permission to create workspace. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "409": - description: Conflict. Workspace with the same name already exists. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "500": - description: Internal server error. An unexpected error occurred on the - server. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - security: - - ApiKeyAuth: [] - summary: Create workspace - tags: - - workspaces - /workspaces/{namespace}/{workspace_name}: - delete: - consumes: - - application/json - description: Deletes a specific workspace identified by namespace and workspace - name. - parameters: - - description: Namespace of the workspace - example: kubeflow-user-example-com - in: path - name: namespace - required: true - type: string - - description: Name of the workspace - example: my-workspace - in: path - name: workspace_name - required: true - type: string - produces: - - application/json - responses: - "204": - description: Workspace deleted successfully - "400": - description: Bad Request. Invalid namespace or workspace name format. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "401": - description: Unauthorized. Authentication is required. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "403": - description: Forbidden. User does not have permission to delete the workspace. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "404": - description: Not Found. Workspace does not exist. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "500": - description: Internal server error. An unexpected error occurred on the - server. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - security: - - ApiKeyAuth: [] - summary: Delete workspace - tags: - - workspaces - get: - consumes: - - application/json - description: Returns details of a specific workspace identified by namespace - and workspace name. - parameters: - - description: Namespace of the workspace - example: kubeflow-user-example-com - in: path - name: namespace - required: true - type: string - - description: Name of the workspace - example: my-workspace - in: path - name: workspace_name - required: true - type: string - produces: - - application/json - responses: - "200": - description: Successful operation. Returns the requested workspace details. - schema: - $ref: '#/definitions/api.WorkspaceEnvelope' - "400": - description: Bad Request. Invalid namespace or workspace name format. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "401": - description: Unauthorized. Authentication is required. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "403": - description: Forbidden. User does not have permission to access the workspace. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "404": - description: Not Found. Workspace does not exist. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - "500": - description: Internal server error. An unexpected error occurred on the - server. - schema: - $ref: '#/definitions/api.ErrorEnvelope' - security: - - ApiKeyAuth: [] - summary: Get workspace - tags: - - workspaces -schemes: -- http -- https -swagger: "2.0" From 48b690ed983ce2cd0eff4590108c3ca0eef036a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Dan=C4=9Bk?= Date: Thu, 26 Jun 2025 21:08:17 +0200 Subject: [PATCH 15/68] ci(ws): archive frontend cypress test results in github actions (#396) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jiri Daněk --- .github/workflows/ws-frontend-test.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.github/workflows/ws-frontend-test.yml b/.github/workflows/ws-frontend-test.yml index 1f935d2b..5e3b5bec 100644 --- a/.github/workflows/ws-frontend-test.yml +++ b/.github/workflows/ws-frontend-test.yml @@ -42,8 +42,34 @@ jobs: - name: Run tests working-directory: workspaces/frontend + # use id to skip archiving artifacts if running the tests was skipped, usually due to failure in steps above + # https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#steps-context + id: run-tests run: npm run test + - name: Upload Cypress test report + uses: actions/upload-artifact@v4 + if: "!cancelled() && steps.run-tests.outcome != 'skipped'" + with: + name: cypress-report + path: | + workspaces/frontend/src/__tests__/cypress/results/mocked/index.html + workspaces/frontend/src/__tests__/cypress/results/mocked/junit-report.xml + + - name: Upload Cypress screenshots (on failure) + uses: actions/upload-artifact@v4 + if: "failure() && steps.run-tests.outcome == 'failure'" + with: + name: cypress-screenshots + path: workspaces/frontend/src/__tests__/cypress/results/mocked/screenshots + + - name: Upload Cypress video recordings (on failure) + uses: actions/upload-artifact@v4 + if: "failure() && steps.run-tests.outcome == 'failure'" + with: + name: cypress-videos + path: workspaces/frontend/src/__tests__/cypress/results/mocked/videos + - name: Check if there are uncommitted file changes working-directory: workspaces/frontend run: | From e3978c28f9b70359cd1f3189707cef720798bb4e Mon Sep 17 00:00:00 2001 From: Andy Stoneberg Date: Thu, 26 Jun 2025 16:49:17 -0400 Subject: [PATCH 16/68] chore: reference ghcr.io images in workspacekind yaml (#305) Given we have migrated all our images from docker.io to ghcr.io - our `notebooks-v2` branch should reference the "proper" container registry. This commit updates the code to use: - `ghcr.io/kubeflow/kubeflow/notebook-servers` Affected areas: - `jupyterlab_scipy_180` and `jupyterlab_scipy_190` `imageConfig` entries - various test files Signed-off-by: Andy Stoneberg --- workspaces/backend/api/suite_test.go | 4 ++-- workspaces/controller/api/v1beta1/workspacekind_types.go | 2 +- .../config/samples/jupyterlab_v1beta1_workspacekind.yaml | 4 ++-- workspaces/controller/internal/controller/suite_test.go | 4 ++-- workspaces/controller/internal/webhook/suite_test.go | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/workspaces/backend/api/suite_test.go b/workspaces/backend/api/suite_test.go index faf34962..ad3e518a 100644 --- a/workspaces/backend/api/suite_test.go +++ b/workspaces/backend/api/suite_test.go @@ -312,7 +312,7 @@ func NewExampleWorkspaceKind(name string) *kubefloworgv1beta1.WorkspaceKind { }, }, Spec: kubefloworgv1beta1.ImageConfigSpec{ - Image: "docker.io/kubeflownotebookswg/jupyter-scipy:v1.8.0", + Image: "ghcr.io/kubeflow/kubeflow/notebook-servers/jupyter-scipy:v1.8.0", Ports: []kubefloworgv1beta1.ImagePort{ { Id: "jupyterlab", @@ -337,7 +337,7 @@ func NewExampleWorkspaceKind(name string) *kubefloworgv1beta1.WorkspaceKind { }, }, Spec: kubefloworgv1beta1.ImageConfigSpec{ - Image: "docker.io/kubeflownotebookswg/jupyter-scipy:v1.9.0", + Image: "ghcr.io/kubeflow/kubeflow/notebook-servers/jupyter-scipy:v1.9.0", Ports: []kubefloworgv1beta1.ImagePort{ { Id: "jupyterlab", diff --git a/workspaces/controller/api/v1beta1/workspacekind_types.go b/workspaces/controller/api/v1beta1/workspacekind_types.go index 2d846237..7e7952a0 100644 --- a/workspaces/controller/api/v1beta1/workspacekind_types.go +++ b/workspaces/controller/api/v1beta1/workspacekind_types.go @@ -318,7 +318,7 @@ type ImageConfigValue struct { type ImageConfigSpec struct { // the container image to use // +kubebuilder:validation:MinLength:=2 - // +kubeflow:example="docker.io/kubeflownotebookswg/jupyter-scipy:v1.7.0" + // +kubeflow:example="ghcr.io/kubeflow/kubeflow/notebook-servers/jupyter-scipy:v1.7.0" Image string `json:"image"` // the pull policy for the container image diff --git a/workspaces/controller/config/samples/jupyterlab_v1beta1_workspacekind.yaml b/workspaces/controller/config/samples/jupyterlab_v1beta1_workspacekind.yaml index 71ed533b..d7bb858b 100644 --- a/workspaces/controller/config/samples/jupyterlab_v1beta1_workspacekind.yaml +++ b/workspaces/controller/config/samples/jupyterlab_v1beta1_workspacekind.yaml @@ -271,7 +271,7 @@ spec: spec: ## the container image to use ## - image: "docker.io/kubeflownotebookswg/jupyter-scipy:v1.8.0" + image: "ghcr.io/kubeflow/kubeflow/notebook-servers/jupyter-scipy:v1.8.0" ## the pull policy for the container image ## - default: "IfNotPresent" @@ -301,7 +301,7 @@ spec: - key: "python_version" value: "3.11" spec: - image: "docker.io/kubeflownotebookswg/jupyter-scipy:v1.9.0" + image: "ghcr.io/kubeflow/kubeflow/notebook-servers/jupyter-scipy:v1.9.0" imagePullPolicy: "IfNotPresent" ports: - id: "jupyterlab" diff --git a/workspaces/controller/internal/controller/suite_test.go b/workspaces/controller/internal/controller/suite_test.go index f89afed2..fbc5a591 100644 --- a/workspaces/controller/internal/controller/suite_test.go +++ b/workspaces/controller/internal/controller/suite_test.go @@ -288,7 +288,7 @@ func NewExampleWorkspaceKind1(name string) *kubefloworgv1beta1.WorkspaceKind { }, }, Spec: kubefloworgv1beta1.ImageConfigSpec{ - Image: "docker.io/kubeflownotebookswg/jupyter-scipy:v1.8.0", + Image: "ghcr.io/kubeflow/kubeflow/notebook-servers/jupyter-scipy:v1.8.0", Ports: []kubefloworgv1beta1.ImagePort{ { Id: "jupyterlab", @@ -313,7 +313,7 @@ func NewExampleWorkspaceKind1(name string) *kubefloworgv1beta1.WorkspaceKind { }, }, Spec: kubefloworgv1beta1.ImageConfigSpec{ - Image: "docker.io/kubeflownotebookswg/jupyter-scipy:v1.9.0", + Image: "ghcr.io/kubeflow/kubeflow/notebook-servers/jupyter-scipy:v1.9.0", Ports: []kubefloworgv1beta1.ImagePort{ { Id: "jupyterlab", diff --git a/workspaces/controller/internal/webhook/suite_test.go b/workspaces/controller/internal/webhook/suite_test.go index b0a652cf..2eee7839 100644 --- a/workspaces/controller/internal/webhook/suite_test.go +++ b/workspaces/controller/internal/webhook/suite_test.go @@ -276,7 +276,7 @@ func NewExampleWorkspaceKind(name string) *kubefloworgv1beta1.WorkspaceKind { }, }, Spec: kubefloworgv1beta1.ImageConfigSpec{ - Image: "docker.io/kubeflownotebookswg/jupyter-scipy:v1.8.0", + Image: "ghcr.io/kubeflow/kubeflow/notebook-servers/jupyter-scipy:v1.8.0", Ports: []kubefloworgv1beta1.ImagePort{ { Id: "jupyterlab", @@ -301,7 +301,7 @@ func NewExampleWorkspaceKind(name string) *kubefloworgv1beta1.WorkspaceKind { }, }, Spec: kubefloworgv1beta1.ImageConfigSpec{ - Image: "docker.io/kubeflownotebookswg/jupyter-scipy:v1.9.0", + Image: "ghcr.io/kubeflow/kubeflow/notebook-servers/jupyter-scipy:v1.9.0", Ports: []kubefloworgv1beta1.ImagePort{ { Id: "jupyterlab", From b6e664ccfff28fd9103a70bf0a983538b571e76a Mon Sep 17 00:00:00 2001 From: Mathew Wicks <5735406+thesuperzapper@users.noreply.github.com> Date: Thu, 26 Jun 2025 14:42:17 -0700 Subject: [PATCH 17/68] chore: add OWNERS files with reviewers and labels (#450) Signed-off-by: Mathew Wicks <5735406+thesuperzapper@users.noreply.github.com> --- .github/workflows/OWNERS | 4 ++++ workspaces/backend/OWNERS | 5 +++++ workspaces/controller/OWNERS | 5 +++++ workspaces/frontend/OWNERS | 7 ++++++- 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/OWNERS create mode 100644 workspaces/backend/OWNERS create mode 100644 workspaces/controller/OWNERS diff --git a/.github/workflows/OWNERS b/.github/workflows/OWNERS new file mode 100644 index 00000000..48a83b38 --- /dev/null +++ b/.github/workflows/OWNERS @@ -0,0 +1,4 @@ +labels: + - area/ci +reviewers: + - andyatmiami \ No newline at end of file diff --git a/workspaces/backend/OWNERS b/workspaces/backend/OWNERS new file mode 100644 index 00000000..c5234774 --- /dev/null +++ b/workspaces/backend/OWNERS @@ -0,0 +1,5 @@ +labels: + - area/backend + - area/v2 +reviewers: + - andyatmiami \ No newline at end of file diff --git a/workspaces/controller/OWNERS b/workspaces/controller/OWNERS new file mode 100644 index 00000000..2b20df06 --- /dev/null +++ b/workspaces/controller/OWNERS @@ -0,0 +1,5 @@ +labels: + - area/controller + - area/v2 +reviewers: + - andyatmiami \ No newline at end of file diff --git a/workspaces/frontend/OWNERS b/workspaces/frontend/OWNERS index f0904f51..656608ce 100644 --- a/workspaces/frontend/OWNERS +++ b/workspaces/frontend/OWNERS @@ -1,2 +1,7 @@ +labels: + - area/frontend + - area/v2 approvers: - - ederign \ No newline at end of file + - ederign +reviewers: + - paulovmr \ No newline at end of file From eae9e23a587b0260c7cd25a984ea9ef8fb8a0f6a Mon Sep 17 00:00:00 2001 From: Liav Weiss <74174727+liavweiss@users.noreply.github.com> Date: Fri, 27 Jun 2025 00:47:17 +0300 Subject: [PATCH 18/68] fix(ws): backend dockerfile (#386) * feat(ws): Properly containerize backend component #323 Signed-off-by: Liav Weiss (EXT-Nokia) * feat(ws): Properly containerize backend component #323 Signed-off-by: Liav Weiss (EXT-Nokia) * feat(ws): Properly containerize backend component #323 Signed-off-by: Liav Weiss (EXT-Nokia) * mathew: revert typo Signed-off-by: Mathew Wicks <5735406+thesuperzapper@users.noreply.github.com> --------- Signed-off-by: Liav Weiss (EXT-Nokia) Signed-off-by: Mathew Wicks <5735406+thesuperzapper@users.noreply.github.com> Co-authored-by: Liav Weiss (EXT-Nokia) Co-authored-by: Mathew Wicks <5735406+thesuperzapper@users.noreply.github.com> --- workspaces/.dockerignore | 7 +++++++ workspaces/backend/Dockerfile | 23 +++++++++++++---------- workspaces/backend/Makefile | 7 ++++--- 3 files changed, 24 insertions(+), 13 deletions(-) create mode 100644 workspaces/.dockerignore diff --git a/workspaces/.dockerignore b/workspaces/.dockerignore new file mode 100644 index 00000000..ae48c79b --- /dev/null +++ b/workspaces/.dockerignore @@ -0,0 +1,7 @@ +# NOTE: This file is used when building Docker images with context `..` +# Primarily intended for backend/Dockerfile builds + +# Exclude frontend code, node_modules, and unnecessary binaries +frontend/ +controller/bin/ +backend/bin/ \ No newline at end of file diff --git a/workspaces/backend/Dockerfile b/workspaces/backend/Dockerfile index a091f293..53d3ad94 100644 --- a/workspaces/backend/Dockerfile +++ b/workspaces/backend/Dockerfile @@ -6,17 +6,20 @@ ARG TARGETARCH WORKDIR /workspace # Copy the Go Modules manifests -COPY go.mod go.sum ./ +COPY backend/go.mod backend/go.sum ./ -# Download dependencies -RUN go mod download +# Copy controller directory +COPY controller /workspace/controller + +# Rewrite the go.mod to update the replace directive and download dependencies +RUN go mod edit -replace=github.com/kubeflow/notebooks/workspaces/controller=./controller && \ + go mod download # Copy the go source files -COPY cmd/ cmd/ -COPY api/ api/ -COPY config/ config/ -COPY data/ data/ -COPY integrations/ integrations/ +COPY backend/cmd/ cmd/ +COPY backend/api/ api/ +COPY backend/internal/ internal/ +COPY backend/openapi/ openapi/ # Build the Go application RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o backend ./cmd/main.go @@ -31,7 +34,7 @@ USER 65532:65532 EXPOSE 4000 # Define environment variables -ENV PORT 4000 -ENV ENV development +ENV PORT=4000 +ENV ENV=development ENTRYPOINT ["/backend"] diff --git a/workspaces/backend/Makefile b/workspaces/backend/Makefile index fc0869b9..55925977 100644 --- a/workspaces/backend/Makefile +++ b/workspaces/backend/Makefile @@ -91,7 +91,8 @@ run: fmt vet swag ## Run a backend from your host. # More info: https://docs.docker.com/develop/develop-images/build_enhancements/ .PHONY: docker-build docker-build: ## Build docker image with the backend. - $(CONTAINER_TOOL) build -t ${IMG} . + $(CONTAINER_TOOL) build -f Dockerfile -t $(IMG) .. + .PHONY: docker-push docker-push: ## Push docker image with the backend. @@ -107,10 +108,10 @@ PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le .PHONY: docker-buildx docker-buildx: ## Build and push docker image for the manager for cross-platform support # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile - sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross + sed '1,// s/^FROM/FROM --platform=$${BUILDPLATFORM}/' Dockerfile > Dockerfile.cross - $(CONTAINER_TOOL) buildx create --name project-v3-builder $(CONTAINER_TOOL) buildx use project-v3-builder - - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . + - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .. - $(CONTAINER_TOOL) buildx rm project-v3-builder rm Dockerfile.cross From a4cd1c27c4932c2ab453a428287838efc5e19e0f Mon Sep 17 00:00:00 2001 From: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> Date: Fri, 27 Jun 2025 10:46:17 -0300 Subject: [PATCH 19/68] feat(ws): add fallback mechanism to broken images (#448) Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> --- workspaces/frontend/.vscode/settings.json | 2 +- .../src/app/components/WorkspaceTable.tsx | 34 ++++++----- .../pages/WorkspaceKinds/WorkspaceKinds.tsx | 27 +++++---- .../details/WorkspaceKindDetailsOverview.tsx | 31 +++++++++- .../Form/kind/WorkspaceFormKindList.tsx | 18 +++++- .../src/shared/components/ImageFallback.tsx | 38 ++++++++++++ .../src/shared/components/WithValidImage.tsx | 58 +++++++++++++++++++ 7 files changed, 178 insertions(+), 30 deletions(-) create mode 100644 workspaces/frontend/src/shared/components/ImageFallback.tsx create mode 100644 workspaces/frontend/src/shared/components/WithValidImage.tsx diff --git a/workspaces/frontend/.vscode/settings.json b/workspaces/frontend/.vscode/settings.json index 1b09158c..cccc87eb 100644 --- a/workspaces/frontend/.vscode/settings.json +++ b/workspaces/frontend/.vscode/settings.json @@ -2,4 +2,4 @@ "eslint.options": { "rulePaths": ["./eslint-local-rules"] } -} \ No newline at end of file +} diff --git a/workspaces/frontend/src/app/components/WorkspaceTable.tsx b/workspaces/frontend/src/app/components/WorkspaceTable.tsx index 20e20c6e..a2c11866 100644 --- a/workspaces/frontend/src/app/components/WorkspaceTable.tsx +++ b/workspaces/frontend/src/app/components/WorkspaceTable.tsx @@ -7,7 +7,6 @@ import { PaginationVariant, Pagination, Content, - Brand, Tooltip, Bullseye, Button, @@ -28,7 +27,6 @@ import { ExclamationTriangleIcon, TimesCircleIcon, QuestionCircleIcon, - CodeIcon, } from '@patternfly/react-icons'; import { formatDistanceToNow } from 'date-fns'; import { Workspace, WorkspaceState } from '~/shared/api/backendApiTypes'; @@ -48,10 +46,12 @@ import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds'; import { WorkspaceConnectAction } from '~/app/pages/Workspaces/WorkspaceConnectAction'; import CustomEmptyState from '~/shared/components/CustomEmptyState'; import Filter, { FilteredColumn, FilterRef } from '~/shared/components/Filter'; +import WithValidImage from '~/shared/components/WithValidImage'; import { formatResourceFromWorkspace, formatWorkspaceIdleState, } from '~/shared/utilities/WorkspaceUtils'; +import ImageFallback from '~/shared/components/ImageFallback'; const { fields: wsTableColumns, @@ -436,19 +436,25 @@ const WorkspaceTable = React.forwardRef( case 'kind': return ( ); case 'namespace': diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx index b16ecb65..f1927d1f 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx @@ -5,7 +5,6 @@ import { DrawerContentBody, PageSection, Content, - Brand, Tooltip, Label, Toolbar, @@ -34,13 +33,15 @@ import { ActionsColumn, IActions, } from '@patternfly/react-table'; -import { CodeIcon, FilterIcon } from '@patternfly/react-icons'; +import { FilterIcon } from '@patternfly/react-icons'; import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds'; import { useWorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind'; import { WorkspaceKindsColumns } from '~/app/types'; import ThemeAwareSearchInput from '~/app/components/ThemeAwareSearchInput'; import CustomEmptyState from '~/shared/components/CustomEmptyState'; +import WithValidImage from '~/shared/components/WithValidImage'; +import ImageFallback from '~/shared/components/ImageFallback'; import { useTypedNavigate } from '~/app/routerHelper'; import { WorkspaceKindDetails } from './details/WorkspaceKindDetails'; @@ -555,15 +556,19 @@ export const WorkspaceKinds: React.FunctionComponent = () => {
- {kindLogoDict[workspace.workspaceKind.name] ? ( - - - - ) : ( - - - - )} + } + > + {(validSrc) => ( + + {workspace.workspaceKind.name} + + )} +
- {workspaceKind.icon.url ? ( - - ) : ( - - )} + } + > + {(validSrc) => ( + {workspaceKind.name} + )} + {workspaceKind.name} Icon - + + } + > + {(validSrc) => {workspaceKind.name}} + Icon URL @@ -61,7 +74,19 @@ export const WorkspaceKindDetailsOverview: React.FunctionComponent< Logo - + + } + > + {(validSrc) => {workspaceKind.name}} + Logo URL diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx index bf50e094..30240e62 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx @@ -12,6 +12,8 @@ import { import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import Filter, { FilteredColumn, FilterRef } from '~/shared/components/Filter'; import CustomEmptyState from '~/shared/components/CustomEmptyState'; +import ImageFallback from '~/shared/components/ImageFallback'; +import WithValidImage from '~/shared/components/WithValidImage'; import { defineDataFields, FilterableDataFieldKey } from '~/app/filterableDataHelper'; // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -111,7 +113,21 @@ export const WorkspaceFormKindList: React.FunctionComponent - {`${kind.name} + + } + > + {(validSrc) => ( + {`${kind.name} + )} + {kind.displayName} {kind.description} diff --git a/workspaces/frontend/src/shared/components/ImageFallback.tsx b/workspaces/frontend/src/shared/components/ImageFallback.tsx new file mode 100644 index 00000000..2002cfa9 --- /dev/null +++ b/workspaces/frontend/src/shared/components/ImageFallback.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { ExclamationCircleIcon } from '@patternfly/react-icons'; +import { Content, ContentVariants, Flex, FlexItem, Tooltip } from '@patternfly/react-core'; + +type ImageFallbackProps = { + extended?: boolean; + imageSrc: string | undefined | null; + message?: string; +}; + +const ImageFallback: React.FC = ({ + extended = false, + imageSrc, + message = `Cannot load image: ${imageSrc || 'no image source provided'}`, +}) => { + if (extended) { + return ( + + + + + + + {message} + + + + ); + } + + return ( + + + + ); +}; + +export default ImageFallback; diff --git a/workspaces/frontend/src/shared/components/WithValidImage.tsx b/workspaces/frontend/src/shared/components/WithValidImage.tsx new file mode 100644 index 00000000..3743b53b --- /dev/null +++ b/workspaces/frontend/src/shared/components/WithValidImage.tsx @@ -0,0 +1,58 @@ +import React, { useEffect, useState } from 'react'; +import { Skeleton, SkeletonProps } from '@patternfly/react-core'; + +type WithValidImageProps = { + imageSrc: string | undefined | null; + fallback: React.ReactNode; + children: (validImageSrc: string) => React.ReactNode; + skeletonWidth?: SkeletonProps['width']; + skeletonShape?: SkeletonProps['shape']; +}; + +const DEFAULT_SKELETON_WIDTH = '32px'; +const DEFAULT_SKELETON_SHAPE: SkeletonProps['shape'] = 'square'; + +type LoadState = 'loading' | 'valid' | 'invalid'; + +const WithValidImage: React.FC = ({ + imageSrc, + fallback, + children, + skeletonWidth = DEFAULT_SKELETON_WIDTH, + skeletonShape = DEFAULT_SKELETON_SHAPE, +}) => { + const [status, setStatus] = useState('loading'); + const [resolvedSrc, setResolvedSrc] = useState(''); + + useEffect(() => { + let cancelled = false; + + if (!imageSrc) { + setStatus('invalid'); + return; + } + + const img = new Image(); + img.onload = () => !cancelled && (setResolvedSrc(imageSrc), setStatus('valid')); + img.onerror = () => !cancelled && setStatus('invalid'); + img.src = imageSrc; + + return () => { + cancelled = true; + }; + }, [imageSrc]); + + if (status === 'loading') { + return ( + + ); + } + + if (status === 'invalid') { + return <>{fallback}; + } + + return <>{children(resolvedSrc)}; +}; + +export default WithValidImage; From 6ba18c0c22685fcca0855ac5c448871ab837ec80 Mon Sep 17 00:00:00 2001 From: Charles Thao Date: Fri, 27 Jun 2025 14:31:18 -0400 Subject: [PATCH 20/68] feat: refactor Form View to Edit only (#451) Signed-off-by: Charles Thao --- .../WorkspaceKinds/Form/WorkspaceKindForm.tsx | 94 +++++++++++-------- .../frontend/src/shared/api/apiUtils.ts | 42 +++++++++ .../src/shared/api/backendApiTypes.ts | 2 +- .../frontend/src/shared/api/notebookApi.ts | 6 +- .../src/shared/api/notebookService.ts | 3 +- 5 files changed, 103 insertions(+), 44 deletions(-) diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx index c95ef8a4..a744cd0f 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { Button, Content, @@ -13,6 +13,7 @@ import { } from '@patternfly/react-core'; import { useTypedNavigate } from '~/app/routerHelper'; import useGenericObjectState from '~/app/hooks/useGenericObjectState'; +import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { WorkspaceKindFormData } from '~/app/types'; import { WorkspaceKindFileUpload } from './fileUpload/WorkspaceKindFileUpload'; import { WorkspaceKindFormProperties } from './properties/WorkspaceKindFormProperties'; @@ -27,6 +28,7 @@ export type ValidationStatus = 'success' | 'error' | 'default'; export const WorkspaceKindForm: React.FC = () => { const navigate = useTypedNavigate(); + const { api } = useNotebookAPI(); // TODO: Detect mode by route const [mode] = useState('create'); const [yamlValue, setYamlValue] = useState(''); @@ -35,14 +37,6 @@ export const WorkspaceKindForm: React.FC = () => { const [validated, setValidated] = useState('default'); const workspaceKindFileUploadId = 'workspace-kind-form-fileupload-view'; - const handleViewClick = (event: React.MouseEvent | React.KeyboardEvent | MouseEvent) => { - const { id } = event.currentTarget as HTMLElement; - setView( - id === workspaceKindFileUploadId - ? WorkspaceKindFormView.FileUpload - : WorkspaceKindFormView.Form, - ); - }; const [data, setData, resetData] = useGenericObjectState({ properties: { displayName: '', @@ -59,16 +53,41 @@ export const WorkspaceKindForm: React.FC = () => { }, }); - const handleCreate = useCallback(() => { - // TODO: Complete handleCreate with API call to create a new WS kind - if (!Object.keys(data).length) { - return; - } + const handleViewClick = useCallback( + (event: React.MouseEvent | React.KeyboardEvent | MouseEvent) => { + const { id } = event.currentTarget as HTMLElement; + setView( + id === workspaceKindFileUploadId + ? WorkspaceKindFormView.FileUpload + : WorkspaceKindFormView.Form, + ); + }, + [], + ); + + const handleSubmit = useCallback(async () => { setIsSubmitting(true); - }, [data]); + // TODO: Complete handleCreate with API call to create a new WS kind + try { + if (mode === 'create') { + const newWorkspaceKind = await api.createWorkspaceKind({}, yamlValue); + console.info('New workspace kind created:', JSON.stringify(newWorkspaceKind)); + } + } catch (err) { + console.error(`Error ${mode === 'edit' ? 'editing' : 'creating'} workspace kind: ${err}`); + } finally { + setIsSubmitting(false); + } + navigate('workspaceKinds'); + }, [navigate, mode, api, yamlValue]); + + const canSubmit = useMemo( + () => !isSubmitting && yamlValue.length > 0 && validated === 'success', + [yamlValue, isSubmitting, validated], + ); const cancel = useCallback(() => { - navigate('workspaceKindCreate'); + navigate('workspaceKinds'); }, [navigate]); return ( @@ -83,29 +102,30 @@ export const WorkspaceKindForm: React.FC = () => { {view === WorkspaceKindFormView.FileUpload - ? `Please upload a Workspace Kind YAML file. Select 'Form View' to view - and edit the workspace kind's information` + ? `Please upload or drag and drop a Workspace Kind YAML file.` : `View and edit the Workspace Kind's information. Some fields may not be represented in this form`} - - - - - - + {mode === 'edit' && ( + + + + + + + )} @@ -144,8 +164,8 @@ export const WorkspaceKindForm: React.FC = () => { diff --git a/workspaces/frontend/src/shared/api/apiUtils.ts b/workspaces/frontend/src/shared/api/apiUtils.ts index 3d90488b..e1c5c6da 100644 --- a/workspaces/frontend/src/shared/api/apiUtils.ts +++ b/workspaces/frontend/src/shared/api/apiUtils.ts @@ -171,6 +171,48 @@ export const restDELETE = ( parseJSON: options?.parseJSON, }); +/** POST -- but with YAML content directly in body */ +export const restYAML = ( + host: string, + path: string, + yamlContent: string, + queryParams?: Record, + options?: APIOptions, +): Promise => { + const { method, ...otherOptions } = mergeRequestInit(options, { method: 'POST' }); + + const sanitizedQueryParams = queryParams + ? Object.entries(queryParams).reduce((acc, [key, value]) => { + if (value) { + return { ...acc, [key]: value }; + } + return acc; + }, {}) + : null; + + const searchParams = sanitizedQueryParams + ? new URLSearchParams(sanitizedQueryParams).toString() + : null; + + return fetch(`${host}${path}${searchParams ? `?${searchParams}` : ''}`, { + ...otherOptions, + headers: { + ...otherOptions.headers, + ...(DEV_MODE && { [AUTH_HEADER]: localStorage.getItem(AUTH_HEADER) }), + 'Content-Type': 'application/vnd.kubeflow-notebooks.manifest+yaml', + }, + method, + body: yamlContent, + }).then((response) => + response.text().then((fetchedData) => { + if (options?.parseJSON !== false) { + return JSON.parse(fetchedData); + } + return fetchedData; + }), + ); +}; + export const isNotebookResponse = (response: unknown): response is ResponseBody => { if (typeof response === 'object' && response !== null) { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions diff --git a/workspaces/frontend/src/shared/api/backendApiTypes.ts b/workspaces/frontend/src/shared/api/backendApiTypes.ts index f1beda3b..ca42d666 100644 --- a/workspaces/frontend/src/shared/api/backendApiTypes.ts +++ b/workspaces/frontend/src/shared/api/backendApiTypes.ts @@ -90,7 +90,7 @@ export interface WorkspaceKindPodTemplate { } // eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface WorkspaceKindCreate {} +export type WorkspaceKindCreate = string; // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface WorkspaceKindUpdate {} diff --git a/workspaces/frontend/src/shared/api/notebookApi.ts b/workspaces/frontend/src/shared/api/notebookApi.ts index 17aa6844..d57a118f 100644 --- a/workspaces/frontend/src/shared/api/notebookApi.ts +++ b/workspaces/frontend/src/shared/api/notebookApi.ts @@ -4,7 +4,6 @@ import { Workspace, WorkspaceCreate, WorkspaceKind, - WorkspaceKindCreate, WorkspaceKindPatch, WorkspaceKindUpdate, WorkspacePatch, @@ -63,10 +62,7 @@ export type StartWorkspace = ( // WorkspaceKind export type ListWorkspaceKinds = (opts: APIOptions) => Promise; export type GetWorkspaceKind = (opts: APIOptions, kind: string) => Promise; -export type CreateWorkspaceKind = ( - opts: APIOptions, - data: RequestData, -) => Promise; +export type CreateWorkspaceKind = (opts: APIOptions, data: string) => Promise; export type UpdateWorkspaceKind = ( opts: APIOptions, kind: string, diff --git a/workspaces/frontend/src/shared/api/notebookService.ts b/workspaces/frontend/src/shared/api/notebookService.ts index 457a310a..81fdbfcc 100644 --- a/workspaces/frontend/src/shared/api/notebookService.ts +++ b/workspaces/frontend/src/shared/api/notebookService.ts @@ -5,6 +5,7 @@ import { restGET, restPATCH, restUPDATE, + restYAML, } from '~/shared/api/apiUtils'; import { handleRestFailures } from '~/shared/api/errorUtils'; import { @@ -96,7 +97,7 @@ export const getWorkspaceKind: GetWorkspaceKindAPI = (hostPath) => (opts, kind) ); export const createWorkspaceKind: CreateWorkspaceKindAPI = (hostPath) => (opts, data) => - handleRestFailures(restCREATE(hostPath, `/workspacekinds`, data, {}, opts)).then((response) => + handleRestFailures(restYAML(hostPath, `/workspacekinds`, data, {}, opts)).then((response) => extractNotebookResponse(response), ); From 23fed9c9a9f6a53fb308b73b8b7a21f063224c4e Mon Sep 17 00:00:00 2001 From: Charles Thao Date: Wed, 2 Jul 2025 16:45:18 -0400 Subject: [PATCH 21/68] feat(ws): Make Create Workspace Kind button visible (#466) Signed-off-by: Charles Thao --- .../app/pages/WorkspaceKinds/WorkspaceKinds.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx index f1927d1f..4b44ee5d 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx @@ -67,10 +67,9 @@ export const WorkspaceKinds: React.FunctionComponent = () => { ); const navigate = useTypedNavigate(); - // TODO: Uncomment when WorkspaceKindForm is complete - // const createWorkspaceKind = useCallback(() => { - // navigate('workspaceKindCreate'); - // }, [navigate]); + const createWorkspaceKind = useCallback(() => { + navigate('workspaceKindCreate'); + }, [navigate]); const [workspaceKinds, workspaceKindsLoaded, workspaceKindsError] = useWorkspaceKinds(); const workspaceCountPerKind = useWorkspaceCountPerKind(); const [selectedWorkspaceKind, setSelectedWorkspaceKind] = useState(null); @@ -523,11 +522,12 @@ export const WorkspaceKinds: React.FunctionComponent = () => { > {statusSelect} + + + - {/* //TODO: Uncomment when WorkspaceKind Form is finished - */} From c6e81c2a77ed89d013fefb2fd48aa9bd8619ca7a Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Fri, 4 Jul 2025 10:36:19 -0400 Subject: [PATCH 22/68] fix(ws): Improve Workspace Creation Wizard Step Descriptions (#452) Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> Changes to step descriptions based on feedback --- .../src/app/pages/Workspaces/Form/WorkspaceForm.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx index be13e07b..cf377e9f 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx @@ -9,6 +9,7 @@ import { ProgressStep, ProgressStepper, Stack, + StackItem, } from '@patternfly/react-core'; import useGenericObjectState from '~/app/hooks/useGenericObjectState'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; @@ -30,10 +31,12 @@ enum WorkspaceFormSteps { } const stepDescriptions: { [key in WorkspaceFormSteps]?: string } = { - [WorkspaceFormSteps.KindSelection]: 'Select a workspace kind to use for the workspace.', + [WorkspaceFormSteps.KindSelection]: + 'A workspace kind is a template for creating a workspace, which is an isolated area where you can work with models in your preferred IDE, such as Jupyter Notebook.', [WorkspaceFormSteps.ImageSelection]: - 'Select a workspace image and image version to use for the workspace.', - [WorkspaceFormSteps.PodConfigSelection]: 'Select a pod config to use for the workspace.', + 'Select a workspace image and image version to use for the workspace. A workspace image is a container image that contains the software and dependencies needed to run a workspace.', + [WorkspaceFormSteps.PodConfigSelection]: + 'Select a pod config to use for the workspace. A pod config is a configuration that defines the resources and settings for a workspace.', [WorkspaceFormSteps.Properties]: 'Configure properties for your workspace.', }; @@ -167,7 +170,6 @@ const WorkspaceForm: React.FC = () => {

{`${mode === 'create' ? 'Create' : 'Edit'} workspace`}

-

{stepDescriptions[currentStep]}

@@ -211,6 +213,9 @@ const WorkspaceForm: React.FC = () => { + +

{stepDescriptions[currentStep]}

+
From d680ea03fdace4213b91f3ad1f60e71dea2e2bda Mon Sep 17 00:00:00 2001 From: Charles Thao Date: Fri, 4 Jul 2025 10:38:20 -0400 Subject: [PATCH 23/68] feat: workspace kind Edit Pod Configs (#425) * Add Pod Config to WorkspaceKind form Signed-off-by: Charles Thao * Add resource section for PodConfig Signed-off-by: Charles Thao * Use refactored types Signed-off-by: Charles Thao * Improve Resource input Signed-off-by: Charles Thao * Move form view to edit mode only Signed-off-by: Charles Thao * Bug fix and improvements Signed-off-by: Charles Thao --------- Signed-off-by: Charles Thao --- .../frontend/src/__mocks__/mockResources.ts | 16 + workspaces/frontend/src/app/AppRoutes.tsx | 1 + ...eKindFormLabels.tsx => EditableLabels.tsx} | 7 +- .../WorkspaceKinds/Form/WorkspaceKindForm.tsx | 57 +-- .../Form/__tests__/helpers.spec.ts | 67 ++++ .../fileUpload/WorkspaceKindFileUpload.tsx | 31 +- .../app/pages/WorkspaceKinds/Form/helpers.ts | 52 ++- .../image/WorkspaceKindFormImageModal.tsx | 6 +- .../Form/podConfig/ResourceInputWrapper.tsx | 146 +++++++ .../podConfig/WorkspaceKindFormPodConfig.tsx | 227 +++++++++++ .../WorkspaceKindFormPodConfigModal.tsx | 203 ++++++++++ .../podConfig/WorkspaceKindFormResource.tsx | 379 ++++++++++++++++++ workspaces/frontend/src/app/routes.ts | 8 + workspaces/frontend/src/app/types.ts | 17 + .../frontend/src/shared/style/MUI-theme.scss | 16 + .../src/shared/utilities/WorkspaceUtils.ts | 31 +- .../src/shared/utilities/valueUnits.ts | 4 +- 17 files changed, 1178 insertions(+), 90 deletions(-) create mode 100644 workspaces/frontend/src/__mocks__/mockResources.ts rename workspaces/frontend/src/app/pages/WorkspaceKinds/Form/{WorkspaceKindFormLabels.tsx => EditableLabels.tsx} (96%) create mode 100644 workspaces/frontend/src/app/pages/WorkspaceKinds/Form/__tests__/helpers.spec.ts create mode 100644 workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/ResourceInputWrapper.tsx create mode 100644 workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfig.tsx create mode 100644 workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfigModal.tsx create mode 100644 workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormResource.tsx diff --git a/workspaces/frontend/src/__mocks__/mockResources.ts b/workspaces/frontend/src/__mocks__/mockResources.ts new file mode 100644 index 00000000..653b9263 --- /dev/null +++ b/workspaces/frontend/src/__mocks__/mockResources.ts @@ -0,0 +1,16 @@ +import { WorkspaceKindPodConfigValue } from '~/app/types'; + +export const mockPodConfig: WorkspaceKindPodConfigValue = { + id: 'pod_config_35', + displayName: '8000m CPU, 2Gi RAM, 1 GPU', + description: 'Pod with 8000m CPU, 2Gi RAM, and 1 GPU', + labels: [ + { key: 'cpu', value: '8000m' }, + { key: 'memory', value: '2Gi' }, + ], + hidden: false, + resources: { + requests: { cpu: '8000m', memory: '2Gi' }, + limits: { 'nvidia.com/gpu': '2' }, + }, +}; diff --git a/workspaces/frontend/src/app/AppRoutes.tsx b/workspaces/frontend/src/app/AppRoutes.tsx index c26d7ed2..0251d74e 100644 --- a/workspaces/frontend/src/app/AppRoutes.tsx +++ b/workspaces/frontend/src/app/AppRoutes.tsx @@ -68,6 +68,7 @@ const AppRoutes: React.FC = () => { } /> } /> } /> + } /> } /> } /> { diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindFormLabels.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/EditableLabels.tsx similarity index 96% rename from workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindFormLabels.tsx rename to workspaces/frontend/src/app/pages/WorkspaceKinds/Form/EditableLabels.tsx index aeab95f3..5d7348c2 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindFormLabels.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/EditableLabels.tsx @@ -68,15 +68,12 @@ const EditableRow: React.FC = ({ type ColumnNames = { [K in keyof T]: string }; -interface WorkspaceKindFormLabelTableProps { +interface EditableLabelsProps { rows: WorkspaceOptionLabel[]; setRows: (value: WorkspaceOptionLabel[]) => void; } -export const WorkspaceKindFormLabelTable: React.FC = ({ - rows, - setRows, -}) => { +export const EditableLabels: React.FC = ({ rows, setRows }) => { const columnNames: ColumnNames = { key: 'Key', value: 'Value', diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx index a744cd0f..2666f54b 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx @@ -8,16 +8,16 @@ import { PageGroup, PageSection, Stack, - ToggleGroup, - ToggleGroupItem, } from '@patternfly/react-core'; import { useTypedNavigate } from '~/app/routerHelper'; +import { useCurrentRouteKey } from '~/app/hooks/useCurrentRouteKey'; import useGenericObjectState from '~/app/hooks/useGenericObjectState'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { WorkspaceKindFormData } from '~/app/types'; import { WorkspaceKindFileUpload } from './fileUpload/WorkspaceKindFileUpload'; import { WorkspaceKindFormProperties } from './properties/WorkspaceKindFormProperties'; import { WorkspaceKindFormImage } from './image/WorkspaceKindFormImage'; +import { WorkspaceKindFormPodConfig } from './podConfig/WorkspaceKindFormPodConfig'; export enum WorkspaceKindFormView { Form, @@ -30,13 +30,10 @@ export const WorkspaceKindForm: React.FC = () => { const navigate = useTypedNavigate(); const { api } = useNotebookAPI(); // TODO: Detect mode by route - const [mode] = useState('create'); const [yamlValue, setYamlValue] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); - const [view, setView] = useState(WorkspaceKindFormView.FileUpload); const [validated, setValidated] = useState('default'); - const workspaceKindFileUploadId = 'workspace-kind-form-fileupload-view'; - + const mode = useCurrentRouteKey() === 'workspaceKindCreate' ? 'create' : 'edit'; const [data, setData, resetData] = useGenericObjectState({ properties: { displayName: '', @@ -51,19 +48,11 @@ export const WorkspaceKindForm: React.FC = () => { default: '', values: [], }, - }); - - const handleViewClick = useCallback( - (event: React.MouseEvent | React.KeyboardEvent | MouseEvent) => { - const { id } = event.currentTarget as HTMLElement; - setView( - id === workspaceKindFileUploadId - ? WorkspaceKindFormView.FileUpload - : WorkspaceKindFormView.Form, - ); + podConfig: { + default: '', + values: [], }, - [], - ); + }); const handleSubmit = useCallback(async () => { setIsSubmitting(true); @@ -101,39 +90,19 @@ export const WorkspaceKindForm: React.FC = () => { {`${mode === 'create' ? 'Create' : 'Edit'} workspace kind`} - {view === WorkspaceKindFormView.FileUpload + {mode === 'create' ? `Please upload or drag and drop a Workspace Kind YAML file.` : `View and edit the Workspace Kind's information. Some fields may not be represented in this form`} - {mode === 'edit' && ( - - - - - - - )} - {view === WorkspaceKindFormView.FileUpload && ( + {mode === 'create' && ( { setValidated={setValidated} /> )} - {view === WorkspaceKindFormView.Form && ( + {mode === 'edit' && ( <> { setData('imageConfig', imageInput); }} /> + { + setData('podConfig', podConfig); + }} + /> )} diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/__tests__/helpers.spec.ts b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/__tests__/helpers.spec.ts new file mode 100644 index 00000000..62137aea --- /dev/null +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/__tests__/helpers.spec.ts @@ -0,0 +1,67 @@ +import { getResources } from '~/app/pages/WorkspaceKinds/Form/helpers'; +import { mockPodConfig } from '~/__mocks__/mockResources'; +import { WorkspaceKindPodConfigValue } from '~/app/types'; + +describe('getResources', () => { + it('should convert k8s resource object to PodResourceEntry array with correct structure', () => { + const result = getResources(mockPodConfig); + expect(result).toHaveLength(3); + + const cpu = result.find((r) => r.type === 'cpu'); + expect(cpu).toBeDefined(); + expect(cpu).toEqual({ + id: 'cpu-resource', + type: 'cpu', + request: '8000m', + limit: '', + }); + + const memory = result.find((r) => r.type === 'memory'); + expect(memory).toBeDefined(); + expect(memory).toEqual({ + id: 'memory-resource', + type: 'memory', + request: '2Gi', + limit: '', + }); + + // Check custom GPU resource + const gpu = result.find((r) => r.type === 'nvidia.com/gpu'); + expect(gpu).toBeDefined(); + expect(gpu?.type).toBe('nvidia.com/gpu'); + expect(gpu?.request).toBe(''); + expect(gpu?.limit).toBe('2'); + expect(gpu?.id).toMatch(/nvidia\.com\/gpu-/); + }); + + it(' handle empty or missing resources and return default CPU and memory entries', () => { + const emptyConfig: WorkspaceKindPodConfigValue = { + id: 'test-config', + displayName: 'Test Config', + description: 'Test Description', + labels: [], + hidden: false, + }; + + const result = getResources(emptyConfig); + + // Should return CPU and memory with empty values + expect(result).toHaveLength(2); + + const cpu = result.find((r) => r.type === 'cpu'); + expect(cpu).toEqual({ + id: 'cpu-resource', + type: 'cpu', + request: '', + limit: '', + }); + + const memory = result.find((r) => r.type === 'memory'); + expect(memory).toEqual({ + id: 'memory-resource', + type: 'memory', + request: '', + limit: '', + }); + }); +}); diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/fileUpload/WorkspaceKindFileUpload.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/fileUpload/WorkspaceKindFileUpload.tsx index eec7ccbc..eae9c918 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/fileUpload/WorkspaceKindFileUpload.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/fileUpload/WorkspaceKindFileUpload.tsx @@ -8,13 +8,10 @@ import { HelperTextItem, Content, } from '@patternfly/react-core'; -import { UpdateObjectAtPropAndValue } from '~/app/hooks/useGenericObjectState'; -import { WorkspaceKindFormData } from '~/app/types'; import { isValidWorkspaceKindYaml } from '~/app/pages/WorkspaceKinds/Form/helpers'; import { ValidationStatus } from '~/app/pages/WorkspaceKinds/Form/WorkspaceKindForm'; interface WorkspaceKindFileUploadProps { - setData: UpdateObjectAtPropAndValue; value: string; setValue: (v: string) => void; resetData: () => void; @@ -23,7 +20,6 @@ interface WorkspaceKindFileUploadProps { } export const WorkspaceKindFileUpload: React.FC = ({ - setData, resetData, value, setValue, @@ -62,30 +58,13 @@ export const WorkspaceKindFileUpload: React.FC = ( if (isYamlFileRef.current) { try { const parsed = yaml.load(v); - if (isValidWorkspaceKindYaml(parsed)) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - setData('properties', (parsed as any).spec.spawner); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const parsedImg = (parsed as any).spec.podTemplate.options.imageConfig; - setData('imageConfig', { - default: parsedImg.spawner.default || '', - // eslint-disable-next-line @typescript-eslint/no-explicit-any - values: parsedImg.values.map((img: any) => { - const res = { - id: img.id, - redirect: img.redirect, - ...img.spawner, - ...img.spec, - }; - return res; - }), - }); - setValidated('success'); - setFileUploadHelperText(''); - } else { + if (!isValidWorkspaceKindYaml(parsed)) { setFileUploadHelperText('YAML is invalid: must follow WorkspaceKind format.'); setValidated('error'); resetData(); + } else { + setValidated('success'); + setFileUploadHelperText(''); } } catch (e) { console.error('Error parsing YAML:', e); @@ -94,7 +73,7 @@ export const WorkspaceKindFileUpload: React.FC = ( } } }, - [setValue, setData, setValidated, resetData], + [setValue, setValidated, resetData], ); const handleClear = useCallback(() => { diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/helpers.ts b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/helpers.ts index 39ce8bf5..aad5f622 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/helpers.ts +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/helpers.ts @@ -1,5 +1,10 @@ -import { ImagePullPolicy, WorkspaceKindImagePort } from '~/app/types'; -import { WorkspaceOptionLabel } from '~/shared/api/backendApiTypes'; +import { ImagePullPolicy, WorkspaceKindImagePort, WorkspaceKindPodConfigValue } from '~/app/types'; +import { WorkspaceOptionLabel, WorkspacePodConfigValue } from '~/shared/api/backendApiTypes'; +import { PodResourceEntry } from './podConfig/WorkspaceKindFormResource'; + +// Simple ID generator to avoid PatternFly dependency in tests +export const generateUniqueId = (): string => + `id-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types export const isValidWorkspaceKindYaml = (data: any): boolean => { @@ -88,3 +93,46 @@ export const emptyImage = { to: '', }, }; + +export const emptyPodConfig: WorkspacePodConfigValue = { + id: '', + displayName: '', + description: '', + labels: [], + hidden: false, + redirect: { + to: '', + }, +}; +// convert from k8s resource object {limits: {}, requests{}} to array of {type: '', limit: '', request: ''} for each type of resource (e.g. CPU, memory, nvidia.com/gpu) +export const getResources = (currConfig: WorkspaceKindPodConfigValue): PodResourceEntry[] => { + const grouped = new Map([ + ['cpu', { request: '', limit: '' }], + ['memory', { request: '', limit: '' }], + ]); + const { requests = {}, limits = {} } = currConfig.resources || {}; + const types = new Set([...Object.keys(requests), ...Object.keys(limits), 'cpu', 'memory']); + types.forEach((type) => { + const entry = grouped.get(type) || { request: '', limit: '' }; + if (type in requests) { + entry.request = String(requests[type]); + } + if (type in limits) { + entry.limit = String(limits[type]); + } + grouped.set(type, entry); + }); + + // Convert to UI-types with consistent IDs + return Array.from(grouped.entries()).map(([type, { request, limit }]) => ({ + id: + type === 'cpu' + ? 'cpu-resource' + : type === 'memory' + ? 'memory-resource' + : `${type}-${generateUniqueId()}`, + type, + request, + limit, + })); +}; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageModal.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageModal.tsx index 09730062..99a7a22b 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageModal.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageModal.tsx @@ -14,7 +14,7 @@ import { HelperText, } from '@patternfly/react-core'; import { WorkspaceKindImageConfigValue, ImagePullPolicy } from '~/app/types'; -import { WorkspaceKindFormLabelTable } from '~/app/pages/WorkspaceKinds/Form/WorkspaceKindFormLabels'; +import { EditableLabels } from '~/app/pages/WorkspaceKinds/Form/EditableLabels'; import { emptyImage } from '~/app/pages/WorkspaceKinds/Form/helpers'; import { WorkspaceKindFormImageRedirect } from './WorkspaceKindFormImageRedirect'; @@ -100,7 +100,7 @@ export const WorkspaceKindFormImageModal: React.FC
Hidden
- Hide this image from users + Hide this image from users } aria-label="-controlled-check" @@ -109,7 +109,7 @@ export const WorkspaceKindFormImageModal: React.FC - setImage({ ...image, labels })} /> diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/ResourceInputWrapper.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/ResourceInputWrapper.tsx new file mode 100644 index 00000000..3c8ab7bb --- /dev/null +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/ResourceInputWrapper.tsx @@ -0,0 +1,146 @@ +import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import { + FormSelect, + FormSelectOption, + NumberInput, + Split, + SplitItem, +} from '@patternfly/react-core'; +import { CPU_UNITS, MEMORY_UNITS_FOR_SELECTION, UnitOption } from '~/shared/utilities/valueUnits'; +import { parseResourceValue } from '~/shared/utilities/WorkspaceUtils'; + +interface ResourceInputWrapperProps { + value: string; + onChange: (value: string) => void; + type: 'cpu' | 'memory' | 'custom'; + min?: number; + max?: number; + step?: number; + placeholder?: string; + 'aria-label'?: string; + isDisabled?: boolean; +} + +const unitMap: { + [key: string]: UnitOption[]; +} = { + memory: MEMORY_UNITS_FOR_SELECTION, + cpu: CPU_UNITS, +}; + +const DEFAULT_STEP = 1; + +const DEFAULT_UNITS = { + memory: 'Mi', + cpu: '', +}; + +export const ResourceInputWrapper: React.FC = ({ + value, + onChange, + min = 1, + max, + step = DEFAULT_STEP, + type, + placeholder, + 'aria-label': ariaLabel, + isDisabled = false, +}) => { + const [inputValue, setInputValue] = useState(value); + const [unit, setUnit] = useState(''); + + useEffect(() => { + if (type === 'custom') { + setInputValue(value); + return; + } + const [numericValue, extractedUnit] = parseResourceValue(value, type); + setInputValue(String(numericValue || '')); + setUnit(extractedUnit?.unit || DEFAULT_UNITS[type]); + }, [value, type]); + + const handleInputChange = useCallback( + (newValue: string) => { + setInputValue(newValue); + if (type === 'custom') { + onChange(newValue); + } else { + onChange(newValue ? `${newValue}${unit}` : ''); + } + }, + [onChange, type, unit], + ); + + const handleUnitChange = useCallback( + (newUnit: string) => { + setUnit(newUnit); + if (inputValue) { + onChange(`${inputValue}${newUnit}`); + } + }, + [inputValue, onChange], + ); + + const handleIncrement = useCallback(() => { + const currentValue = parseFloat(inputValue) || 0; + const newValue = Math.min(currentValue + step, max || Infinity); + handleInputChange(newValue.toString()); + }, [inputValue, step, max, handleInputChange]); + + const handleDecrement = useCallback(() => { + const currentValue = parseFloat(inputValue) || 0; + const newValue = Math.max(currentValue - step, min); + handleInputChange(newValue.toString()); + }, [inputValue, step, min, handleInputChange]); + + const handleNumberInputChange = useCallback( + (event: React.FormEvent) => { + const newValue = (event.target as HTMLInputElement).value; + handleInputChange(newValue); + }, + [handleInputChange], + ); + + const unitOptions = useMemo( + () => + type !== 'custom' + ? unitMap[type].map((u) => ) + : [], + [type], + ); + + return ( + + + + + + {type !== 'custom' && ( + handleUnitChange(v)} + id={`${ariaLabel}-unit-select`} + isDisabled={isDisabled} + > + {unitOptions} + + )} + + + ); +}; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfig.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfig.tsx new file mode 100644 index 00000000..400d1da0 --- /dev/null +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfig.tsx @@ -0,0 +1,227 @@ +import React, { useCallback, useState } from 'react'; +import { + Button, + Content, + Dropdown, + MenuToggle, + DropdownItem, + Modal, + ModalHeader, + ModalFooter, + ModalVariant, + EmptyState, + EmptyStateFooter, + EmptyStateActions, + ExpandableSection, + EmptyStateBody, + Label, + getUniqueId, +} from '@patternfly/react-core'; +import { Table, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table'; +import { PlusCircleIcon, EllipsisVIcon, CubesIcon } from '@patternfly/react-icons'; +import { emptyPodConfig } from '~/app/pages/WorkspaceKinds/Form/helpers'; +import { WorkspaceKindPodConfigValue, WorkspaceKindPodConfigData } from '~/app/types'; + +import { WorkspaceKindFormPodConfigModal } from './WorkspaceKindFormPodConfigModal'; + +interface WorkspaceKindFormPodConfigProps { + podConfig: WorkspaceKindPodConfigData; + updatePodConfig: (podConfigs: WorkspaceKindPodConfigData) => void; +} + +export const WorkspaceKindFormPodConfig: React.FC = ({ + podConfig, + updatePodConfig, +}) => { + const [isExpanded, setIsExpanded] = useState(false); + const [defaultId, setDefaultId] = useState(podConfig.default || ''); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [dropdownOpen, setDropdownOpen] = useState(null); + const [editIndex, setEditIndex] = useState(null); + const [deleteIndex, setDeleteIndex] = useState(null); + const [currConfig, setCurrConfig] = useState({ ...emptyPodConfig }); + + const clearForm = useCallback(() => { + setCurrConfig({ ...emptyPodConfig }); + setEditIndex(null); + setIsModalOpen(false); + }, []); + + const openDeleteModal = useCallback((i: number) => { + setIsDeleteModalOpen(true); + setDeleteIndex(i); + }, []); + + const handleAddOrEditSubmit = useCallback( + (config: WorkspaceKindPodConfigValue) => { + if (editIndex !== null) { + const updated = [...podConfig.values]; + updated[editIndex] = config; + updatePodConfig({ ...podConfig, values: updated }); + } else { + updatePodConfig({ ...podConfig, values: [...podConfig.values, config] }); + } + clearForm(); + }, + [clearForm, editIndex, podConfig, updatePodConfig], + ); + + const handleEdit = useCallback( + (index: number) => { + setCurrConfig(podConfig.values[index]); + setEditIndex(index); + setIsModalOpen(true); + }, + [podConfig.values], + ); + + const handleDelete = useCallback(() => { + if (deleteIndex === null) { + return; + } + updatePodConfig({ + default: podConfig.values[deleteIndex].id === defaultId ? '' : defaultId, + values: podConfig.values.filter((_, i) => i !== deleteIndex), + }); + if (podConfig.values[deleteIndex].id === defaultId) { + setDefaultId(''); + } + setDeleteIndex(null); + setIsDeleteModalOpen(false); + }, [deleteIndex, podConfig, updatePodConfig, setDefaultId, defaultId]); + + const addConfigBtn = ( + + ); + + return ( + +
+ setIsExpanded((prev) => !prev)} + isExpanded={isExpanded} + isIndented + > + {podConfig.values.length === 0 && ( + + + Configure specifications for pods and containers in your Workspace Kind + + + {addConfigBtn} + + + )} + {podConfig.values.length > 0 && ( + <> + + + + + + + + + + + + {podConfig.values.map((config, index) => ( + + + + + + + + + ))} + +
Display NameIDDefaultHiddenLabels +
{config.displayName}{config.id} + { + setDefaultId(config.id); + updatePodConfig({ ...podConfig, default: config.id }); + }} + aria-label={`Select ${config.id} as default`} + /> + {config.hidden ? 'Yes' : 'No'} + {config.labels.length > 0 && + config.labels.map((label) => ( + + ))} + + ( + setDropdownOpen(dropdownOpen === index ? null : index)} + variant="plain" + aria-label="plain kebab" + > + + + )} + isOpen={dropdownOpen === index} + onSelect={() => setDropdownOpen(null)} + popperProps={{ position: 'right' }} + > + handleEdit(index)}>Edit + openDeleteModal(index)}>Remove + +
+ {addConfigBtn} + + )} + + setIsDeleteModalOpen(false)} + variant={ModalVariant.small} + > + + + + + + +
+
+
+ ); +}; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfigModal.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfigModal.tsx new file mode 100644 index 00000000..4d7ca683 --- /dev/null +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfigModal.tsx @@ -0,0 +1,203 @@ +import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import { + Modal, + ModalHeader, + ModalBody, + ModalFooter, + Button, + Form, + FormGroup, + TextInput, + Switch, + HelperText, +} from '@patternfly/react-core'; +import { WorkspaceKindPodConfigValue } from '~/app/types'; +import { WorkspaceOptionLabel } from '~/shared/api/backendApiTypes'; +import { EditableLabels } from '~/app/pages/WorkspaceKinds/Form/EditableLabels'; +import { getResources } from '~/app/pages/WorkspaceKinds/Form/helpers'; +import { WorkspaceKindFormResource, PodResourceEntry } from './WorkspaceKindFormResource'; + +interface WorkspaceKindFormPodConfigModalProps { + isOpen: boolean; + onClose: () => void; + onSubmit: (podConfig: WorkspaceKindPodConfigValue) => void; + editIndex: number | null; + currConfig: WorkspaceKindPodConfigValue; + setCurrConfig: (currConfig: WorkspaceKindPodConfigValue) => void; +} + +export const WorkspaceKindFormPodConfigModal: React.FC = ({ + isOpen, + onClose, + onSubmit, + editIndex, + currConfig, + setCurrConfig, +}) => { + const initialResources = useMemo(() => getResources(currConfig), [currConfig]); + + const [resources, setResources] = useState(initialResources); + const [labels, setLabels] = useState(currConfig.labels); + const [id, setId] = useState(currConfig.id); + const [displayName, setDisplayName] = useState(currConfig.displayName); + const [description, setDescription] = useState(currConfig.description); + const [hidden, setHidden] = useState(currConfig.hidden || false); + + useEffect(() => { + setResources(getResources(currConfig)); + setId(currConfig.id); + setDisplayName(currConfig.displayName); + setDescription(currConfig.description); + setHidden(currConfig.hidden || false); + setLabels(currConfig.labels); + }, [currConfig, isOpen, editIndex]); + + // merge resource entries to k8s resources type + // resources: {requests: {}, limits: {}} + const mergeResourceLabels = useCallback((resourceEntries: PodResourceEntry[]) => { + const parsedResources = resourceEntries.reduce( + (acc, r) => { + if (r.type.length) { + if (r.limit.length) { + acc.limits[r.type] = r.limit; + } + if (r.request.length) { + acc.requests[r.type] = r.request; + } + } + return acc; + }, + { requests: {}, limits: {} } as { + requests: { [key: string]: string }; + limits: { [key: string]: string }; + }, + ); + return parsedResources; + }, []); + + const handleSubmit = useCallback(() => { + const updatedConfig = { + ...currConfig, + id, + displayName, + description, + hidden, + resources: mergeResourceLabels(resources), + labels, + }; + setCurrConfig(updatedConfig); + onSubmit(updatedConfig); + }, [ + currConfig, + description, + displayName, + hidden, + id, + labels, + mergeResourceLabels, + onSubmit, + resources, + setCurrConfig, + ]); + + const cpuResource: PodResourceEntry = useMemo( + () => + resources.find((r) => r.type === 'cpu') || { + id: 'cpu-resource', + type: 'cpu', + request: '', + limit: '', + }, + [resources], + ); + + const memoryResource: PodResourceEntry = useMemo( + () => + resources.find((r) => r.type === 'memory') || { + id: 'memory-resource', + type: 'memory', + request: '', + limit: '', + }, + [resources], + ); + + const customResources: PodResourceEntry[] = useMemo( + () => resources.filter((r) => r.type !== 'cpu' && r.type !== 'memory'), + [resources], + ); + + return ( + + + +
+ + setId(value)} + id="workspace-kind-pod-config-id" + /> + + + setDisplayName(value)} + id="workspace-kind-pod-config-name" + /> + + + setDescription(value)} + id="workspace-kind-pod-config-description" + /> + + + +
Hidden
+ Hide this Pod Config from users + + } + aria-label="pod config hidden controlled check" + onChange={() => setHidden(!hidden)} + id="workspace-kind-pod-config-hidden" + name="check5" + /> +
+ setLabels(newLabels)} /> + + +
+ + + + +
+ ); +}; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormResource.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormResource.tsx new file mode 100644 index 00000000..19c8d0ed --- /dev/null +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormResource.tsx @@ -0,0 +1,379 @@ +import React, { useCallback, useEffect, useState, useMemo } from 'react'; +import { + Button, + Grid, + GridItem, + Title, + FormFieldGroupExpandable, + FormFieldGroupHeader, + TextInput, + Checkbox, + HelperText, + HelperTextItem, +} from '@patternfly/react-core'; +import { PlusCircleIcon, TrashAltIcon } from '@patternfly/react-icons'; +import { generateUniqueId } from '~/app/pages/WorkspaceKinds/Form/helpers'; +import { isMemoryLimitLarger } from '~/shared/utilities/valueUnits'; +import { ResourceInputWrapper } from './ResourceInputWrapper'; + +export type PodResourceEntry = { + id: string; // Unique identifier for each resource entry + type: string; + request: string; + limit: string; +}; + +interface WorkspaceKindFormResourceProps { + setResources: (value: React.SetStateAction) => void; + cpu: PodResourceEntry; + memory: PodResourceEntry; + custom: PodResourceEntry[]; +} + +export const WorkspaceKindFormResource: React.FC = ({ + setResources, + cpu, + memory, + custom, +}) => { + // State for tracking limit toggles + const [cpuRequestEnabled, setCpuRequestEnabled] = useState(cpu.request.length > 0); + const [memoryRequestEnabled, setMemoryRequestEnabled] = useState(memory.request.length > 0); + const [cpuLimitEnabled, setCpuLimitEnabled] = useState(cpu.limit.length > 0); + const [memoryLimitEnabled, setMemoryLimitEnabled] = useState(memory.limit.length > 0); + const [customLimitsEnabled, setCustomLimitsEnabled] = useState>(() => { + const customToggles: Record = {}; + custom.forEach((res) => { + if (res.limit) { + customToggles[res.id] = true; + } + }); + return customToggles; + }); + + useEffect(() => { + setCpuRequestEnabled(cpu.request.length > 0); + setMemoryRequestEnabled(memory.request.length > 0); + setCpuLimitEnabled(cpu.request.length > 0 && cpu.limit.length > 0); + setMemoryLimitEnabled(memory.request.length > 0 && memory.limit.length > 0); + }, [cpu.limit.length, cpu.request.length, memory.limit.length, memory.request.length]); + + const handleChange = useCallback( + (resourceId: string, field: 'type' | 'request' | 'limit', value: string) => { + setResources((resources: PodResourceEntry[]) => + resources.map((r) => (r.id === resourceId ? { ...r, [field]: value } : r)), + ); + }, + [setResources], + ); + + const handleAddCustom = useCallback(() => { + setResources((resources: PodResourceEntry[]) => [ + ...resources, + { id: generateUniqueId(), type: '', request: '1', limit: '' }, + ]); + }, [setResources]); + + const handleRemoveCustom = useCallback( + (resourceId: string) => { + setResources((resources: PodResourceEntry[]) => resources.filter((r) => r.id !== resourceId)); + // Remove the corresponding limit toggle + const newCustomLimitsEnabled = { ...customLimitsEnabled }; + delete newCustomLimitsEnabled[resourceId]; + setCustomLimitsEnabled(newCustomLimitsEnabled); + }, + [customLimitsEnabled, setResources], + ); + + const handleCpuLimitToggle = useCallback( + (enabled: boolean) => { + setCpuLimitEnabled(enabled); + handleChange(cpu.id, 'limit', cpu.request); + if (!enabled) { + handleChange(cpu.id, 'limit', ''); + } + }, + [cpu.id, cpu.request, handleChange], + ); + + const handleCpuRequestToggle = useCallback( + (enabled: boolean) => { + setCpuRequestEnabled(enabled); + handleChange(cpu.id, 'request', '1'); + if (!enabled) { + handleChange(cpu.id, 'request', ''); + handleCpuLimitToggle(enabled); + } + }, + [cpu.id, handleChange, handleCpuLimitToggle], + ); + + const handleMemoryLimitToggle = useCallback( + (enabled: boolean) => { + setMemoryLimitEnabled(enabled); + handleChange(memory.id, 'limit', memory.request); + if (!enabled) { + handleChange(memory.id, 'limit', ''); + } + }, + [handleChange, memory.id, memory.request], + ); + + const handleMemoryRequestToggle = useCallback( + (enabled: boolean) => { + setMemoryRequestEnabled(enabled); + handleChange(memory.id, 'request', '1Mi'); + if (!enabled) { + handleChange(memory.id, 'request', ''); + handleMemoryLimitToggle(enabled); + } + }, + [handleChange, handleMemoryLimitToggle, memory.id], + ); + + const handleCustomLimitToggle = useCallback( + (resourceId: string, enabled: boolean) => { + setCustomLimitsEnabled((prev) => ({ ...prev, [resourceId]: enabled })); + if (!enabled) { + handleChange(resourceId, 'limit', ''); + } + }, + [handleChange], + ); + const cpuRequestLargerThanLimit = useMemo( + () => parseFloat(cpu.request) > parseFloat(cpu.limit), + [cpu.request, cpu.limit], + ); + + const memoryRequestLargerThanLimit = useMemo( + () => + memory.request.length > 0 && + memory.limit.length > 0 && + !isMemoryLimitLarger(memory.request, memory.limit, true), + [memory.request, memory.limit], + ); + + const requestRequestLargerThanLimit = useMemo( + () => + custom.reduce( + (prev, curr) => prev || parseFloat(curr.request) > parseFloat(curr.limit), + false, + ), + [custom], + ); + + const getResourceCountText = useCallback(() => { + const standardResourceCount = (cpu.request ? 1 : 0) + (memory.request ? 1 : 0); + const customResourceCount = custom.length; + if (standardResourceCount > 0 && customResourceCount > 0) { + return `${standardResourceCount} standard and ${customResourceCount} custom resources added`; + } + if (standardResourceCount > 0) { + return `${standardResourceCount} standard resources added`; + } + if (customResourceCount > 0) { + return `${customResourceCount} custom resources added`; + } + return '0 added'; + }, [cpu.request, memory.request, custom.length]); + + return ( + +
+ Optional: Configure k8s Pod Resource Requests & Limits. +
+
+ {getResourceCountText()} +
+ + } + /> + } + > + Standard Resources + + + handleCpuRequestToggle(checked)} + isChecked={cpuRequestEnabled} + label="CPU Request" + /> + + + handleMemoryRequestToggle(checked)} + isChecked={memoryRequestEnabled} + label="Memory Request" + /> + + + handleChange(cpu.id, 'request', value)} + placeholder="e.g. 1" + min={1} + aria-label="CPU request" + isDisabled={!cpuRequestEnabled} + /> + + + handleChange(memory.id, 'request', value)} + placeholder="e.g. 512Mi" + min={1} + aria-label="Memory request" + isDisabled={!memoryRequestEnabled} + /> + + + handleCpuLimitToggle(checked)} + isChecked={cpuLimitEnabled} + label="CPU Limit" + isDisabled={!cpuRequestEnabled} + aria-label="Enable CPU limit" + /> + + + handleMemoryLimitToggle(checked)} + isChecked={memoryLimitEnabled} + isDisabled={!memoryRequestEnabled} + label="Memory Limit" + aria-label="Enable Memory limit" + /> + + + handleChange(cpu.id, 'limit', value)} + placeholder="e.g. 2" + min={parseFloat(cpu.request)} + step={1} + aria-label="CPU limit" + isDisabled={!cpuRequestEnabled || !cpuLimitEnabled} + /> + + + handleChange(memory.id, 'limit', value)} + placeholder="e.g. 1Gi" + min={parseFloat(memory.request)} + aria-label="Memory limit" + isDisabled={!memoryRequestEnabled || !memoryLimitEnabled} + /> + + + {cpuRequestLargerThanLimit && ( + + + CPU limit should not be smaller than the request value + + + )} + + + {memoryRequestLargerThanLimit && ( + + + Memory limit should not be smaller than the request value + + + )} + + + Custom Resources + {custom.map((res) => ( + + + handleChange(res.id, 'type', value)} + /> + + + + + Request + + handleChange(res.id, 'request', value)} + placeholder="Request" + min={1} + aria-label="Custom resource request" + /> + + + { + handleChange(res.id, 'limit', res.request); + handleCustomLimitToggle(res.id, checked); + }} + aria-label={`Enable limit for ${res.type || 'custom resource'}`} + /> + + + handleChange(res.id, 'limit', value)} + placeholder="Limit" + min={parseFloat(res.request)} + isDisabled={!customLimitsEnabled[res.id]} + aria-label={`${res.type || 'Custom resource'} limit`} + /> + + + ))} + + {requestRequestLargerThanLimit && ( + + + Resource limit should not be smaller than the request value + + + )} +
+ ); +}; diff --git a/workspaces/frontend/src/app/routes.ts b/workspaces/frontend/src/app/routes.ts index ee0cf4d5..8379d179 100644 --- a/workspaces/frontend/src/app/routes.ts +++ b/workspaces/frontend/src/app/routes.ts @@ -6,6 +6,7 @@ export const AppRoutePaths = { workspaceKinds: '/workspacekinds', workspaceKindSummary: '/workspacekinds/:kind/summary', workspaceKindCreate: '/workspacekinds/create', + workspaceKindEdit: '/workspacekinds/:kind/edit', } satisfies Record; export type AppRoute = (typeof AppRoutePaths)[keyof typeof AppRoutePaths]; @@ -31,6 +32,9 @@ export type RouteParamsMap = { kind: string; }; workspaceKindCreate: undefined; + workspaceKindEdit: { + kind: string; + }; }; /** @@ -62,6 +66,9 @@ export type RouteStateMap = { workspaceKindCreate: { namespace: string; }; + workspaceKindEdit: { + workspaceKindName: string; + }; }; /** @@ -82,4 +89,5 @@ export type RouteSearchParamsMap = { workspaceKinds: undefined; workspaceKindSummary: undefined; workspaceKindCreate: undefined; + workspaceKindEdit: undefined; }; diff --git a/workspaces/frontend/src/app/types.ts b/workspaces/frontend/src/app/types.ts index ccc1523b..d0dcaf9b 100644 --- a/workspaces/frontend/src/app/types.ts +++ b/workspaces/frontend/src/app/types.ts @@ -72,12 +72,29 @@ export interface WorkspaceKindImagePort { protocol: 'HTTP'; // ONLY HTTP is supported at the moment, per https://github.com/thesuperzapper/kubeflow-notebooks-v2-design/blob/main/crds/workspace-kind.yaml#L275 } +export interface WorkspaceKindPodConfigValue extends WorkspacePodConfigValue { + resources?: { + requests: { + [key: string]: string; + }; + limits: { + [key: string]: string; + }; + }; +} + export interface WorkspaceKindImageConfigData { default: string; values: WorkspaceKindImageConfigValue[]; } +export interface WorkspaceKindPodConfigData { + default: string; + values: WorkspaceKindPodConfigValue[]; +} + export interface WorkspaceKindFormData { properties: WorkspaceKindProperties; imageConfig: WorkspaceKindImageConfigData; + podConfig: WorkspaceKindPodConfigData; } diff --git a/workspaces/frontend/src/shared/style/MUI-theme.scss b/workspaces/frontend/src/shared/style/MUI-theme.scss index ec9aa157..6084f158 100644 --- a/workspaces/frontend/src/shared/style/MUI-theme.scss +++ b/workspaces/frontend/src/shared/style/MUI-theme.scss @@ -316,6 +316,22 @@ outline: none; } +.mui-theme .workspacekind-form-resource-input, +.custom-resource-type-input { + // Make sure input and select have the same height in ResourceInputWrapper + .pf-v6-c-form-control { + --pf-v6-c-form-control--PaddingBlockStart: var(--mui-spacing-8px); + --pf-v6-c-form-control--PaddingBlockEnd: var(--mui-spacing-8px); + + &:has(select) { + --pf-v6-c-form-control--PaddingInlineEnd: calc( + var(--pf-v6-c-form-control__select--PaddingInlineEnd) + + var(--pf-v6-c-form-control__icon--FontSize) + ); + } + } +} + .mui-theme .pf-v6-c-text-input-group__text-input:focus { --pf-v6-c-form-control--OutlineOffset: none; outline: none; diff --git a/workspaces/frontend/src/shared/utilities/WorkspaceUtils.ts b/workspaces/frontend/src/shared/utilities/WorkspaceUtils.ts index a732b4af..ff07e661 100644 --- a/workspaces/frontend/src/shared/utilities/WorkspaceUtils.ts +++ b/workspaces/frontend/src/shared/utilities/WorkspaceUtils.ts @@ -13,6 +13,20 @@ export enum YesNoValue { No = 'No', } +const RESOURCE_UNIT_CONFIG = { + cpu: CPU_UNITS, + memory: MEMORY_UNITS_FOR_PARSING, + gpu: OTHER, +}; + +export const parseResourceValue = ( + value: string, + resourceType: ResourceType, +): [number | undefined, { name: string; unit: string } | undefined] => { + const units = RESOURCE_UNIT_CONFIG[resourceType]; + return splitValueUnit(value, units); +}; + export const extractResourceValue = ( workspace: Workspace, resourceType: ResourceType, @@ -24,18 +38,13 @@ export const formatResourceValue = (v: string | undefined, resourceType?: Resour if (v === undefined) { return '-'; } - switch (resourceType) { - case 'cpu': { - const [cpuValue, cpuUnit] = splitValueUnit(v, CPU_UNITS); - return `${cpuValue ?? ''} ${cpuUnit.name}`; - } - case 'memory': { - const [memoryValue, memoryUnit] = splitValueUnit(v, MEMORY_UNITS_FOR_PARSING); - return `${memoryValue ?? ''} ${memoryUnit.name}`; - } - default: - return v; + + if (!resourceType) { + return v; } + + const [value, unit] = parseResourceValue(v, resourceType); + return `${value || ''} ${unit?.name || ''}`.trim(); }; export const formatResourceFromWorkspace = ( diff --git a/workspaces/frontend/src/shared/utilities/valueUnits.ts b/workspaces/frontend/src/shared/utilities/valueUnits.ts index 9efb05d0..5017cf6e 100644 --- a/workspaces/frontend/src/shared/utilities/valueUnits.ts +++ b/workspaces/frontend/src/shared/utilities/valueUnits.ts @@ -19,7 +19,7 @@ export type UnitOption = { export const CPU_UNITS: UnitOption[] = [ { name: 'Cores', unit: '', weight: 1000 }, - { name: 'Milicores', unit: 'm', weight: 1 }, + { name: 'Millicores', unit: 'm', weight: 1 }, ]; export const MEMORY_UNITS_FOR_SELECTION: UnitOption[] = [ { name: 'GiB', unit: 'Gi', weight: 1024 }, @@ -40,7 +40,7 @@ export const MEMORY_UNITS_FOR_PARSING: UnitOption[] = [ { name: 'KiB', unit: 'Ki', weight: 1024 }, { name: 'B', unit: '', weight: 1 }, ]; -export const OTHER: UnitOption[] = [{ name: 'Other', unit: '', weight: 1 }]; +export const OTHER: UnitOption[] = [{ name: '', unit: '', weight: 1 }]; export const splitValueUnit = ( value: ValueUnitString, From db3e0005a72407d5878b54f3eebd2ec5b55f3f01 Mon Sep 17 00:00:00 2001 From: Dominik Kawka <31955648+dominikkawka@users.noreply.github.com> Date: Fri, 4 Jul 2025 15:40:19 +0100 Subject: [PATCH 24/68] fix: removed blank space on left of dropdown options (#329) Signed-off-by: DominikKawka --- workspaces/frontend/src/shared/components/Filter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/frontend/src/shared/components/Filter.tsx b/workspaces/frontend/src/shared/components/Filter.tsx index 8d7edca3..5e788211 100644 --- a/workspaces/frontend/src/shared/components/Filter.tsx +++ b/workspaces/frontend/src/shared/components/Filter.tsx @@ -215,7 +215,7 @@ const Filter = React.forwardRef( () => ( onFilterSelect(itemId)}> - + {Object.keys(columnDefinition).map((columnKey: string) => ( {columnDefinition[columnKey]} From 60d6de01f68f243ca66336b6c3b2c69e271150a0 Mon Sep 17 00:00:00 2001 From: asaadbalum <154635253+asaadbalum@users.noreply.github.com> Date: Sun, 6 Jul 2025 09:57:21 +0300 Subject: [PATCH 25/68] feat(ws): backend api to create wsk with YAML (#434) * feat(ws): Notebooks 2.0 // Backend // API that allows frontend to upload a YAML file containing a full new WorkspaceKind definition Signed-off-by: Asaad Balum * mathew: 1 Signed-off-by: Mathew Wicks <5735406+thesuperzapper@users.noreply.github.com> --------- Signed-off-by: Asaad Balum Signed-off-by: Mathew Wicks <5735406+thesuperzapper@users.noreply.github.com> Co-authored-by: Mathew Wicks <5735406+thesuperzapper@users.noreply.github.com> --- workspaces/backend/api/app.go | 39 ++- workspaces/backend/api/helpers.go | 19 +- workspaces/backend/api/response_errors.go | 12 + workspaces/backend/api/suite_test.go | 1 + .../backend/api/workspacekinds_handler.go | 99 +++++++ .../api/workspacekinds_handler_test.go | 253 ++++++++++++++++++ workspaces/backend/api/workspaces_handler.go | 8 +- .../backend/api/workspaces_handler_test.go | 4 +- .../repositories/workspacekinds/repo.go | 24 ++ workspaces/backend/openapi/docs.go | 92 +++++++ workspaces/backend/openapi/swagger.json | 92 +++++++ 11 files changed, 627 insertions(+), 16 deletions(-) diff --git a/workspaces/backend/api/app.go b/workspaces/backend/api/app.go index ef3fe7db..2f76c252 100644 --- a/workspaces/backend/api/app.go +++ b/workspaces/backend/api/app.go @@ -17,11 +17,13 @@ limitations under the License. package api import ( + "fmt" "log/slog" "net/http" "github.com/julienschmidt/httprouter" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authorization/authorizer" "sigs.k8s.io/controller-runtime/pkg/client" @@ -35,6 +37,9 @@ const ( Version = "1.0.0" PathPrefix = "/api/v1" + MediaTypeJson = "application/json" + MediaTypeYaml = "application/yaml" + NamespacePathParam = "namespace" ResourceNamePathParam = "name" @@ -59,12 +64,13 @@ const ( ) type App struct { - Config *config.EnvConfig - logger *slog.Logger - repositories *repositories.Repositories - Scheme *runtime.Scheme - RequestAuthN authenticator.Request - RequestAuthZ authorizer.Authorizer + Config *config.EnvConfig + logger *slog.Logger + repositories *repositories.Repositories + Scheme *runtime.Scheme + StrictYamlSerializer runtime.Serializer + RequestAuthN authenticator.Request + RequestAuthZ authorizer.Authorizer } // NewApp creates a new instance of the app @@ -72,13 +78,21 @@ func NewApp(cfg *config.EnvConfig, logger *slog.Logger, cl client.Client, scheme // TODO: log the configuration on startup + // get a serializer for Kubernetes YAML + codecFactory := serializer.NewCodecFactory(scheme) + yamlSerializerInfo, found := runtime.SerializerInfoForMediaType(codecFactory.SupportedMediaTypes(), runtime.ContentTypeYAML) + if !found { + return nil, fmt.Errorf("unable to find Kubernetes serializer for media type: %s", runtime.ContentTypeYAML) + } + app := &App{ - Config: cfg, - logger: logger, - repositories: repositories.NewRepositories(cl), - Scheme: scheme, - RequestAuthN: reqAuthN, - RequestAuthZ: reqAuthZ, + Config: cfg, + logger: logger, + repositories: repositories.NewRepositories(cl), + Scheme: scheme, + StrictYamlSerializer: yamlSerializerInfo.StrictSerializer, + RequestAuthN: reqAuthN, + RequestAuthZ: reqAuthZ, } return app, nil } @@ -106,6 +120,7 @@ func (a *App) Routes() http.Handler { // workspacekinds router.GET(AllWorkspaceKindsPath, a.GetWorkspaceKindsHandler) router.GET(WorkspaceKindsByNamePath, a.GetWorkspaceKindHandler) + router.POST(AllWorkspaceKindsPath, a.CreateWorkspaceKindHandler) // swagger router.GET(SwaggerPath, a.GetSwaggerHandler) diff --git a/workspaces/backend/api/helpers.go b/workspaces/backend/api/helpers.go index 0076a060..7583ea70 100644 --- a/workspaces/backend/api/helpers.go +++ b/workspaces/backend/api/helpers.go @@ -18,6 +18,7 @@ package api import ( "encoding/json" + "errors" "fmt" "mime" "net/http" @@ -46,7 +47,7 @@ func (a *App) WriteJSON(w http.ResponseWriter, status int, data any, headers htt w.Header()[key] = value } - w.Header().Set("Content-Type", "application/json") + w.Header().Set("Content-Type", MediaTypeJson) w.WriteHeader(status) _, err = w.Write(js) if err != nil { @@ -61,11 +62,21 @@ func (a *App) DecodeJSON(r *http.Request, v any) error { decoder := json.NewDecoder(r.Body) decoder.DisallowUnknownFields() if err := decoder.Decode(v); err != nil { + // NOTE: we don't wrap this error so we can unpack it in the caller + if a.IsMaxBytesError(err) { + return err + } return fmt.Errorf("error decoding JSON: %w", err) } return nil } +// IsMaxBytesError checks if the error is an instance of http.MaxBytesError. +func (a *App) IsMaxBytesError(err error) bool { + var maxBytesError *http.MaxBytesError + return errors.As(err, &maxBytesError) +} + // ValidateContentType validates the Content-Type header of the request. // If this method returns false, the request has been handled and the caller should return immediately. // If this method returns true, the request has the correct Content-Type. @@ -94,3 +105,9 @@ func (a *App) LocationGetWorkspace(namespace, name string) string { path = strings.Replace(path, ":"+ResourceNamePathParam, name, 1) return path } + +// LocationGetWorkspaceKind returns the GET location (HTTP path) for a workspace kind resource. +func (a *App) LocationGetWorkspaceKind(name string) string { + path := strings.Replace(WorkspaceKindsByNamePath, ":"+ResourceNamePathParam, name, 1) + return path +} diff --git a/workspaces/backend/api/response_errors.go b/workspaces/backend/api/response_errors.go index cf7e1dd9..b9aadba3 100644 --- a/workspaces/backend/api/response_errors.go +++ b/workspaces/backend/api/response_errors.go @@ -156,6 +156,18 @@ func (a *App) conflictResponse(w http.ResponseWriter, r *http.Request, err error a.errorResponse(w, r, httpError) } +// HTTP:413 +func (a *App) requestEntityTooLargeResponse(w http.ResponseWriter, r *http.Request, err error) { + httpError := &HTTPError{ + StatusCode: http.StatusRequestEntityTooLarge, + ErrorResponse: ErrorResponse{ + Code: strconv.Itoa(http.StatusRequestEntityTooLarge), + Message: err.Error(), + }, + } + a.errorResponse(w, r, httpError) +} + // HTTP:415 func (a *App) unsupportedMediaTypeResponse(w http.ResponseWriter, r *http.Request, err error) { httpError := &HTTPError{ diff --git a/workspaces/backend/api/suite_test.go b/workspaces/backend/api/suite_test.go index ad3e518a..ce03bdad 100644 --- a/workspaces/backend/api/suite_test.go +++ b/workspaces/backend/api/suite_test.go @@ -150,6 +150,7 @@ var _ = BeforeSuite(func() { By("creating the application") // NOTE: we use the `k8sClient` rather than `k8sManager.GetClient()` to avoid race conditions with the cached client a, err = NewApp(&config.EnvConfig{}, appLogger, k8sClient, k8sManager.GetScheme(), reqAuthN, reqAuthZ) + Expect(err).NotTo(HaveOccurred()) go func() { defer GinkgoRecover() diff --git a/workspaces/backend/api/workspacekinds_handler.go b/workspaces/backend/api/workspacekinds_handler.go index c8e0b6af..85bd5a62 100644 --- a/workspaces/backend/api/workspacekinds_handler.go +++ b/workspaces/backend/api/workspacekinds_handler.go @@ -18,11 +18,15 @@ package api import ( "errors" + "fmt" + "io" "net/http" "github.com/julienschmidt/httprouter" kubefloworgv1beta1 "github.com/kubeflow/notebooks/workspaces/controller/api/v1beta1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" "github.com/kubeflow/notebooks/workspaces/backend/internal/auth" @@ -31,6 +35,9 @@ import ( repository "github.com/kubeflow/notebooks/workspaces/backend/internal/repositories/workspacekinds" ) +// TODO: this should wrap the models.WorkspaceKindUpdate once we implement the update handler +type WorkspaceKindCreateEnvelope Envelope[*models.WorkspaceKind] + type WorkspaceKindListEnvelope Envelope[[]models.WorkspaceKind] type WorkspaceKindEnvelope Envelope[models.WorkspaceKind] @@ -123,3 +130,95 @@ func (a *App) GetWorkspaceKindsHandler(w http.ResponseWriter, r *http.Request, _ responseEnvelope := &WorkspaceKindListEnvelope{Data: workspaceKinds} a.dataResponse(w, r, responseEnvelope) } + +// CreateWorkspaceKindHandler creates a new workspace kind. +// +// @Summary Create workspace kind +// @Description Creates a new workspace kind. +// @Tags workspacekinds +// @Accept application/yaml +// @Produce json +// @Param body body string true "Kubernetes YAML manifest of a WorkspaceKind" +// @Success 201 {object} WorkspaceKindEnvelope "WorkspaceKind created successfully" +// @Failure 400 {object} ErrorEnvelope "Bad Request." +// @Failure 401 {object} ErrorEnvelope "Unauthorized. Authentication is required." +// @Failure 403 {object} ErrorEnvelope "Forbidden. User does not have permission to create WorkspaceKind." +// @Failure 409 {object} ErrorEnvelope "Conflict. WorkspaceKind with the same name already exists." +// @Failure 413 {object} ErrorEnvelope "Request Entity Too Large. The request body is too large."" +// @Failure 415 {object} ErrorEnvelope "Unsupported Media Type. Content-Type header is not correct." +// @Failure 422 {object} ErrorEnvelope "Unprocessable Entity. Validation error." +// @Failure 500 {object} ErrorEnvelope "Internal server error. An unexpected error occurred on the server." +// @Router /workspacekinds [post] +func (a *App) CreateWorkspaceKindHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + + // validate the Content-Type header + if success := a.ValidateContentType(w, r, MediaTypeYaml); !success { + return + } + + // decode the request body + bodyBytes, err := io.ReadAll(r.Body) + if err != nil { + if a.IsMaxBytesError(err) { + a.requestEntityTooLargeResponse(w, r, err) + return + } + a.badRequestResponse(w, r, err) + return + } + workspaceKind := &kubefloworgv1beta1.WorkspaceKind{} + err = runtime.DecodeInto(a.StrictYamlSerializer, bodyBytes, workspaceKind) + if err != nil { + a.badRequestResponse(w, r, fmt.Errorf("error decoding request body: %w", err)) + return + } + + // validate the workspace kind + // NOTE: we only do basic validation so we know it's safe to send to the Kubernetes API server + // comprehensive validation will be done by Kubernetes + // NOTE: checking the name field is non-empty also verifies that the workspace kind is not nil/empty + var valErrs field.ErrorList + wskNamePath := field.NewPath("metadata", "name") + valErrs = append(valErrs, helper.ValidateFieldIsDNS1123Subdomain(wskNamePath, workspaceKind.Name)...) + if len(valErrs) > 0 { + a.failedValidationResponse(w, r, errMsgRequestBodyInvalid, valErrs, nil) + return + } + + // =========================== AUTH =========================== + authPolicies := []*auth.ResourcePolicy{ + auth.NewResourcePolicy( + auth.ResourceVerbCreate, + &kubefloworgv1beta1.WorkspaceKind{ + ObjectMeta: metav1.ObjectMeta{ + Name: workspaceKind.Name, + }, + }, + ), + } + if success := a.requireAuth(w, r, authPolicies); !success { + return + } + // ============================================================ + + createdWorkspaceKind, err := a.repositories.WorkspaceKind.Create(r.Context(), workspaceKind) + if err != nil { + if errors.Is(err, repository.ErrWorkspaceKindAlreadyExists) { + a.conflictResponse(w, r, err) + return + } + if apierrors.IsInvalid(err) { + causes := helper.StatusCausesFromAPIStatus(err) + a.failedValidationResponse(w, r, errMsgKubernetesValidation, nil, causes) + return + } + a.serverErrorResponse(w, r, fmt.Errorf("error creating workspace kind: %w", err)) + return + } + + // calculate the GET location for the created workspace kind (for the Location header) + location := a.LocationGetWorkspaceKind(createdWorkspaceKind.Name) + + responseEnvelope := &WorkspaceKindCreateEnvelope{Data: createdWorkspaceKind} + a.createdResponse(w, r, responseEnvelope, location) +} diff --git a/workspaces/backend/api/workspacekinds_handler_test.go b/workspaces/backend/api/workspacekinds_handler_test.go index f37c58c9..19853676 100644 --- a/workspaces/backend/api/workspacekinds_handler_test.go +++ b/workspaces/backend/api/workspacekinds_handler_test.go @@ -17,6 +17,7 @@ limitations under the License. package api import ( + "bytes" "encoding/json" "fmt" "io" @@ -31,6 +32,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/validation/field" models "github.com/kubeflow/notebooks/workspaces/backend/internal/models/workspacekinds" ) @@ -254,4 +256,255 @@ var _ = Describe("WorkspaceKinds Handler", func() { Expect(rs.StatusCode).To(Equal(http.StatusNotFound), descUnexpectedHTTPStatus, rr.Body.String()) }) }) + + // NOTE: these tests create and delete resources on the cluster, so cannot be run in parallel. + // therefore, we run them using the `Serial` Ginkgo decorator. + Context("when creating a WorkspaceKind", Serial, func() { + + var newWorkspaceKindName = "wsk-create-test" + var validYAML []byte + + BeforeEach(func() { + validYAML = []byte(fmt.Sprintf(` +apiVersion: kubeflow.org/v1beta1 +kind: WorkspaceKind +metadata: + name: %s +spec: + spawner: + displayName: "JupyterLab Notebook" + description: "A Workspace which runs JupyterLab in a Pod" + icon: + url: "https://jupyter.org/assets/favicons/apple-touch-icon-152x152.png" + logo: + url: "https://upload.wikimedia.org/wikipedia/commons/3/38/Jupyter_logo.svg" + podTemplate: + serviceAccount: + name: "default-editor" + volumeMounts: + home: "/home/jovyan" + options: + imageConfig: + spawner: + default: "jupyterlab_scipy_190" + values: + - id: "jupyterlab_scipy_190" + spawner: + displayName: "jupyter-scipy:v1.9.0" + description: "JupyterLab, with SciPy Packages" + spec: + image: "ghcr.io/kubeflow/kubeflow/notebook-servers/jupyter-scipy:v1.9.0" + imagePullPolicy: "IfNotPresent" + ports: + - id: "jupyterlab" + displayName: "JupyterLab" + port: 8888 + protocol: "HTTP" + podConfig: + spawner: + default: "tiny_cpu" + values: + - id: "tiny_cpu" + spawner: + displayName: "Tiny CPU" + description: "Pod with 0.1 CPU, 128 Mb RAM" + spec: + resources: + requests: + cpu: 100m + memory: 128Mi +`, newWorkspaceKindName)) + }) + + AfterEach(func() { + By("cleaning up the created WorkspaceKind") + wsk := &kubefloworgv1beta1.WorkspaceKind{ + ObjectMeta: metav1.ObjectMeta{ + Name: newWorkspaceKindName, + }, + } + _ = k8sClient.Delete(ctx, wsk) + }) + + It("should succeed when creating a WorkspaceKind with valid YAML", func() { + By("creating the HTTP request") + req, err := http.NewRequest(http.MethodPost, AllWorkspaceKindsPath, bytes.NewReader(validYAML)) + Expect(err).NotTo(HaveOccurred()) + req.Header.Set("Content-Type", MediaTypeYaml) + req.Header.Set(userIdHeader, adminUser) + + By("executing CreateWorkspaceKindHandler") + rr := httptest.NewRecorder() + a.CreateWorkspaceKindHandler(rr, req, httprouter.Params{}) + rs := rr.Result() + defer rs.Body.Close() + + By("verifying the HTTP response status code") + Expect(rs.StatusCode).To(Equal(http.StatusCreated), descUnexpectedHTTPStatus, rr.Body.String()) + + By("verifying the resource was created in the cluster") + createdWsk := &kubefloworgv1beta1.WorkspaceKind{} + err = k8sClient.Get(ctx, types.NamespacedName{Name: newWorkspaceKindName}, createdWsk) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should fail to create a WorkspaceKind with no name in the YAML", func() { + missingNameYAML := []byte(` +apiVersion: kubeflow.org/v1beta1 +kind: WorkspaceKind +metadata: {} +spec: + spawner: + displayName: "This will fail"`) + + By("creating the HTTP request") + req, err := http.NewRequest(http.MethodPost, AllWorkspaceKindsPath, bytes.NewReader(missingNameYAML)) + Expect(err).NotTo(HaveOccurred()) + req.Header.Set("Content-Type", MediaTypeYaml) + req.Header.Set(userIdHeader, adminUser) + + By("executing CreateWorkspaceKindHandler") + rr := httptest.NewRecorder() + a.CreateWorkspaceKindHandler(rr, req, httprouter.Params{}) + rs := rr.Result() + defer rs.Body.Close() + + By("verifying the HTTP response status code") + Expect(rs.StatusCode).To(Equal(http.StatusUnprocessableEntity), descUnexpectedHTTPStatus, rr.Body.String()) + + By("decoding the error response") + var response ErrorEnvelope + err = json.Unmarshal(rr.Body.Bytes(), &response) + Expect(err).NotTo(HaveOccurred()) + + By("verifying the error message indicates a validation failure") + Expect(response.Error.Cause.ValidationErrors).To(BeComparableTo( + []ValidationError{ + { + Type: field.ErrorTypeRequired, + Field: "metadata.name", + Message: field.ErrorTypeRequired.String(), + }, + }, + )) + }) + + It("should fail to create a WorkspaceKind that already exists", func() { + By("creating the HTTP request") + req1, err := http.NewRequest(http.MethodPost, AllWorkspaceKindsPath, bytes.NewReader(validYAML)) + Expect(err).NotTo(HaveOccurred()) + req1.Header.Set("Content-Type", MediaTypeYaml) + req1.Header.Set(userIdHeader, adminUser) + + By("executing CreateWorkspaceKindHandler for the first time") + rr1 := httptest.NewRecorder() + a.CreateWorkspaceKindHandler(rr1, req1, httprouter.Params{}) + rs1 := rr1.Result() + defer rs1.Body.Close() + + By("verifying the HTTP response status code for the first request") + Expect(rs1.StatusCode).To(Equal(http.StatusCreated), descUnexpectedHTTPStatus, rr1.Body.String()) + + By("creating a second HTTP request") + req2, err := http.NewRequest(http.MethodPost, AllWorkspaceKindsPath, bytes.NewReader(validYAML)) + Expect(err).NotTo(HaveOccurred()) + req2.Header.Set("Content-Type", MediaTypeYaml) + req2.Header.Set(userIdHeader, adminUser) + + By("executing CreateWorkspaceKindHandler for the second time") + rr2 := httptest.NewRecorder() + a.CreateWorkspaceKindHandler(rr2, req2, httprouter.Params{}) + rs2 := rr2.Result() + defer rs2.Body.Close() + + By("verifying the HTTP response status code for the second request") + Expect(rs2.StatusCode).To(Equal(http.StatusConflict), descUnexpectedHTTPStatus, rr1.Body.String()) + }) + + It("should fail when the YAML has the wrong kind", func() { + wrongKindYAML := []byte(` +apiVersion: v1 +kind: Pod +metadata: + name: i-am-the-wrong-kind`) + + By("creating the HTTP request") + req, err := http.NewRequest(http.MethodPost, AllWorkspaceKindsPath, bytes.NewReader(wrongKindYAML)) + Expect(err).NotTo(HaveOccurred()) + req.Header.Set("Content-Type", MediaTypeYaml) + req.Header.Set(userIdHeader, adminUser) + + By("executing CreateWorkspaceKindHandler") + rr := httptest.NewRecorder() + a.CreateWorkspaceKindHandler(rr, req, httprouter.Params{}) + rs := rr.Result() + defer rs.Body.Close() + + By("verifying the HTTP response status code") + Expect(rs.StatusCode).To(Equal(http.StatusBadRequest), descUnexpectedHTTPStatus, rr.Body.String()) + Expect(rr.Body.String()).To(ContainSubstring("unable to decode /v1, Kind=Pod into *v1beta1.WorkspaceKind")) + }) + + It("should fail when the body is not valid YAML", func() { + notYAML := []byte(`this is not yaml {`) + + By("creating the HTTP request") + req, err := http.NewRequest(http.MethodPost, AllWorkspaceKindsPath, bytes.NewReader(notYAML)) + Expect(err).NotTo(HaveOccurred()) + req.Header.Set("Content-Type", MediaTypeYaml) + req.Header.Set(userIdHeader, adminUser) + + By("executing CreateWorkspaceKindHandler") + rr := httptest.NewRecorder() + a.CreateWorkspaceKindHandler(rr, req, httprouter.Params{}) + rs := rr.Result() + defer rs.Body.Close() + + By("verifying the HTTP response status code") + Expect(rs.StatusCode).To(Equal(http.StatusBadRequest), descUnexpectedHTTPStatus, rr.Body.String()) + + By("decoding the error response") + var response ErrorEnvelope + err = json.Unmarshal(rr.Body.Bytes(), &response) + Expect(err).NotTo(HaveOccurred()) + + By("verifying the error message indicates a decoding failure") + Expect(response.Error.Message).To(ContainSubstring("error decoding request body: couldn't get version/kind; json parse error")) + }) + + It("should fail for an empty YAML object", func() { + invalidYAML := []byte("{}") + + By("creating the HTTP request") + req, err := http.NewRequest(http.MethodPost, AllWorkspaceKindsPath, bytes.NewReader(invalidYAML)) + Expect(err).NotTo(HaveOccurred()) + req.Header.Set("Content-Type", MediaTypeYaml) + req.Header.Set(userIdHeader, adminUser) + + By("executing the CreateWorkspaceKindHandler") + rr := httptest.NewRecorder() + a.CreateWorkspaceKindHandler(rr, req, httprouter.Params{}) + rs := rr.Result() + defer rs.Body.Close() + + By("verifying the HTTP response status code") + Expect(rs.StatusCode).To(Equal(http.StatusUnprocessableEntity), descUnexpectedHTTPStatus, rr.Body.String()) + + By("decoding the error response") + var response ErrorEnvelope + err = json.Unmarshal(rr.Body.Bytes(), &response) + Expect(err).NotTo(HaveOccurred()) + + By("verifying the error message indicates a validation failure") + Expect(response.Error.Cause.ValidationErrors).To(BeComparableTo( + []ValidationError{ + { + Type: field.ErrorTypeRequired, + Field: "metadata.name", + Message: field.ErrorTypeRequired.String(), + }, + }, + )) + }) + }) }) diff --git a/workspaces/backend/api/workspaces_handler.go b/workspaces/backend/api/workspaces_handler.go index 2cf909fd..97354b12 100644 --- a/workspaces/backend/api/workspaces_handler.go +++ b/workspaces/backend/api/workspaces_handler.go @@ -176,6 +176,8 @@ func (a *App) GetWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps ht // @Failure 401 {object} ErrorEnvelope "Unauthorized. Authentication is required." // @Failure 403 {object} ErrorEnvelope "Forbidden. User does not have permission to create workspace." // @Failure 409 {object} ErrorEnvelope "Conflict. Workspace with the same name already exists." +// @Failure 413 {object} ErrorEnvelope "Request Entity Too Large. The request body is too large." +// @Failure 415 {object} ErrorEnvelope "Unsupported Media Type. Content-Type header is not correct." // @Failure 500 {object} ErrorEnvelope "Internal server error. An unexpected error occurred on the server." // @Router /workspaces/{namespace} [post] func (a *App) CreateWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { @@ -190,7 +192,7 @@ func (a *App) CreateWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps } // validate the Content-Type header - if success := a.ValidateContentType(w, r, "application/json"); !success { + if success := a.ValidateContentType(w, r, MediaTypeJson); !success { return } @@ -198,6 +200,10 @@ func (a *App) CreateWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps bodyEnvelope := &WorkspaceCreateEnvelope{} err := a.DecodeJSON(r, bodyEnvelope) if err != nil { + if a.IsMaxBytesError(err) { + a.requestEntityTooLargeResponse(w, r, err) + return + } a.badRequestResponse(w, r, fmt.Errorf("error decoding request body: %w", err)) return } diff --git a/workspaces/backend/api/workspaces_handler_test.go b/workspaces/backend/api/workspaces_handler_test.go index 179b91b8..10f1b445 100644 --- a/workspaces/backend/api/workspaces_handler_test.go +++ b/workspaces/backend/api/workspaces_handler_test.go @@ -729,7 +729,7 @@ var _ = Describe("Workspaces Handler", func() { path := strings.Replace(WorkspacesByNamespacePath, ":"+NamespacePathParam, namespaceNameCrud, 1) req, err := http.NewRequest(http.MethodPost, path, strings.NewReader(string(bodyEnvelopeJSON))) Expect(err).NotTo(HaveOccurred()) - req.Header.Set("Content-Type", "application/json") + req.Header.Set("Content-Type", MediaTypeJson) By("setting the auth headers") req.Header.Set(userIdHeader, adminUser) @@ -845,7 +845,7 @@ var _ = Describe("Workspaces Handler", func() { path := strings.Replace(WorkspacesByNamespacePath, ":"+NamespacePathParam, namespaceNameCrud, 1) req, err := http.NewRequest(http.MethodPost, path, strings.NewReader(string(bodyEnvelopeJSON))) Expect(err).NotTo(HaveOccurred()) - req.Header.Set("Content-Type", "application/json") + req.Header.Set("Content-Type", MediaTypeJson) req.Header.Set(userIdHeader, adminUser) rr := httptest.NewRecorder() diff --git a/workspaces/backend/internal/repositories/workspacekinds/repo.go b/workspaces/backend/internal/repositories/workspacekinds/repo.go index 3796e297..02cd208b 100644 --- a/workspaces/backend/internal/repositories/workspacekinds/repo.go +++ b/workspaces/backend/internal/repositories/workspacekinds/repo.go @@ -28,6 +28,7 @@ import ( ) var ErrWorkspaceKindNotFound = errors.New("workspace kind not found") +var ErrWorkspaceKindAlreadyExists = errors.New("workspacekind already exists") type WorkspaceKindRepository struct { client client.Client @@ -68,3 +69,26 @@ func (r *WorkspaceKindRepository) GetWorkspaceKinds(ctx context.Context) ([]mode return workspaceKindsModels, nil } + +func (r *WorkspaceKindRepository) Create(ctx context.Context, workspaceKind *kubefloworgv1beta1.WorkspaceKind) (*models.WorkspaceKind, error) { + // create workspace kind + if err := r.client.Create(ctx, workspaceKind); err != nil { + if apierrors.IsAlreadyExists(err) { + return nil, ErrWorkspaceKindAlreadyExists + } + if apierrors.IsInvalid(err) { + // NOTE: we don't wrap this error so we can unpack it in the caller + // and extract the validation errors returned by the Kubernetes API server + return nil, err + } + return nil, err + } + + // convert the created workspace to a WorkspaceKindUpdate model + // + // TODO: this function should return the WorkspaceKindUpdate model, once the update WSK api is implemented + // + createdWorkspaceKindModel := models.NewWorkspaceKindModelFromWorkspaceKind(workspaceKind) + + return &createdWorkspaceKindModel, nil +} diff --git a/workspaces/backend/openapi/docs.go b/workspaces/backend/openapi/docs.go index e0fc73e4..ba09bcc6 100644 --- a/workspaces/backend/openapi/docs.go +++ b/workspaces/backend/openapi/docs.go @@ -122,6 +122,86 @@ const docTemplate = `{ } } } + }, + "post": { + "description": "Creates a new workspace kind.", + "consumes": [ + "application/yaml" + ], + "produces": [ + "application/json" + ], + "tags": [ + "workspacekinds" + ], + "summary": "Create workspace kind", + "parameters": [ + { + "description": "Kubernetes YAML manifest of a WorkspaceKind", + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "WorkspaceKind created successfully", + "schema": { + "$ref": "#/definitions/api.WorkspaceKindEnvelope" + } + }, + "400": { + "description": "Bad Request.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "401": { + "description": "Unauthorized. Authentication is required.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "403": { + "description": "Forbidden. User does not have permission to create WorkspaceKind.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "409": { + "description": "Conflict. WorkspaceKind with the same name already exists.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "413": { + "description": "Request Entity Too Large. The request body is too large.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "415": { + "description": "Unsupported Media Type. Content-Type header is not correct.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "422": { + "description": "Unprocessable Entity. Validation error.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "500": { + "description": "Internal server error. An unexpected error occurred on the server.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + } + } } }, "/workspacekinds/{name}": { @@ -352,6 +432,18 @@ const docTemplate = `{ "$ref": "#/definitions/api.ErrorEnvelope" } }, + "413": { + "description": "Request Entity Too Large. The request body is too large.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "415": { + "description": "Unsupported Media Type. Content-Type header is not correct.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, "500": { "description": "Internal server error. An unexpected error occurred on the server.", "schema": { diff --git a/workspaces/backend/openapi/swagger.json b/workspaces/backend/openapi/swagger.json index 4f9fdeff..31c2b554 100644 --- a/workspaces/backend/openapi/swagger.json +++ b/workspaces/backend/openapi/swagger.json @@ -120,6 +120,86 @@ } } } + }, + "post": { + "description": "Creates a new workspace kind.", + "consumes": [ + "application/yaml" + ], + "produces": [ + "application/json" + ], + "tags": [ + "workspacekinds" + ], + "summary": "Create workspace kind", + "parameters": [ + { + "description": "Kubernetes YAML manifest of a WorkspaceKind", + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "WorkspaceKind created successfully", + "schema": { + "$ref": "#/definitions/api.WorkspaceKindEnvelope" + } + }, + "400": { + "description": "Bad Request.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "401": { + "description": "Unauthorized. Authentication is required.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "403": { + "description": "Forbidden. User does not have permission to create WorkspaceKind.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "409": { + "description": "Conflict. WorkspaceKind with the same name already exists.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "413": { + "description": "Request Entity Too Large. The request body is too large.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "415": { + "description": "Unsupported Media Type. Content-Type header is not correct.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "422": { + "description": "Unprocessable Entity. Validation error.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "500": { + "description": "Internal server error. An unexpected error occurred on the server.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + } + } } }, "/workspacekinds/{name}": { @@ -350,6 +430,18 @@ "$ref": "#/definitions/api.ErrorEnvelope" } }, + "413": { + "description": "Request Entity Too Large. The request body is too large.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "415": { + "description": "Unsupported Media Type. Content-Type header is not correct.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, "500": { "description": "Internal server error. An unexpected error occurred on the server.", "schema": { From 08c206d619c79cb9942891fe62f1da88c1c7d36f Mon Sep 17 00:00:00 2001 From: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> Date: Mon, 7 Jul 2025 08:14:21 -0300 Subject: [PATCH 26/68] feat(ws): prepare frontend for validation errors during WorkspaceKind creation (#471) * feat(ws): prepare frontend for validation errors during WorkspaceKind creation Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> * feat(ws): extract validation alert to its own component Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> * fix(ws): use error icon for helper text Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> --------- Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> --- workspaces/frontend/src/app/NavSidebar.tsx | 2 +- .../app/components/ValidationErrorAlert.tsx | 24 ++++++ .../WorkspaceKinds/Form/WorkspaceKindForm.tsx | 45 ++++++++-- .../fileUpload/WorkspaceKindFileUpload.tsx | 85 ++++++++++--------- .../shared/api/__tests__/errorUtils.spec.ts | 17 ++-- .../api/__tests__/notebookService.spec.ts | 18 ++-- .../frontend/src/shared/api/apiUtils.ts | 41 +++++++++ .../src/shared/api/backendApiTypes.ts | 33 +++++++ .../frontend/src/shared/api/errorUtils.ts | 35 +++----- .../src/shared/api/notebookService.ts | 75 +++++----------- workspaces/frontend/src/shared/api/types.ts | 7 -- .../src/shared/mock/mockNotebookService.ts | 35 +++++++- .../frontend/src/shared/mock/mockUtils.ts | 7 ++ .../frontend/src/shared/style/MUI-theme.scss | 1 - 14 files changed, 273 insertions(+), 152 deletions(-) create mode 100644 workspaces/frontend/src/app/components/ValidationErrorAlert.tsx create mode 100644 workspaces/frontend/src/shared/mock/mockUtils.ts diff --git a/workspaces/frontend/src/app/NavSidebar.tsx b/workspaces/frontend/src/app/NavSidebar.tsx index f1deb892..0bd4ac81 100644 --- a/workspaces/frontend/src/app/NavSidebar.tsx +++ b/workspaces/frontend/src/app/NavSidebar.tsx @@ -17,7 +17,7 @@ const NavHref: React.FC<{ item: NavDataHref }> = ({ item }) => { const location = useTypedLocation(); // With the redirect in place, we can now use a simple path comparison. - const isActive = location.pathname === item.path; + const isActive = location.pathname === item.path || location.pathname.startsWith(`${item.path}/`); return ( diff --git a/workspaces/frontend/src/app/components/ValidationErrorAlert.tsx b/workspaces/frontend/src/app/components/ValidationErrorAlert.tsx new file mode 100644 index 00000000..e2691b2a --- /dev/null +++ b/workspaces/frontend/src/app/components/ValidationErrorAlert.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { Alert, List, ListItem } from '@patternfly/react-core'; +import { ValidationError } from '~/shared/api/backendApiTypes'; + +interface ValidationErrorAlertProps { + title: string; + errors: ValidationError[]; +} + +export const ValidationErrorAlert: React.FC = ({ title, errors }) => { + if (errors.length === 0) { + return null; + } + + return ( + + + {errors.map((error, index) => ( + {error.message} + ))} + + + ); +}; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx index 2666f54b..feb1ba68 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx @@ -8,12 +8,17 @@ import { PageGroup, PageSection, Stack, + StackItem, } from '@patternfly/react-core'; +import { t_global_spacer_sm as SmallPadding } from '@patternfly/react-tokens'; +import { ValidationErrorAlert } from '~/app/components/ValidationErrorAlert'; import { useTypedNavigate } from '~/app/routerHelper'; import { useCurrentRouteKey } from '~/app/hooks/useCurrentRouteKey'; import useGenericObjectState from '~/app/hooks/useGenericObjectState'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { WorkspaceKindFormData } from '~/app/types'; +import { ErrorEnvelopeException } from '~/shared/api/apiUtils'; +import { ValidationError } from '~/shared/api/backendApiTypes'; import { WorkspaceKindFileUpload } from './fileUpload/WorkspaceKindFileUpload'; import { WorkspaceKindFormProperties } from './properties/WorkspaceKindFormProperties'; import { WorkspaceKindFormImage } from './image/WorkspaceKindFormImage'; @@ -34,6 +39,8 @@ export const WorkspaceKindForm: React.FC = () => { const [isSubmitting, setIsSubmitting] = useState(false); const [validated, setValidated] = useState('default'); const mode = useCurrentRouteKey() === 'workspaceKindCreate' ? 'create' : 'edit'; + const [specErrors, setSpecErrors] = useState([]); + const [data, setData, resetData] = useGenericObjectState({ properties: { displayName: '', @@ -60,14 +67,24 @@ export const WorkspaceKindForm: React.FC = () => { try { if (mode === 'create') { const newWorkspaceKind = await api.createWorkspaceKind({}, yamlValue); + // TODO: alert user about success console.info('New workspace kind created:', JSON.stringify(newWorkspaceKind)); + navigate('workspaceKinds'); } } catch (err) { + if (err instanceof ErrorEnvelopeException) { + const validationErrors = err.envelope.error?.cause?.validation_errors; + if (validationErrors && validationErrors.length > 0) { + setSpecErrors(validationErrors); + setValidated('error'); + return; + } + } + // TODO: alert user about error console.error(`Error ${mode === 'edit' ? 'editing' : 'creating'} workspace kind: ${err}`); } finally { setIsSubmitting(false); } - navigate('workspaceKinds'); }, [navigate, mode, api, yamlValue]); const canSubmit = useMemo( @@ -102,13 +119,25 @@ export const WorkspaceKindForm: React.FC = () => { {mode === 'create' && ( - + + {specErrors.length > 0 && ( + + + + )} + + { + setSpecErrors([]); + }} + /> + + )} {mode === 'edit' && ( <> diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/fileUpload/WorkspaceKindFileUpload.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/fileUpload/WorkspaceKindFileUpload.tsx index eae9c918..bbd78ee2 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/fileUpload/WorkspaceKindFileUpload.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/fileUpload/WorkspaceKindFileUpload.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useRef, useState } from 'react'; +import React, { useCallback, useState } from 'react'; import yaml, { YAMLException } from 'js-yaml'; import { FileUpload, @@ -7,6 +7,7 @@ import { HelperText, HelperTextItem, Content, + DropzoneErrorCode, } from '@patternfly/react-core'; import { isValidWorkspaceKindYaml } from '~/app/pages/WorkspaceKinds/Form/helpers'; import { ValidationStatus } from '~/app/pages/WorkspaceKinds/Form/WorkspaceKindForm'; @@ -17,60 +18,52 @@ interface WorkspaceKindFileUploadProps { resetData: () => void; validated: ValidationStatus; setValidated: (type: ValidationStatus) => void; + onClear: () => void; } +const YAML_MIME_TYPE = 'application/x-yaml'; +const YAML_EXTENSIONS = ['.yml', '.yaml']; + export const WorkspaceKindFileUpload: React.FC = ({ resetData, value, setValue, validated, setValidated, + onClear, }) => { - const isYamlFileRef = useRef(false); const [filename, setFilename] = useState(''); const [isLoading, setIsLoading] = useState(false); - const [fileUploadHelperText, setFileUploadHelperText] = useState(''); + const [fileUploadHelperText, setFileUploadHelperText] = useState(); const handleFileInputChange = useCallback( (_: unknown, file: File) => { - const fileName = file.name; + onClear(); setFilename(file.name); - // if extension is not yaml or yml, raise a flag - const ext = fileName.split('.').pop(); - const isYaml = ext === 'yml' || ext === 'yaml'; - isYamlFileRef.current = isYaml; - if (!isYaml) { - setFileUploadHelperText('Invalid file. Only YAML files are allowed.'); - resetData(); - setValidated('error'); - } else { - setFileUploadHelperText(''); - setValidated('success'); - } + setFileUploadHelperText(undefined); + setValidated('success'); }, - [resetData, setValidated], + [setValidated, onClear], ); // TODO: Use zod or another TS type coercion/schema for file upload const handleDataChange = useCallback( (_: DropEvent, v: string) => { setValue(v); - if (isYamlFileRef.current) { - try { - const parsed = yaml.load(v); - if (!isValidWorkspaceKindYaml(parsed)) { - setFileUploadHelperText('YAML is invalid: must follow WorkspaceKind format.'); - setValidated('error'); - resetData(); - } else { - setValidated('success'); - setFileUploadHelperText(''); - } - } catch (e) { - console.error('Error parsing YAML:', e); - setFileUploadHelperText(`Error parsing YAML: ${e as YAMLException['reason']}`); + try { + const parsed = yaml.load(v); + if (!isValidWorkspaceKindYaml(parsed)) { + setFileUploadHelperText('YAML is invalid: must follow WorkspaceKind format.'); setValidated('error'); + resetData(); + } else { + setValidated('success'); + setFileUploadHelperText(''); } + } catch (e) { + console.error('Error parsing YAML:', e); + setFileUploadHelperText(`Error parsing YAML: ${e as YAMLException['reason']}`); + setValidated('error'); } }, [setValue, setValidated, resetData], @@ -82,7 +75,8 @@ export const WorkspaceKindFileUpload: React.FC = ( setFileUploadHelperText(''); setValidated('default'); resetData(); - }, [resetData, setValidated, setValue]); + onClear(); + }, [resetData, setValidated, setValue, onClear]); const handleFileReadStarted = useCallback(() => { setIsLoading(true); @@ -110,14 +104,27 @@ export const WorkspaceKindFileUpload: React.FC = ( validated={validated} allowEditingUploadedText={false} browseButtonText="Choose File" + dropzoneProps={{ + accept: { [YAML_MIME_TYPE]: YAML_EXTENSIONS }, + onDropRejected: (rejections) => { + const error = rejections[0]?.errors?.[0] ?? {}; + setFileUploadHelperText( + error.code === DropzoneErrorCode.FileInvalidType + ? 'Invalid file. Only YAML files are allowed.' + : error.message, + ); + }, + }} > - - - - {fileUploadHelperText} - - - + {fileUploadHelperText && ( + + + + {fileUploadHelperText} + + + + )} ); diff --git a/workspaces/frontend/src/shared/api/__tests__/errorUtils.spec.ts b/workspaces/frontend/src/shared/api/__tests__/errorUtils.spec.ts index 16870890..099905e5 100644 --- a/workspaces/frontend/src/shared/api/__tests__/errorUtils.spec.ts +++ b/workspaces/frontend/src/shared/api/__tests__/errorUtils.spec.ts @@ -1,8 +1,9 @@ -import { NotReadyError } from '~/shared/utilities/useFetchState'; -import { APIError } from '~/shared/api/types'; -import { handleRestFailures } from '~/shared/api/errorUtils'; import { mockNamespaces } from '~/__mocks__/mockNamespaces'; import { mockBFFResponse } from '~/__mocks__/utils'; +import { ErrorEnvelopeException } from '~/shared/api/apiUtils'; +import { ErrorEnvelope } from '~/shared/api/backendApiTypes'; +import { handleRestFailures } from '~/shared/api/errorUtils'; +import { NotReadyError } from '~/shared/utilities/useFetchState'; describe('handleRestFailures', () => { it('should successfully return namespaces', async () => { @@ -11,14 +12,14 @@ describe('handleRestFailures', () => { }); it('should handle and throw notebook errors', async () => { - const statusMock: APIError = { + const errorEnvelope: ErrorEnvelope = { error: { - code: '', - message: 'error', + code: '', + message: '', }, }; - - await expect(handleRestFailures(Promise.resolve(statusMock))).rejects.toThrow('error'); + const expectedError = new ErrorEnvelopeException(errorEnvelope); + await expect(handleRestFailures(Promise.reject(errorEnvelope))).rejects.toThrow(expectedError); }); it('should handle common state errors ', async () => { diff --git a/workspaces/frontend/src/shared/api/__tests__/notebookService.spec.ts b/workspaces/frontend/src/shared/api/__tests__/notebookService.spec.ts index d84aa9a9..27a650b5 100644 --- a/workspaces/frontend/src/shared/api/__tests__/notebookService.spec.ts +++ b/workspaces/frontend/src/shared/api/__tests__/notebookService.spec.ts @@ -1,10 +1,9 @@ import { BFF_API_VERSION } from '~/app/const'; -import { restGET } from '~/shared/api/apiUtils'; -import { handleRestFailures } from '~/shared/api/errorUtils'; +import { restGET, wrapRequest } from '~/shared/api/apiUtils'; import { listNamespaces } from '~/shared/api/notebookService'; -const mockRestPromise = Promise.resolve({ data: {} }); -const mockRestResponse = {}; +const mockRestResponse = { data: {} }; +const mockRestPromise = Promise.resolve(mockRestResponse); jest.mock('~/shared/api/apiUtils', () => ({ restCREATE: jest.fn(() => mockRestPromise), @@ -12,13 +11,10 @@ jest.mock('~/shared/api/apiUtils', () => ({ restPATCH: jest.fn(() => mockRestPromise), isNotebookResponse: jest.fn(() => true), extractNotebookResponse: jest.fn(() => mockRestResponse), + wrapRequest: jest.fn(() => mockRestPromise), })); -jest.mock('~/shared/api/errorUtils', () => ({ - handleRestFailures: jest.fn(() => mockRestPromise), -})); - -const handleRestFailuresMock = jest.mocked(handleRestFailures); +const wrapRequestMock = jest.mocked(wrapRequest); const restGETMock = jest.mocked(restGET); const APIOptionsMock = {}; @@ -33,7 +29,7 @@ describe('getNamespaces', () => { {}, APIOptionsMock, ); - expect(handleRestFailuresMock).toHaveBeenCalledTimes(1); - expect(handleRestFailuresMock).toHaveBeenCalledWith(mockRestPromise); + expect(wrapRequestMock).toHaveBeenCalledTimes(1); + expect(wrapRequestMock).toHaveBeenCalledWith(mockRestPromise); }); }); diff --git a/workspaces/frontend/src/shared/api/apiUtils.ts b/workspaces/frontend/src/shared/api/apiUtils.ts index e1c5c6da..6993cc45 100644 --- a/workspaces/frontend/src/shared/api/apiUtils.ts +++ b/workspaces/frontend/src/shared/api/apiUtils.ts @@ -1,3 +1,5 @@ +import { ErrorEnvelope } from '~/shared/api/backendApiTypes'; +import { handleRestFailures } from '~/shared/api/errorUtils'; import { APIOptions, ResponseBody } from '~/shared/api/types'; import { EitherOrNone } from '~/shared/typeHelpers'; import { AUTH_HEADER, DEV_MODE } from '~/shared/utilities/const'; @@ -222,9 +224,48 @@ export const isNotebookResponse = (response: unknown): response is ResponseBo return false; }; +export const isErrorEnvelope = (e: unknown): e is ErrorEnvelope => + typeof e === 'object' && + e !== null && + 'error' in e && + typeof (e as Record).error === 'object' && + (e as { error: unknown }).error !== null && + typeof (e as { error: { message: unknown } }).error.message === 'string'; + export function extractNotebookResponse(response: unknown): T { if (isNotebookResponse(response)) { return response.data; } throw new Error('Invalid response format'); } + +export function extractErrorEnvelope(error: unknown): ErrorEnvelope { + if (isErrorEnvelope(error)) { + return error; + } + + const message = + error instanceof Error ? error.message : typeof error === 'string' ? error : 'Unexpected error'; + + return { + error: { + message, + code: 'UNKNOWN_ERROR', + }, + }; +} + +export async function wrapRequest(promise: Promise, extractData = true): Promise { + try { + const res = await handleRestFailures(promise); + return extractData ? extractNotebookResponse(res) : res; + } catch (error) { + throw new ErrorEnvelopeException(extractErrorEnvelope(error)); + } +} + +export class ErrorEnvelopeException extends Error { + constructor(public envelope: ErrorEnvelope) { + super(envelope.error?.message ?? 'Unknown error'); + } +} diff --git a/workspaces/frontend/src/shared/api/backendApiTypes.ts b/workspaces/frontend/src/shared/api/backendApiTypes.ts index ca42d666..e84ca6b5 100644 --- a/workspaces/frontend/src/shared/api/backendApiTypes.ts +++ b/workspaces/frontend/src/shared/api/backendApiTypes.ts @@ -282,3 +282,36 @@ export interface WorkspacePauseState { workspaceName: string; paused: boolean; } + +export enum FieldErrorType { + FieldValueRequired = 'FieldValueRequired', + FieldValueInvalid = 'FieldValueInvalid', + FieldValueNotSupported = 'FieldValueNotSupported', + FieldValueDuplicate = 'FieldValueDuplicate', + FieldValueTooLong = 'FieldValueTooLong', + FieldValueForbidden = 'FieldValueForbidden', + FieldValueNotFound = 'FieldValueNotFound', + FieldValueConflict = 'FieldValueConflict', + FieldValueTooShort = 'FieldValueTooShort', + FieldValueUnknown = 'FieldValueUnknown', +} + +export interface ValidationError { + type: FieldErrorType; + field: string; + message: string; +} + +export interface ErrorCause { + validation_errors?: ValidationError[]; // TODO: backend is not using camelCase for this field +} + +export type HTTPError = { + code: string; + message: string; + cause?: ErrorCause; +}; + +export type ErrorEnvelope = { + error: HTTPError | null; +}; diff --git a/workspaces/frontend/src/shared/api/errorUtils.ts b/workspaces/frontend/src/shared/api/errorUtils.ts index 47046554..9205a8f0 100644 --- a/workspaces/frontend/src/shared/api/errorUtils.ts +++ b/workspaces/frontend/src/shared/api/errorUtils.ts @@ -1,25 +1,16 @@ -import { APIError } from '~/shared/api/types'; +import { ErrorEnvelopeException, isErrorEnvelope } from '~/shared/api//apiUtils'; import { isCommonStateError } from '~/shared/utilities/useFetchState'; -const isError = (e: unknown): e is APIError => typeof e === 'object' && e !== null && 'error' in e; - export const handleRestFailures = (promise: Promise): Promise => - promise - .then((result) => { - if (isError(result)) { - throw result; - } - return result; - }) - .catch((e) => { - if (isError(e)) { - throw new Error(e.error.message); - } - if (isCommonStateError(e)) { - // Common state errors are handled by useFetchState at storage level, let them deal with it - throw e; - } - // eslint-disable-next-line no-console - console.error('Unknown API error', e); - throw new Error('Error communicating with server'); - }); + promise.catch((e) => { + if (isErrorEnvelope(e)) { + throw new ErrorEnvelopeException(e); + } + if (isCommonStateError(e)) { + // Common state errors are handled by useFetchState at storage level, let them deal with it + throw e; + } + // eslint-disable-next-line no-console + console.error('Unknown API error', e); + throw new Error('Error communicating with server'); + }); diff --git a/workspaces/frontend/src/shared/api/notebookService.ts b/workspaces/frontend/src/shared/api/notebookService.ts index 81fdbfcc..5cdd91bb 100644 --- a/workspaces/frontend/src/shared/api/notebookService.ts +++ b/workspaces/frontend/src/shared/api/notebookService.ts @@ -1,19 +1,12 @@ import { - extractNotebookResponse, restCREATE, restDELETE, restGET, restPATCH, restUPDATE, restYAML, + wrapRequest, } from '~/shared/api/apiUtils'; -import { handleRestFailures } from '~/shared/api/errorUtils'; -import { - Namespace, - Workspace, - WorkspaceKind, - WorkspacePauseState, -} from '~/shared/api/backendApiTypes'; import { CreateWorkspaceAPI, CreateWorkspaceKindAPI, @@ -32,86 +25,60 @@ import { StartWorkspaceAPI, UpdateWorkspaceAPI, UpdateWorkspaceKindAPI, -} from './callTypes'; +} from '~/shared/api/callTypes'; export const getHealthCheck: GetHealthCheckAPI = (hostPath) => (opts) => - handleRestFailures(restGET(hostPath, `/healthcheck`, {}, opts)); + wrapRequest(restGET(hostPath, `/healthcheck`, {}, opts), false); export const listNamespaces: ListNamespacesAPI = (hostPath) => (opts) => - handleRestFailures(restGET(hostPath, `/namespaces`, {}, opts)).then((response) => - extractNotebookResponse(response), - ); + wrapRequest(restGET(hostPath, `/namespaces`, {}, opts)); export const listAllWorkspaces: ListAllWorkspacesAPI = (hostPath) => (opts) => - handleRestFailures(restGET(hostPath, `/workspaces`, {}, opts)).then((response) => - extractNotebookResponse(response), - ); + wrapRequest(restGET(hostPath, `/workspaces`, {}, opts)); export const listWorkspaces: ListWorkspacesAPI = (hostPath) => (opts, namespace) => - handleRestFailures(restGET(hostPath, `/workspaces/${namespace}`, {}, opts)).then((response) => - extractNotebookResponse(response), - ); + wrapRequest(restGET(hostPath, `/workspaces/${namespace}`, {}, opts)); export const getWorkspace: GetWorkspaceAPI = (hostPath) => (opts, namespace, workspace) => - handleRestFailures(restGET(hostPath, `/workspaces/${namespace}/${workspace}`, {}, opts)).then( - (response) => extractNotebookResponse(response), - ); + wrapRequest(restGET(hostPath, `/workspaces/${namespace}/${workspace}`, {}, opts)); export const createWorkspace: CreateWorkspaceAPI = (hostPath) => (opts, namespace, data) => - handleRestFailures(restCREATE(hostPath, `/workspaces/${namespace}`, data, {}, opts)).then( - (response) => extractNotebookResponse(response), - ); + wrapRequest(restCREATE(hostPath, `/workspaces/${namespace}`, data, {}, opts)); export const updateWorkspace: UpdateWorkspaceAPI = (hostPath) => (opts, namespace, workspace, data) => - handleRestFailures( - restUPDATE(hostPath, `/workspaces/${namespace}/${workspace}`, data, {}, opts), - ).then((response) => extractNotebookResponse(response)); + wrapRequest(restUPDATE(hostPath, `/workspaces/${namespace}/${workspace}`, data, {}, opts)); export const patchWorkspace: PatchWorkspaceAPI = (hostPath) => (opts, namespace, workspace, data) => - handleRestFailures(restPATCH(hostPath, `/workspaces/${namespace}/${workspace}`, data, opts)).then( - (response) => extractNotebookResponse(response), - ); + wrapRequest(restPATCH(hostPath, `/workspaces/${namespace}/${workspace}`, data, opts)); export const deleteWorkspace: DeleteWorkspaceAPI = (hostPath) => (opts, namespace, workspace) => - handleRestFailures(restDELETE(hostPath, `/workspaces/${namespace}/${workspace}`, {}, {}, opts)); + wrapRequest(restDELETE(hostPath, `/workspaces/${namespace}/${workspace}`, {}, {}, opts), false); export const pauseWorkspace: PauseWorkspaceAPI = (hostPath) => (opts, namespace, workspace) => - handleRestFailures( + wrapRequest( restCREATE(hostPath, `/workspaces/${namespace}/${workspace}/actions/pause`, {}, opts), - ).then((response) => extractNotebookResponse(response)); + ); export const startWorkspace: StartWorkspaceAPI = (hostPath) => (opts, namespace, workspace) => - handleRestFailures( + wrapRequest( restCREATE(hostPath, `/workspaces/${namespace}/${workspace}/actions/start`, {}, opts), - ).then((response) => extractNotebookResponse(response)); + ); export const listWorkspaceKinds: ListWorkspaceKindsAPI = (hostPath) => (opts) => - handleRestFailures(restGET(hostPath, `/workspacekinds`, {}, opts)).then((response) => - extractNotebookResponse(response), - ); + wrapRequest(restGET(hostPath, `/workspacekinds`, {}, opts)); export const getWorkspaceKind: GetWorkspaceKindAPI = (hostPath) => (opts, kind) => - handleRestFailures(restGET(hostPath, `/workspacekinds/${kind}`, {}, opts)).then((response) => - extractNotebookResponse(response), - ); + wrapRequest(restGET(hostPath, `/workspacekinds/${kind}`, {}, opts)); export const createWorkspaceKind: CreateWorkspaceKindAPI = (hostPath) => (opts, data) => - handleRestFailures(restYAML(hostPath, `/workspacekinds`, data, {}, opts)).then((response) => - extractNotebookResponse(response), - ); + wrapRequest(restYAML(hostPath, `/workspacekinds`, data, {}, opts)); export const updateWorkspaceKind: UpdateWorkspaceKindAPI = (hostPath) => (opts, kind, data) => - handleRestFailures(restUPDATE(hostPath, `/workspacekinds/${kind}`, data, {}, opts)).then( - (response) => extractNotebookResponse(response), - ); + wrapRequest(restUPDATE(hostPath, `/workspacekinds/${kind}`, data, {}, opts)); export const patchWorkspaceKind: PatchWorkspaceKindAPI = (hostPath) => (opts, kind, data) => - handleRestFailures(restPATCH(hostPath, `/workspacekinds/${kind}`, data, opts)).then((response) => - extractNotebookResponse(response), - ); + wrapRequest(restPATCH(hostPath, `/workspacekinds/${kind}`, data, opts)); export const deleteWorkspaceKind: DeleteWorkspaceKindAPI = (hostPath) => (opts, kind) => - handleRestFailures(restDELETE(hostPath, `/workspacekinds/${kind}`, {}, {}, opts)).then( - (response) => extractNotebookResponse(response), - ); + wrapRequest(restDELETE(hostPath, `/workspacekinds/${kind}`, {}, {}, opts), false); diff --git a/workspaces/frontend/src/shared/api/types.ts b/workspaces/frontend/src/shared/api/types.ts index 6f1ac507..71389f36 100644 --- a/workspaces/frontend/src/shared/api/types.ts +++ b/workspaces/frontend/src/shared/api/types.ts @@ -5,13 +5,6 @@ export type APIOptions = { headers?: Record; }; -export type APIError = { - error: { - code: string; - message: string; - }; -}; - export type APIState = { /** If API will successfully call */ apiAvailable: boolean; diff --git a/workspaces/frontend/src/shared/mock/mockNotebookService.ts b/workspaces/frontend/src/shared/mock/mockNotebookService.ts index 91397c9e..0bb63e16 100644 --- a/workspaces/frontend/src/shared/mock/mockNotebookService.ts +++ b/workspaces/frontend/src/shared/mock/mockNotebookService.ts @@ -1,3 +1,5 @@ +import { ErrorEnvelopeException } from '~/shared/api/apiUtils'; +import { FieldErrorType } from '~/shared/api/backendApiTypes'; import { CreateWorkspaceAPI, CreateWorkspaceKindAPI, @@ -27,6 +29,7 @@ import { mockWorkspaceKind1, mockWorkspaceKinds, } from '~/shared/mock/mockNotebookServiceData'; +import { isInvalidYaml } from '~/shared/mock/mockUtils'; const delay = (ms: number) => new Promise((resolve) => { @@ -70,7 +73,37 @@ export const mockListWorkspaceKinds: ListWorkspaceKindsAPI = () => async () => m export const mockGetWorkspaceKind: GetWorkspaceKindAPI = () => async (_opts, kind) => mockWorkspaceKinds.find((w) => w.name === kind)!; -export const mockCreateWorkspaceKind: CreateWorkspaceKindAPI = () => async () => mockWorkspaceKind1; +export const mockCreateWorkspaceKind: CreateWorkspaceKindAPI = () => async (_opts, data) => { + if (isInvalidYaml(data)) { + throw new ErrorEnvelopeException({ + error: { + code: 'invalid_yaml', + message: 'Invalid YAML provided', + cause: { + // eslint-disable-next-line camelcase + validation_errors: [ + { + type: FieldErrorType.FieldValueRequired, + field: 'spec.spawner.displayName', + message: "Missing required 'spec.spawner.displayName' property", + }, + { + type: FieldErrorType.FieldValueUnknown, + field: 'spec.spawner.xyz', + message: "Unknown property 'spec.spawner.xyz'", + }, + { + type: FieldErrorType.FieldValueNotSupported, + field: 'spec.spawner.hidden', + message: "Invalid data type for 'spec.spawner.hidden', expected 'boolean'", + }, + ], + }, + }, + }); + } + return mockWorkspaceKind1; +}; export const mockUpdateWorkspaceKind: UpdateWorkspaceKindAPI = () => async () => mockWorkspaceKind1; diff --git a/workspaces/frontend/src/shared/mock/mockUtils.ts b/workspaces/frontend/src/shared/mock/mockUtils.ts new file mode 100644 index 00000000..c4af8e1a --- /dev/null +++ b/workspaces/frontend/src/shared/mock/mockUtils.ts @@ -0,0 +1,7 @@ +import yaml from 'js-yaml'; + +// For testing purposes, a YAML string is considered invalid if it contains a specific pattern in the metadata name. +export function isInvalidYaml(yamlString: string): boolean { + const parsed = yaml.load(yamlString) as { metadata?: { name?: string } }; + return parsed.metadata?.name?.includes('-invalid') ?? false; +} diff --git a/workspaces/frontend/src/shared/style/MUI-theme.scss b/workspaces/frontend/src/shared/style/MUI-theme.scss index 6084f158..ebc7263b 100644 --- a/workspaces/frontend/src/shared/style/MUI-theme.scss +++ b/workspaces/frontend/src/shared/style/MUI-theme.scss @@ -124,7 +124,6 @@ .mui-theme .pf-v6-c-alert { --pf-v6-c-alert--m-warning__title--Color: var(--pf-t--global--text--color--status--warning--default); --pf-v6-c-alert__icon--MarginInlineEnd: var(--mui-alert__icon--MarginInlineEnd); - --pf-v6-c-alert__title--FontWeight: var(--mui-alert-warning-font-weight); --pf-v6-c-alert__icon--MarginBlockStart: var(--mui-alert__icon--MarginBlockStart); --pf-v6-c-alert__icon--FontSize: var(--mui-alert__icon--FontSize); --pf-v6-c-alert--BoxShadow: var(--mui-alert--BoxShadow); From 989fe534d4892c37d3a0e0f3842268963c70ed5a Mon Sep 17 00:00:00 2001 From: Paulo Rego <832830+paulovmr@users.noreply.github.com> Date: Mon, 7 Jul 2025 08:15:22 -0300 Subject: [PATCH 27/68] chore(ws): added cspell to enforce spelling check (#469) Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> --- workspaces/frontend/.eslintrc.js | 5 + .../frontend/config/cspell-ignore-words.txt | 8 + workspaces/frontend/config/cspell.config.cjs | 51 + workspaces/frontend/config/cspell.json | 3 + workspaces/frontend/package-lock.json | 1055 ++++++++++++++++- workspaces/frontend/package.json | 5 +- .../WorkspaceKinds/Form/EditableLabels.tsx | 2 +- .../podConfig/WorkspaceKindFormResource.tsx | 2 +- .../src/app/pages/notFound/NotFound.tsx | 2 +- .../src/shared/components/DeleteModal.tsx | 2 +- .../frontend/src/shared/mock/mockBuilder.ts | 2 +- .../frontend/src/shared/style/MUI-theme.scss | 2 +- .../src/shared/utilities/useFetchState.ts | 2 +- 13 files changed, 1082 insertions(+), 59 deletions(-) create mode 100644 workspaces/frontend/config/cspell-ignore-words.txt create mode 100644 workspaces/frontend/config/cspell.config.cjs create mode 100644 workspaces/frontend/config/cspell.json diff --git a/workspaces/frontend/.eslintrc.js b/workspaces/frontend/.eslintrc.js index 09f4cb08..29f4f8bb 100644 --- a/workspaces/frontend/.eslintrc.js +++ b/workspaces/frontend/.eslintrc.js @@ -23,6 +23,7 @@ module.exports = { 'no-relative-import-paths', 'prettier', 'local-rules', + '@cspell', ], extends: [ 'eslint:recommended', @@ -233,6 +234,10 @@ module.exports = { 'func-names': 'warn', 'local-rules/no-react-hook-namespace': 'error', 'local-rules/no-raw-react-router-hook': 'error', + '@cspell/spellchecker': [ + 'error', + { configFile: 'config/cspell.json', customWordListFile: 'config/cspell-ignore-words.txt' }, + ], }, overrides: [ { diff --git a/workspaces/frontend/config/cspell-ignore-words.txt b/workspaces/frontend/config/cspell-ignore-words.txt new file mode 100644 index 00000000..6b02547d --- /dev/null +++ b/workspaces/frontend/config/cspell-ignore-words.txt @@ -0,0 +1,8 @@ +scipy +kubeflow +mochawesome +jovyan +millicores +workspacekind +workspacekinds +healthcheck \ No newline at end of file diff --git a/workspaces/frontend/config/cspell.config.cjs b/workspaces/frontend/config/cspell.config.cjs new file mode 100644 index 00000000..9efe8460 --- /dev/null +++ b/workspaces/frontend/config/cspell.config.cjs @@ -0,0 +1,51 @@ +/* + * Workaround suggested in https://github.com/streetsidesoftware/cspell/issues/3215 + * while the fix for the library is in progress + */ + +const fs = require('fs'); +const path = require('path'); + +/** + * Search for `package.json` + * @param {string} from - search `from` directory. + * @returns {string} - path to package.json + */ +function findNearestPackageJson(from) { + from = path.resolve(from); + const parent = path.dirname(from); + if (!from || parent === from) { + return; + } + + const pkg = path.join(from, 'package.json'); + if (fs.existsSync(pkg)) { + return pkg; + } + return findNearestPackageJson(parent); +} + +/** + * Load the nearest package.json + * @param {string} cwd + * @returns + */ +function loadPackage(cwd) { + const pkgFile = findNearestPackageJson(cwd); + if (!pkgFile) return; + return JSON.parse(fs.readFileSync(pkgFile, 'utf-8')); +} + +function determinePackageNamesAndMethods(cwd = process.cwd()) { + const pkg = loadPackage(cwd) || {}; + const packageNames = Object.keys(pkg.dependencies || {}).concat( + Object.keys(pkg.devDependencies || {}), + ); + const setOfWords = new Set(packageNames.flatMap((name) => name.replace(/[@]/g, '').split('/'))); + const words = [...setOfWords]; + return { words }; +} + +module.exports = { + words: determinePackageNamesAndMethods().words, +}; diff --git a/workspaces/frontend/config/cspell.json b/workspaces/frontend/config/cspell.json new file mode 100644 index 00000000..3a9e1a3d --- /dev/null +++ b/workspaces/frontend/config/cspell.json @@ -0,0 +1,3 @@ +{ + "import": ["./cspell.config.cjs"] +} diff --git a/workspaces/frontend/package-lock.json b/workspaces/frontend/package-lock.json index feb1e0eb..a283be97 100644 --- a/workspaces/frontend/package-lock.json +++ b/workspaces/frontend/package-lock.json @@ -27,6 +27,7 @@ "sirv-cli": "^2.0.2" }, "devDependencies": { + "@cspell/eslint-plugin": "^9.1.2", "@cypress/code-coverage": "^3.13.5", "@mui/icons-material": "^6.3.1", "@mui/material": "^6.3.1", @@ -114,7 +115,7 @@ "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "optional": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -1959,6 +1960,629 @@ "node": ">=0.1.90" } }, + "node_modules/@cspell/cspell-bundled-dicts": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-9.1.2.tgz", + "integrity": "sha512-mdhxj7j1zqXYKO/KPx2MgN3RPAvqoWvncxz2dOMFBcuUteZPt58NenUoi0VZXEhV/FM2V80NvhHZZafaIcxVjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/dict-ada": "^4.1.0", + "@cspell/dict-al": "^1.1.0", + "@cspell/dict-aws": "^4.0.10", + "@cspell/dict-bash": "^4.2.0", + "@cspell/dict-companies": "^3.2.1", + "@cspell/dict-cpp": "^6.0.8", + "@cspell/dict-cryptocurrencies": "^5.0.4", + "@cspell/dict-csharp": "^4.0.6", + "@cspell/dict-css": "^4.0.17", + "@cspell/dict-dart": "^2.3.0", + "@cspell/dict-data-science": "^2.0.8", + "@cspell/dict-django": "^4.1.4", + "@cspell/dict-docker": "^1.1.14", + "@cspell/dict-dotnet": "^5.0.9", + "@cspell/dict-elixir": "^4.0.7", + "@cspell/dict-en_us": "^4.4.11", + "@cspell/dict-en-common-misspellings": "^2.1.1", + "@cspell/dict-en-gb-mit": "^3.1.1", + "@cspell/dict-filetypes": "^3.0.12", + "@cspell/dict-flutter": "^1.1.0", + "@cspell/dict-fonts": "^4.0.4", + "@cspell/dict-fsharp": "^1.1.0", + "@cspell/dict-fullstack": "^3.2.6", + "@cspell/dict-gaming-terms": "^1.1.1", + "@cspell/dict-git": "^3.0.6", + "@cspell/dict-golang": "^6.0.22", + "@cspell/dict-google": "^1.0.8", + "@cspell/dict-haskell": "^4.0.5", + "@cspell/dict-html": "^4.0.11", + "@cspell/dict-html-symbol-entities": "^4.0.3", + "@cspell/dict-java": "^5.0.11", + "@cspell/dict-julia": "^1.1.0", + "@cspell/dict-k8s": "^1.0.11", + "@cspell/dict-kotlin": "^1.1.0", + "@cspell/dict-latex": "^4.0.3", + "@cspell/dict-lorem-ipsum": "^4.0.4", + "@cspell/dict-lua": "^4.0.7", + "@cspell/dict-makefile": "^1.0.4", + "@cspell/dict-markdown": "^2.0.11", + "@cspell/dict-monkeyc": "^1.0.10", + "@cspell/dict-node": "^5.0.7", + "@cspell/dict-npm": "^5.2.7", + "@cspell/dict-php": "^4.0.14", + "@cspell/dict-powershell": "^5.0.14", + "@cspell/dict-public-licenses": "^2.0.13", + "@cspell/dict-python": "^4.2.18", + "@cspell/dict-r": "^2.1.0", + "@cspell/dict-ruby": "^5.0.8", + "@cspell/dict-rust": "^4.0.11", + "@cspell/dict-scala": "^5.0.7", + "@cspell/dict-shell": "^1.1.0", + "@cspell/dict-software-terms": "^5.1.1", + "@cspell/dict-sql": "^2.2.0", + "@cspell/dict-svelte": "^1.0.6", + "@cspell/dict-swift": "^2.0.5", + "@cspell/dict-terraform": "^1.1.1", + "@cspell/dict-typescript": "^3.2.2", + "@cspell/dict-vue": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@cspell/cspell-pipe": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-9.1.2.tgz", + "integrity": "sha512-/pIhsf4SI4Q/kvehq9GsGKLgbQsRhiDgthQIgO6YOrEa761wOI2hVdRyc0Tgc1iAGiJEedDaFsAhabVRJBeo2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@cspell/cspell-resolver": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-9.1.2.tgz", + "integrity": "sha512-dNDx7yMl2h1Ousk08lizTou+BUvce4RPSnPXrQPB7B7CscgZloSyuP3Yyj1Zt81pHNpggrym4Ezx6tMdyPjESw==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-directory": "^4.0.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@cspell/cspell-service-bus": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-9.1.2.tgz", + "integrity": "sha512-YOsUctzCMzEJbKdzNyvPkyMen/i7sGO3Xgcczn848GJPlRsJc50QwsoU67SY7zEARz6y2WS0tv5F5RMrRO4idw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@cspell/cspell-types": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-9.1.2.tgz", + "integrity": "sha512-bSDDjoQi4pbh1BULEA596XCo1PMShTpTb4J2lj8jVYqYgXYQNjSmQFA1fj4NHesC84JpK1um4ybzXBcqtniC7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@cspell/dict-ada": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-ada/-/dict-ada-4.1.0.tgz", + "integrity": "sha512-7SvmhmX170gyPd+uHXrfmqJBY5qLcCX8kTGURPVeGxmt8XNXT75uu9rnZO+jwrfuU2EimNoArdVy5GZRGljGNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-al": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-al/-/dict-al-1.1.0.tgz", + "integrity": "sha512-PtNI1KLmYkELYltbzuoztBxfi11jcE9HXBHCpID2lou/J4VMYKJPNqe4ZjVzSI9NYbMnMnyG3gkbhIdx66VSXg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-aws": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-4.0.10.tgz", + "integrity": "sha512-0qW4sI0GX8haELdhfakQNuw7a2pnWXz3VYQA2MpydH2xT2e6EN9DWFpKAi8DfcChm8MgDAogKkoHtIo075iYng==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-bash": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-bash/-/dict-bash-4.2.0.tgz", + "integrity": "sha512-HOyOS+4AbCArZHs/wMxX/apRkjxg6NDWdt0jF9i9XkvJQUltMwEhyA2TWYjQ0kssBsnof+9amax2lhiZnh3kCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/dict-shell": "1.1.0" + } + }, + "node_modules/@cspell/dict-companies": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-3.2.1.tgz", + "integrity": "sha512-ryaeJ1KhTTKL4mtinMtKn8wxk6/tqD4vX5tFP+Hg89SiIXmbMk5vZZwVf+eyGUWJOyw5A1CVj9EIWecgoi+jYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-cpp": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-6.0.8.tgz", + "integrity": "sha512-BzurRZilWqaJt32Gif6/yCCPi+FtrchjmnehVEIFzbWyeBd/VOUw77IwrEzehZsu5cRU91yPWuWp5fUsKfDAXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-cryptocurrencies": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-cryptocurrencies/-/dict-cryptocurrencies-5.0.4.tgz", + "integrity": "sha512-6iFu7Abu+4Mgqq08YhTKHfH59mpMpGTwdzDB2Y8bbgiwnGFCeoiSkVkgLn1Kel2++hYcZ8vsAW/MJS9oXxuMag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-csharp": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-4.0.6.tgz", + "integrity": "sha512-w/+YsqOknjQXmIlWDRmkW+BHBPJZ/XDrfJhZRQnp0wzpPOGml7W0q1iae65P2AFRtTdPKYmvSz7AL5ZRkCnSIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-css": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.17.tgz", + "integrity": "sha512-2EisRLHk6X/PdicybwlajLGKF5aJf4xnX2uuG5lexuYKt05xV/J/OiBADmi8q9obhxf1nesrMQbqAt+6CsHo/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-dart": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-dart/-/dict-dart-2.3.0.tgz", + "integrity": "sha512-1aY90lAicek8vYczGPDKr70pQSTQHwMFLbmWKTAI6iavmb1fisJBS1oTmMOKE4ximDf86MvVN6Ucwx3u/8HqLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-data-science": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-data-science/-/dict-data-science-2.0.8.tgz", + "integrity": "sha512-uyAtT+32PfM29wRBeAkUSbkytqI8bNszNfAz2sGPtZBRmsZTYugKMEO9eDjAIE/pnT9CmbjNuoiXhk+Ss4fCOg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-django": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-4.1.4.tgz", + "integrity": "sha512-fX38eUoPvytZ/2GA+g4bbdUtCMGNFSLbdJJPKX2vbewIQGfgSFJKY56vvcHJKAvw7FopjvgyS/98Ta9WN1gckg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-docker": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@cspell/dict-docker/-/dict-docker-1.1.14.tgz", + "integrity": "sha512-p6Qz5mokvcosTpDlgSUREdSbZ10mBL3ndgCdEKMqjCSZJFdfxRdNdjrGER3lQ6LMq5jGr1r7nGXA0gvUJK80nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-dotnet": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-5.0.9.tgz", + "integrity": "sha512-JGD6RJW5sHtO5lfiJl11a5DpPN6eKSz5M1YBa1I76j4dDOIqgZB6rQexlDlK1DH9B06X4GdDQwdBfnpAB0r2uQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-elixir": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-elixir/-/dict-elixir-4.0.7.tgz", + "integrity": "sha512-MAUqlMw73mgtSdxvbAvyRlvc3bYnrDqXQrx5K9SwW8F7fRYf9V4vWYFULh+UWwwkqkhX9w03ZqFYRTdkFku6uA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-en_us": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.4.13.tgz", + "integrity": "sha512-6TEHCJKmRqq7fQI7090p+ju12vhuGcNkc6YfxHrcjO816m53VPVaS6IfG6+6OqelQiOMjr0ZD8IHcDIkwThSFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-en-common-misspellings": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.1.2.tgz", + "integrity": "sha512-r74AObInM1XOUxd3lASnNZNDOIA9Bka7mBDTkvkOeCGoLQhn+Cr7h1889u4K07KHbecKMHP6zw5zQhkdocNzCw==", + "dev": true, + "license": "CC BY-SA 4.0" + }, + "node_modules/@cspell/dict-en-gb-mit": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb-mit/-/dict-en-gb-mit-3.1.3.tgz", + "integrity": "sha512-4aY8ySQxSNSRILtf9lJIfSR+su86u8VL6z41gOIhvLIvYnHMFiohV7ebM91GbtdZXBazL7zmGFcpm2EnBzewug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-filetypes": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.12.tgz", + "integrity": "sha512-+ds5wgNdlUxuJvhg8A1TjuSpalDFGCh7SkANCWvIplg6QZPXL4j83lqxP7PgjHpx7PsBUS7vw0aiHPjZy9BItw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-flutter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-flutter/-/dict-flutter-1.1.0.tgz", + "integrity": "sha512-3zDeS7zc2p8tr9YH9tfbOEYfopKY/srNsAa+kE3rfBTtQERAZeOhe5yxrnTPoufctXLyuUtcGMUTpxr3dO0iaA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-fonts": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-4.0.4.tgz", + "integrity": "sha512-cHFho4hjojBcHl6qxidl9CvUb492IuSk7xIf2G2wJzcHwGaCFa2o3gRcxmIg1j62guetAeDDFELizDaJlVRIOg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-fsharp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-fsharp/-/dict-fsharp-1.1.0.tgz", + "integrity": "sha512-oguWmHhGzgbgbEIBKtgKPrFSVAFtvGHaQS0oj+vacZqMObwkapcTGu7iwf4V3Bc2T3caf0QE6f6rQfIJFIAVsw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-fullstack": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-3.2.6.tgz", + "integrity": "sha512-cSaq9rz5RIU9j+0jcF2vnKPTQjxGXclntmoNp4XB7yFX2621PxJcekGjwf/lN5heJwVxGLL9toR0CBlGKwQBgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-gaming-terms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-gaming-terms/-/dict-gaming-terms-1.1.1.tgz", + "integrity": "sha512-tb8GFxjTLDQstkJcJ90lDqF4rKKlMUKs5/ewePN9P+PYRSehqDpLI5S5meOfPit8LGszeOrjUdBQ4zXo7NpMyQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-git": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-3.0.6.tgz", + "integrity": "sha512-nazfOqyxlBOQGgcur9ssEOEQCEZkH8vXfQe8SDEx8sCN/g0SFm8ktabgLVmBOXjy3RzjVNLlM2nBfRQ7e6+5hQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-golang": { + "version": "6.0.22", + "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.22.tgz", + "integrity": "sha512-FvV0m3Y0nUFxw36uDCD8UtfOPv4wsZnnlabNwB3xNZ2IBn0gBURuMUZywScb9sd2wXM8VFBRoU//tc6NQsOVOg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-google": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-google/-/dict-google-1.0.8.tgz", + "integrity": "sha512-BnMHgcEeaLyloPmBs8phCqprI+4r2Jb8rni011A8hE+7FNk7FmLE3kiwxLFrcZnnb7eqM0agW4zUaNoB0P+z8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-haskell": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-haskell/-/dict-haskell-4.0.5.tgz", + "integrity": "sha512-s4BG/4tlj2pPM9Ha7IZYMhUujXDnI0Eq1+38UTTCpatYLbQqDwRFf2KNPLRqkroU+a44yTUAe0rkkKbwy4yRtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-html": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.11.tgz", + "integrity": "sha512-QR3b/PB972SRQ2xICR1Nw/M44IJ6rjypwzA4jn+GH8ydjAX9acFNfc+hLZVyNe0FqsE90Gw3evLCOIF0vy1vQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-html-symbol-entities": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.3.tgz", + "integrity": "sha512-aABXX7dMLNFdSE8aY844X4+hvfK7977sOWgZXo4MTGAmOzR8524fjbJPswIBK7GaD3+SgFZ2yP2o0CFvXDGF+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-java": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-java/-/dict-java-5.0.11.tgz", + "integrity": "sha512-T4t/1JqeH33Raa/QK/eQe26FE17eUCtWu+JsYcTLkQTci2dk1DfcIKo8YVHvZXBnuM43ATns9Xs0s+AlqDeH7w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-julia": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-julia/-/dict-julia-1.1.0.tgz", + "integrity": "sha512-CPUiesiXwy3HRoBR3joUseTZ9giFPCydSKu2rkh6I2nVjXnl5vFHzOMLXpbF4HQ1tH2CNfnDbUndxD+I+7eL9w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-k8s": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-k8s/-/dict-k8s-1.0.11.tgz", + "integrity": "sha512-8ojNwB5j4PfZ1Gq9n5c/HKJCtZD3h6+wFy+zpALpDWFFQ2qT22Be30+3PVd+G5gng8or0LeK8VgKKd0l1uKPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-kotlin": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-kotlin/-/dict-kotlin-1.1.0.tgz", + "integrity": "sha512-vySaVw6atY7LdwvstQowSbdxjXG6jDhjkWVWSjg1XsUckyzH1JRHXe9VahZz1i7dpoFEUOWQrhIe5B9482UyJQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-latex": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-latex/-/dict-latex-4.0.3.tgz", + "integrity": "sha512-2KXBt9fSpymYHxHfvhUpjUFyzrmN4c4P8mwIzweLyvqntBT3k0YGZJSriOdjfUjwSygrfEwiuPI1EMrvgrOMJw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-lorem-ipsum": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-lorem-ipsum/-/dict-lorem-ipsum-4.0.4.tgz", + "integrity": "sha512-+4f7vtY4dp2b9N5fn0za/UR0kwFq2zDtA62JCbWHbpjvO9wukkbl4rZg4YudHbBgkl73HRnXFgCiwNhdIA1JPw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-lua": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-lua/-/dict-lua-4.0.7.tgz", + "integrity": "sha512-Wbr7YSQw+cLHhTYTKV6cAljgMgcY+EUAxVIZW3ljKswEe4OLxnVJ7lPqZF5JKjlXdgCjbPSimsHqyAbC5pQN/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-makefile": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-makefile/-/dict-makefile-1.0.4.tgz", + "integrity": "sha512-E4hG/c0ekPqUBvlkrVvzSoAA+SsDA9bLi4xSV3AXHTVru7Y2bVVGMPtpfF+fI3zTkww/jwinprcU1LSohI3ylw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-markdown": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-markdown/-/dict-markdown-2.0.11.tgz", + "integrity": "sha512-stZieFKJyMQbzKTVoalSx2QqCpB0j8nPJF/5x+sBnDIWgMC65jp8Wil+jccWh9/vnUVukP3Ejewven5NC7SWuQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@cspell/dict-css": "^4.0.17", + "@cspell/dict-html": "^4.0.11", + "@cspell/dict-html-symbol-entities": "^4.0.3", + "@cspell/dict-typescript": "^3.2.2" + } + }, + "node_modules/@cspell/dict-monkeyc": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.0.10.tgz", + "integrity": "sha512-7RTGyKsTIIVqzbvOtAu6Z/lwwxjGRtY5RkKPlXKHEoEAgIXwfDxb5EkVwzGQwQr8hF/D3HrdYbRT8MFBfsueZw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-node": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-5.0.7.tgz", + "integrity": "sha512-ZaPpBsHGQCqUyFPKLyCNUH2qzolDRm1/901IO8e7btk7bEDF56DN82VD43gPvD4HWz3yLs/WkcLa01KYAJpnOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-npm": { + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.2.9.tgz", + "integrity": "sha512-1uxRQ0LGPweRX8U9EEoU/tk5GGtTLAJT0BMmeHbe2AfzxX3nYSZtK/q52h9yg/wZLgvnFYzha2DL70uuT8oZuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-php": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.0.14.tgz", + "integrity": "sha512-7zur8pyncYZglxNmqsRycOZ6inpDoVd4yFfz1pQRe5xaRWMiK3Km4n0/X/1YMWhh3e3Sl/fQg5Axb2hlN68t1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-powershell": { + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/@cspell/dict-powershell/-/dict-powershell-5.0.14.tgz", + "integrity": "sha512-ktjjvtkIUIYmj/SoGBYbr3/+CsRGNXGpvVANrY0wlm/IoGlGywhoTUDYN0IsGwI2b8Vktx3DZmQkfb3Wo38jBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-public-licenses": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.13.tgz", + "integrity": "sha512-1Wdp/XH1ieim7CadXYE7YLnUlW0pULEjVl9WEeziZw3EKCAw8ZI8Ih44m4bEa5VNBLnuP5TfqC4iDautAleQzQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-python": { + "version": "4.2.18", + "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-4.2.18.tgz", + "integrity": "sha512-hYczHVqZBsck7DzO5LumBLJM119a3F17aj8a7lApnPIS7cmEwnPc2eACNscAHDk7qAo2127oI7axUoFMe9/g1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/dict-data-science": "^2.0.8" + } + }, + "node_modules/@cspell/dict-r": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-r/-/dict-r-2.1.0.tgz", + "integrity": "sha512-k2512wgGG0lTpTYH9w5Wwco+lAMf3Vz7mhqV8+OnalIE7muA0RSuD9tWBjiqLcX8zPvEJr4LdgxVju8Gk3OKyA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-ruby": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-5.0.8.tgz", + "integrity": "sha512-ixuTneU0aH1cPQRbWJvtvOntMFfeQR2KxT8LuAv5jBKqQWIHSxzGlp+zX3SVyoeR0kOWiu64/O5Yn836A5yMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-rust": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-4.0.11.tgz", + "integrity": "sha512-OGWDEEzm8HlkSmtD8fV3pEcO2XBpzG2XYjgMCJCRwb2gRKvR+XIm6Dlhs04N/K2kU+iH8bvrqNpM8fS/BFl0uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-scala": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-5.0.7.tgz", + "integrity": "sha512-yatpSDW/GwulzO3t7hB5peoWwzo+Y3qTc0pO24Jf6f88jsEeKmDeKkfgPbYuCgbE4jisGR4vs4+jfQZDIYmXPA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-shell": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-shell/-/dict-shell-1.1.0.tgz", + "integrity": "sha512-D/xHXX7T37BJxNRf5JJHsvziFDvh23IF/KvkZXNSh8VqcRdod3BAz9VGHZf6VDqcZXr1VRqIYR3mQ8DSvs3AVQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-software-terms": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-5.1.2.tgz", + "integrity": "sha512-MssT9yyInezB6mFqHTDNOIVjbMakORllIt7IJ91LrgiQOcDLzidR0gN9pE340s655TJ8U5MJNAfRfH0oRU14KQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-sql": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-sql/-/dict-sql-2.2.0.tgz", + "integrity": "sha512-MUop+d1AHSzXpBvQgQkCiok8Ejzb+nrzyG16E8TvKL2MQeDwnIvMe3bv90eukP6E1HWb+V/MA/4pnq0pcJWKqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-svelte": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-svelte/-/dict-svelte-1.0.6.tgz", + "integrity": "sha512-8LAJHSBdwHCoKCSy72PXXzz7ulGROD0rP1CQ0StOqXOOlTUeSFaJJlxNYjlONgd2c62XBQiN2wgLhtPN+1Zv7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-swift": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-swift/-/dict-swift-2.0.5.tgz", + "integrity": "sha512-3lGzDCwUmnrfckv3Q4eVSW3sK3cHqqHlPprFJZD4nAqt23ot7fic5ALR7J4joHpvDz36nHX34TgcbZNNZOC/JA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-terraform": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-terraform/-/dict-terraform-1.1.2.tgz", + "integrity": "sha512-RB9dnhxKIiWpwQB+b3JuFa8X4m+6Ny92Y4Z5QARR7jEtapg8iF2ODZX1yLtozp4kFVoRsUKEP6vj3MLv87VTdg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-typescript": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.2.2.tgz", + "integrity": "sha512-H9Y+uUHsTIDFO/jdfUAcqmcd5osT+2DB5b0aRCHfLWN/twUbGn/1qq3b7YwEvttxKlYzWHU3uNFf+KfA93VY7w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-vue": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-vue/-/dict-vue-3.0.4.tgz", + "integrity": "sha512-0dPtI0lwHcAgSiQFx8CzvqjdoXROcH+1LyqgROCpBgppommWpVhbQ0eubnKotFEXgpUCONVkeZJ6Ql8NbTEu+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dynamic-import": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-9.1.2.tgz", + "integrity": "sha512-Kg22HCx5m0znVPLea2jRrvMnzHZAAzqcDr5g6Dbd4Pizs5b3SPQuRpFmYaDvKo26JNZnfRqA9eweiuE5aQAf2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/url": "9.1.2", + "import-meta-resolve": "^4.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@cspell/eslint-plugin": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@cspell/eslint-plugin/-/eslint-plugin-9.1.2.tgz", + "integrity": "sha512-UUCCBAyv3gTL1P19fX9C+cknkwCXHvnHUAaFBz25dX6PhJSPyYPmVdA8jm/2H6+GQYKBnHvWgfjkkiZgtqoQRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-types": "9.1.2", + "@cspell/url": "9.1.2", + "cspell-lib": "9.1.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "eslint": "^7 || ^8 || ^9" + } + }, + "node_modules/@cspell/eslint-plugin/node_modules/@pkgr/core": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", + "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@cspell/eslint-plugin/node_modules/synckit": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", + "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.4" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/@cspell/filetypes": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-9.1.2.tgz", + "integrity": "sha512-j+6kDz3GbeYwwtlzVosqVaSiFGMhf0u3y8eAP3IV2bTelhP2ZiOLD+yNbAyYGao7p10/Sqv+Ri0yT7IsGLniww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@cspell/strong-weak-map": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-9.1.2.tgz", + "integrity": "sha512-6X9oXnklvdt1pd0x0Mh6qXaaIRxjt0G50Xz5ZGm3wpAagv0MFvTThdmYVFfBuZ91x7fDT3u77y3d1uqdGQW1CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@cspell/url": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@cspell/url/-/url-9.1.2.tgz", + "integrity": "sha512-PMJBuLYQIdFnEfPHQXaVE5hHUkbbOxOIRmHyZwWEc9+79tIaIkiwLpjZvbm8p6f9WXAaESqXs/uK2tUC/bjwmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/@cypress/code-coverage": { "version": "3.13.6", "resolved": "https://registry.npmjs.org/@cypress/code-coverage/-/code-coverage-3.13.6.tgz", @@ -2372,7 +2996,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "optional": true, + "devOptional": true, "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -2387,7 +3011,7 @@ "version": "3.4.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "optional": true, + "devOptional": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -2399,7 +3023,7 @@ "version": "4.10.1", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", - "optional": true, + "devOptional": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -2408,7 +3032,7 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "optional": true, + "devOptional": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -2431,7 +3055,7 @@ "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "optional": true, + "devOptional": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -2446,7 +3070,7 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "optional": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -2458,7 +3082,7 @@ "version": "8.57.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", - "optional": true, + "devOptional": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -2467,7 +3091,7 @@ "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "optional": true, + "devOptional": true, "dependencies": { "@humanwhocodes/object-schema": "^2.0.2", "debug": "^4.3.1", @@ -2481,7 +3105,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "optional": true, + "devOptional": true, "engines": { "node": ">=12.22" }, @@ -2494,7 +3118,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "optional": true + "devOptional": true }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -5405,7 +6029,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "optional": true + "devOptional": true }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.6", @@ -5558,7 +6182,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "optional": true, + "devOptional": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -5848,6 +6472,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "dev": true, + "license": "MIT" + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -7197,6 +7828,36 @@ "node": ">=6" } }, + "node_modules/clear-module": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/clear-module/-/clear-module-4.1.2.tgz", + "integrity": "sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^2.0.0", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clear-module/node_modules/parent-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz", + "integrity": "sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cli-boxes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", @@ -7370,6 +8031,30 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/comment-json": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.5.tgz", + "integrity": "sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/comment-json/node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, "node_modules/common-path-prefix": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", @@ -7894,6 +8579,159 @@ "node": ">= 8" } }, + "node_modules/cspell-config-lib": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-9.1.2.tgz", + "integrity": "sha512-QvHHGUuMI5h3ymU6O/Qz8zfhMhvPTuopT1FgebYRBB1cyggl4KnEJKU9m7wy/SQ1IGSlFDtQp6rCy70ujTfavQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-types": "9.1.2", + "comment-json": "^4.2.5", + "yaml": "^2.8.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cspell-config-lib/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", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/cspell-dictionary": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-9.1.2.tgz", + "integrity": "sha512-Osn5f9ugkX/zA3PVtSmYKRer3gZX3YqVB0UH0wVNzi8Ryl/1RUuYLIcvd0SDEhiVW56WKxFLfZ5sggTz/l9cDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-pipe": "9.1.2", + "@cspell/cspell-types": "9.1.2", + "cspell-trie-lib": "9.1.2", + "fast-equals": "^5.2.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cspell-glob": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-9.1.2.tgz", + "integrity": "sha512-l7Mqirn5h2tilTXgRamRIqqnzeA7R5iJEtJkY/zHDMEBeLWTR/5ai7dBp2+ooe8gIebpDtvv4938IXa5/75E6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/url": "9.1.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cspell-glob/node_modules/picomatch": { + "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" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/cspell-grammar": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-9.1.2.tgz", + "integrity": "sha512-vUcnlUqJKK0yhwYHfGC71zjGyEn918l64U/NWb1ijn1VXrL6gsh3w8Acwdo++zbpOASd9HTAuuZelveDJKLLgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-pipe": "9.1.2", + "@cspell/cspell-types": "9.1.2" + }, + "bin": { + "cspell-grammar": "bin.mjs" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cspell-io": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-9.1.2.tgz", + "integrity": "sha512-oLPxbteI+uFV9ZPcJjII7Lr/C/gVXpdmDLlAMwR8/7LHGnEfxXR0lqYu5GZVEvZ7riX9whCUOsQWQQqr2u2Fzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-service-bus": "9.1.2", + "@cspell/url": "9.1.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cspell-lib": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-9.1.2.tgz", + "integrity": "sha512-OFCssgfp6Z2gd1K8j2FsYr9YGoA/C6xXlcUwgU75Ut/XMZ/S44chdA9fUupGd4dUOw+CZl0qKzSP21J6kYObIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-bundled-dicts": "9.1.2", + "@cspell/cspell-pipe": "9.1.2", + "@cspell/cspell-resolver": "9.1.2", + "@cspell/cspell-types": "9.1.2", + "@cspell/dynamic-import": "9.1.2", + "@cspell/filetypes": "9.1.2", + "@cspell/strong-weak-map": "9.1.2", + "@cspell/url": "9.1.2", + "clear-module": "^4.1.2", + "comment-json": "^4.2.5", + "cspell-config-lib": "9.1.2", + "cspell-dictionary": "9.1.2", + "cspell-glob": "9.1.2", + "cspell-grammar": "9.1.2", + "cspell-io": "9.1.2", + "cspell-trie-lib": "9.1.2", + "env-paths": "^3.0.0", + "fast-equals": "^5.2.2", + "gensequence": "^7.0.0", + "import-fresh": "^3.3.1", + "resolve-from": "^5.0.0", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-uri": "^3.1.0", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cspell-trie-lib": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-9.1.2.tgz", + "integrity": "sha512-TkIQaknRRusUznqy+HwpqKCETCAznrzPJJHRHi8m6Zo3tAMsnIpaBQPRN8xem6w8/r/yJqFhLrsLSma0swyviQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-pipe": "9.1.2", + "@cspell/cspell-types": "9.1.2", + "gensequence": "^7.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/css-declaration-sorter": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", @@ -8834,7 +9672,7 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "optional": true + "devOptional": true }, "node_modules/deepmerge": { "version": "4.3.1", @@ -9050,7 +9888,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "optional": true, + "devOptional": true, "dependencies": { "esutils": "^2.0.2" }, @@ -9352,6 +10190,19 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/envinfo": { "version": "7.10.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", @@ -9617,7 +10468,7 @@ "version": "8.57.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", - "optional": true, + "devOptional": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -10103,7 +10954,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "optional": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -10118,7 +10969,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "optional": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -10134,7 +10985,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "optional": true, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -10146,13 +10997,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "optional": true + "devOptional": true }, "node_modules/eslint/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==", - "optional": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -10164,7 +11015,7 @@ "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "optional": true, + "devOptional": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -10180,7 +11031,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "optional": true, + "devOptional": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -10192,7 +11043,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "optional": true, + "devOptional": true, "engines": { "node": ">=4.0" } @@ -10201,7 +11052,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "optional": true, + "devOptional": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -10217,7 +11068,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "optional": true, + "devOptional": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -10229,7 +11080,7 @@ "version": "13.20.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "optional": true, + "devOptional": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -10244,7 +11095,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "optional": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -10253,7 +11104,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "optional": true, + "devOptional": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -10268,7 +11119,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "optional": true, + "devOptional": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -10283,7 +11134,7 @@ "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==", - "optional": true, + "devOptional": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -10298,7 +11149,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "optional": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -10310,7 +11161,7 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "optional": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -10322,7 +11173,7 @@ "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "optional": true, + "devOptional": true, "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -10339,7 +11190,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "optional": true, + "devOptional": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -10364,7 +11215,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "optional": true, + "devOptional": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -10376,7 +11227,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "optional": true, + "devOptional": true, "engines": { "node": ">=4.0" } @@ -10689,6 +11540,16 @@ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "optional": true }, + "node_modules/fast-equals": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", + "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -10728,7 +11589,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "optional": true + "devOptional": true }, "node_modules/fastest-levenshtein": { "version": "1.0.12", @@ -10796,7 +11657,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "optional": true, + "devOptional": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -11092,7 +11953,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "optional": true, + "devOptional": true, "dependencies": { "flatted": "^3.1.0", "rimraf": "^3.0.2" @@ -11105,7 +11966,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "optional": true, + "devOptional": true, "dependencies": { "glob": "^7.1.3" }, @@ -11120,7 +11981,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", - "optional": true + "devOptional": true }, "node_modules/focus-trap": { "version": "7.6.4", @@ -11513,6 +12374,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gensequence": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/gensequence/-/gensequence-7.0.0.tgz", + "integrity": "sha512-47Frx13aZh01afHJTB3zTtKIlFI6vWY+MYCN9Qpew6i52rfKjnhCF/l1YlC8UmEMvvntZZ6z4PiCcmyuedR2aQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -11659,6 +12530,32 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-directory/node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/global-dirs": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", @@ -11740,7 +12637,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "optional": true + "devOptional": true }, "node_modules/handle-thing": { "version": "2.0.1", @@ -11764,6 +12661,16 @@ "node": ">=4" } }, + "node_modules/has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -12391,6 +13298,17 @@ "node": ">=8" } }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -15134,7 +16052,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "optional": true + "devOptional": true }, "node_modules/json-stringify-safe": { "version": "5.0.1", @@ -15310,7 +16228,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "optional": true, + "devOptional": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -15477,7 +16395,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "optional": true + "devOptional": true }, "node_modules/lodash.once": { "version": "4.1.1", @@ -17436,7 +18354,7 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "optional": true, + "devOptional": true, "dependencies": { "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", @@ -18315,7 +19233,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "optional": true, + "devOptional": true, "engines": { "node": ">= 0.8.0" } @@ -19072,6 +19990,16 @@ "node": ">=4" } }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/replace-ext": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", @@ -20954,7 +21882,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "optional": true + "devOptional": true }, "node_modules/thingies": { "version": "1.21.0", @@ -21449,7 +22377,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "optional": true, + "devOptional": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -21895,6 +22823,20 @@ "extsprintf": "^1.2.0" } }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", @@ -22825,6 +23767,19 @@ } } }, + "node_modules/xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", diff --git a/workspaces/frontend/package.json b/workspaces/frontend/package.json index 8e08b930..7adc8470 100644 --- a/workspaces/frontend/package.json +++ b/workspaces/frontend/package.json @@ -32,11 +32,12 @@ "cypress:run:mock": "CY_MOCK=1 npm run cypress:run -- ", "cypress:server:build": "POLL_INTERVAL=9999999 FAST_POLL_INTERVAL=9999999 npm run build", "cypress:server": "serve ./dist -p 9001 -s -L", - "prettier": "prettier --ignore-path .gitignore --write \"**/*{.ts,.tsx,.js,.jsx,.css,.json}\"", - "prettier:check": "prettier --ignore-path .gitignore --check \"**/*{.ts,.tsx,.js,.jsx,.css,.json}\"", + "prettier": "prettier --ignore-path .gitignore --write \"**/*{.ts,.tsx,.js,.cjs,.jsx,.css,.json}\"", + "prettier:check": "prettier --ignore-path .gitignore --check \"**/*{.ts,.tsx,.js,.cjs,.jsx,.css,.json}\"", "prepare": "cd ../../ && husky workspaces/frontend/.husky" }, "devDependencies": { + "@cspell/eslint-plugin": "^9.1.2", "@cypress/code-coverage": "^3.13.5", "@mui/icons-material": "^6.3.1", "@mui/material": "^6.3.1", diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/EditableLabels.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/EditableLabels.tsx index 5d7348c2..f738e956 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/EditableLabels.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/EditableLabels.tsx @@ -81,7 +81,7 @@ export const EditableLabels: React.FC = ({ rows, setRows }) return ( diff --git a/workspaces/frontend/src/app/pages/notFound/NotFound.tsx b/workspaces/frontend/src/app/pages/notFound/NotFound.tsx index e8a4c2db..9e376102 100644 --- a/workspaces/frontend/src/app/pages/notFound/NotFound.tsx +++ b/workspaces/frontend/src/app/pages/notFound/NotFound.tsx @@ -24,7 +24,7 @@ const NotFound: React.FunctionComponent = () => { - We didn't find a page that matches the address you navigated to. + We did not find a page that matches the address you navigated to. diff --git a/workspaces/frontend/src/shared/components/DeleteModal.tsx b/workspaces/frontend/src/shared/components/DeleteModal.tsx index f97d7f7d..1ac13067 100644 --- a/workspaces/frontend/src/shared/components/DeleteModal.tsx +++ b/workspaces/frontend/src/shared/components/DeleteModal.tsx @@ -90,7 +90,7 @@ const DeleteModal: React.FC = ({ {showWarning && ( } variant="error"> - The name doesn't match. Please enter exactly: {resourceName} + The name does not match. Please enter exactly: {resourceName} )} diff --git a/workspaces/frontend/src/shared/mock/mockBuilder.ts b/workspaces/frontend/src/shared/mock/mockBuilder.ts index 002d1279..2ba0862a 100644 --- a/workspaces/frontend/src/shared/mock/mockBuilder.ts +++ b/workspaces/frontend/src/shared/mock/mockBuilder.ts @@ -365,7 +365,7 @@ export const buildMockWorkspaceList = (args: { current: { id: podConfig.id, displayName: podConfig.displayName, - description: `Pod with ${i}00 Milicores, ${i} GiB RAM`, + description: `Pod with ${i}00 Millicores, ${i} GiB RAM`, labels: [ { key: 'cpu', diff --git a/workspaces/frontend/src/shared/style/MUI-theme.scss b/workspaces/frontend/src/shared/style/MUI-theme.scss index ebc7263b..ec4c1396 100644 --- a/workspaces/frontend/src/shared/style/MUI-theme.scss +++ b/workspaces/frontend/src/shared/style/MUI-theme.scss @@ -648,7 +648,7 @@ align-self: start; } /* CSS workaround for spacing in labels in Workspace Kind */ -.form-label-fieldgroup .pf-v6-c-table tr:where(.pf-v6-c-table__tr) > :where(th, td) { +.form-label-field-group .pf-v6-c-table tr:where(.pf-v6-c-table__tr) > :where(th, td) { padding-block-start: 0px; } /* CSS workaround to use MUI icon for sort icon */ diff --git a/workspaces/frontend/src/shared/utilities/useFetchState.ts b/workspaces/frontend/src/shared/utilities/useFetchState.ts index 8765f094..a4fe3049 100644 --- a/workspaces/frontend/src/shared/utilities/useFetchState.ts +++ b/workspaces/frontend/src/shared/utilities/useFetchState.ts @@ -207,7 +207,7 @@ const useFetchState = ( return [doRequest(), unload]; }, [fetchCallbackPromise]); - // Use a memmo to update the `changePendingRef` immediately on change. + // Use a memo to update the `changePendingRef` immediately on change. useMemo(() => { changePendingRef.current = true; // React to changes to the `call` reference. From da615f5f7ec4c5c2cc6ff16a6f916d612e9f783a Mon Sep 17 00:00:00 2001 From: Paulo Rego <832830+paulovmr@users.noreply.github.com> Date: Mon, 7 Jul 2025 08:17:22 -0300 Subject: [PATCH 28/68] chore(ws): added prettier to test and test:fix scripts (#470) Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> --- workspaces/frontend/package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/workspaces/frontend/package.json b/workspaces/frontend/package.json index 7adc8470..a4e19392 100644 --- a/workspaces/frontend/package.json +++ b/workspaces/frontend/package.json @@ -18,14 +18,15 @@ "build:prod": "webpack --config ./config/webpack.prod.js", "start:dev": "cross-env STYLE_THEME=$npm_config_theme webpack serve --hot --color --config ./config/webpack.dev.js", "start:dev:mock": "cross-env MOCK_API_ENABLED=true STYLE_THEME=$npm_config_theme npm run start:dev", - "test": "run-s test:lint test:unit test:cypress-ci", + "test": "run-s prettier:check test:lint test:unit test:cypress-ci", "test:cypress-ci": "npx concurrently -P -k -s first \"npm run cypress:server:build && npm run cypress:server\" \"npx wait-on tcp:127.0.0.1:9001 && npm run cypress:run:mock -- {@}\" -- ", "test:jest": "jest --passWithNoTests", "test:unit": "npm run test:jest -- --silent", "test:watch": "jest --watch", "test:coverage": "jest --coverage", - "test:fix": "eslint --ext .js,.ts,.jsx,.tsx ./src --fix", "test:lint": "eslint --max-warnings 0 --ext .js,.ts,.jsx,.tsx ./src", + "test:lint:fix": "eslint --ext .js,.ts,.jsx,.tsx ./src --fix", + "test:fix": "run-s prettier test:lint:fix", "cypress:open": "cypress open --project src/__tests__/cypress", "cypress:open:mock": "CY_MOCK=1 CY_WS_PORT=9002 npm run cypress:open -- ", "cypress:run": "cypress run -b chrome --project src/__tests__/cypress", From 9607fabd930d82c74d6eb662b0734e1fa4a49b54 Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Mon, 7 Jul 2025 15:09:50 -0400 Subject: [PATCH 29/68] fix(ws): Updates to Table Columns, Expandable Rows, and Theming (#432) Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> add icon to workspaceKindsColumns interface fix(ws): Update table with expandable variant and fix styles fix secondary border in menu toggle fix menu toggle expanded text color and update icon to use status prop remove unused files add cluster storage description list group Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> Add title and packages revert form label styling, revert homeVol column fix linting fix lint Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> Add PR code suggestions, remove unused interfaces Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> remove unused import Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> fix filterWorkspacesTest Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> fix(ws): apply feedback to fix Cypress tests Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> Update tests, add width to defineDataFields, remove duplicate WorkspaceTableColumnKeys type Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> fix wrapping behavior Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> Replace Th values with mapped instance Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> revert column order Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> remove hardcoded package label instances Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> delete cursor rule --- .../workspaces/WorkspaceDetailsActivity.cy.ts | 7 +- .../tests/mocked/workspaces/Workspaces.cy.ts | 7 +- .../workspaces/filterWorkspacesTest.cy.ts | 41 ++- .../src/app/components/WorkspaceTable.tsx | 342 +++++++++--------- .../frontend/src/app/filterableDataHelper.ts | 1 + .../summary/WorkspaceKindSummary.tsx | 2 +- .../app/pages/Workspaces/DataVolumesList.tsx | 50 +-- .../pages/Workspaces/ExpandedWorkspaceRow.tsx | 72 +++- .../Workspaces/WorkspaceConfigDetails.tsx | 36 ++ .../Workspaces/WorkspaceConnectAction.tsx | 2 + .../Workspaces/WorkspacePackageDetails.tsx | 38 ++ .../app/pages/Workspaces/WorkspaceStorage.tsx | 27 ++ .../frontend/src/shared/mock/mockBuilder.ts | 38 +- .../frontend/src/shared/style/MUI-theme.scss | 44 ++- .../src/shared/utilities/WorkspaceUtils.ts | 23 +- 15 files changed, 471 insertions(+), 259 deletions(-) create mode 100644 workspaces/frontend/src/app/pages/Workspaces/WorkspaceConfigDetails.tsx create mode 100644 workspaces/frontend/src/app/pages/Workspaces/WorkspacePackageDetails.tsx create mode 100644 workspaces/frontend/src/app/pages/Workspaces/WorkspaceStorage.tsx diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/WorkspaceDetailsActivity.cy.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/WorkspaceDetailsActivity.cy.ts index e268984d..528ca287 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/WorkspaceDetailsActivity.cy.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/WorkspaceDetailsActivity.cy.ts @@ -11,7 +11,12 @@ describe('WorkspaceDetailsActivity Component', () => { // This tests depends on the mocked workspaces data at home page, needs revisit once workspace data fetched from BE it('open workspace details, open activity tab, check all fields match', () => { - cy.findAllByTestId('table-body').first().findByTestId('action-column').click(); + cy.findAllByTestId('table-body') + .first() + .findByTestId('action-column') + .find('button') + .should('be.visible') + .click(); // Extract first workspace from mock data cy.wait('@getWorkspaces').then((interception) => { if (!interception.response || !interception.response.body) { diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/Workspaces.cy.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/Workspaces.cy.ts index 6e401139..35a29d3e 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/Workspaces.cy.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/Workspaces.cy.ts @@ -119,7 +119,12 @@ describe('Workspaces Component', () => { }); function openDeleteModal() { - cy.findAllByTestId('table-body').first().findByTestId('action-column').click(); + cy.findAllByTestId('table-body') + .first() + .findByTestId('action-column') + .find('button') + .should('be.visible') + .click(); cy.findByTestId('action-delete').click(); cy.findByTestId('delete-modal-input').should('have.value', ''); } diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/filterWorkspacesTest.cy.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/filterWorkspacesTest.cy.ts index 9b468b13..748f4dd7 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/filterWorkspacesTest.cy.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/filterWorkspacesTest.cy.ts @@ -31,20 +31,36 @@ describe('Application', () => { it('filter rows with multiple filters', () => { home.visit(); + // First filter by name useFilter('name', 'Name', 'My'); - useFilter('podConfig', 'Pod Config', 'Tiny'); - cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 1); + cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 2); + cy.get("[id$='workspaces-table-row-1']").contains('My First Jupyter Notebook'); + + // Add second filter by image + useFilter('image', 'Image', 'jupyter'); + cy.get("[class$='pf-v6-c-toolbar__group']").contains('Name'); + cy.get("[class$='pf-v6-c-toolbar__group']").contains('Image'); + cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 2); cy.get("[id$='workspaces-table-row-1']").contains('My First Jupyter Notebook'); }); it('filter rows with multiple filters and remove one', () => { home.visit(); + // Add name filter useFilter('name', 'Name', 'My'); - useFilter('podConfig', 'Pod Config', 'Tiny'); - cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 1); + cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 2); cy.get("[id$='workspaces-table-row-1']").contains('My First Jupyter Notebook'); - cy.get("[class$='pf-v6-c-label-group__close']").eq(1).click(); - cy.get("[class$='pf-v6-c-toolbar__group']").should('not.contain', 'Pod Config'); + + // Add image filter + useFilter('image', 'Image', 'jupyter'); + cy.get("[class$='pf-v6-c-toolbar__group']").contains('Name'); + cy.get("[class$='pf-v6-c-toolbar__group']").contains('Image'); + cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 2); + + // Remove one filter (the first one) + cy.get("[class$='pf-v6-c-label-group__close']").first().click(); + cy.get("[class$='pf-v6-c-toolbar__group']").should('not.contain', 'Name'); + cy.get("[class$='pf-v6-c-toolbar__group']").contains('Image'); cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 2); cy.get("[id$='workspaces-table-row-1']").contains('My First Jupyter Notebook'); cy.get("[id$='workspaces-table-row-2']").contains('My Second Jupyter Notebook'); @@ -52,13 +68,20 @@ describe('Application', () => { it('filter rows with multiple filters and remove all', () => { home.visit(); + // Add name filter useFilter('name', 'Name', 'My'); - useFilter('podConfig', 'Pod Config', 'Tiny'); - cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 1); + cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 2); cy.get("[id$='workspaces-table-row-1']").contains('My First Jupyter Notebook'); + + // Add image filter + useFilter('image', 'Image', 'jupyter'); + cy.get("[class$='pf-v6-c-toolbar__group']").contains('Name'); + cy.get("[class$='pf-v6-c-toolbar__group']").contains('Image'); + + // Clear all filters cy.get('*').contains('Clear all filters').click(); - cy.get("[class$='pf-v6-c-toolbar__group']").should('not.contain', 'Pod Config'); cy.get("[class$='pf-v6-c-toolbar__group']").should('not.contain', 'Name'); + cy.get("[class$='pf-v6-c-toolbar__group']").should('not.contain', 'Image'); cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 2); }); }); diff --git a/workspaces/frontend/src/app/components/WorkspaceTable.tsx b/workspaces/frontend/src/app/components/WorkspaceTable.tsx index a2c11866..6254ff1c 100644 --- a/workspaces/frontend/src/app/components/WorkspaceTable.tsx +++ b/workspaces/frontend/src/app/components/WorkspaceTable.tsx @@ -10,6 +10,7 @@ import { Tooltip, Bullseye, Button, + Icon, } from '@patternfly/react-core'; import { Table, @@ -36,7 +37,6 @@ import { FilterableDataFieldKey, SortableDataFieldKey, } from '~/app/filterableDataHelper'; -import { ExpandedWorkspaceRow } from '~/app/pages/Workspaces/ExpandedWorkspaceRow'; import { useTypedNavigate } from '~/app/routerHelper'; import { buildKindLogoDictionary, @@ -47,11 +47,12 @@ import { WorkspaceConnectAction } from '~/app/pages/Workspaces/WorkspaceConnectA import CustomEmptyState from '~/shared/components/CustomEmptyState'; import Filter, { FilteredColumn, FilterRef } from '~/shared/components/Filter'; import WithValidImage from '~/shared/components/WithValidImage'; +import ImageFallback from '~/shared/components/ImageFallback'; import { formatResourceFromWorkspace, formatWorkspaceIdleState, } from '~/shared/utilities/WorkspaceUtils'; -import ImageFallback from '~/shared/components/ImageFallback'; +import { ExpandedWorkspaceRow } from '~/app/pages/Workspaces/ExpandedWorkspaceRow'; const { fields: wsTableColumns, @@ -59,21 +60,16 @@ const { sortableKeyArray: sortableWsTableColumnKeyArray, filterableKeyArray: filterableWsTableColumnKeyArray, } = defineDataFields({ - redirectStatus: { label: 'Redirect Status', isFilterable: false, isSortable: false }, - name: { label: 'Name', isFilterable: true, isSortable: true }, - kind: { label: 'Kind', isFilterable: true, isSortable: true }, - namespace: { label: 'Namespace', isFilterable: true, isSortable: true }, - image: { label: 'Image', isFilterable: true, isSortable: true }, - podConfig: { label: 'Pod Config', isFilterable: true, isSortable: true }, - state: { label: 'State', isFilterable: true, isSortable: true }, - homeVol: { label: 'Home Vol', isFilterable: true, isSortable: true }, - cpu: { label: 'CPU', isFilterable: false, isSortable: true }, - ram: { label: 'Memory', isFilterable: false, isSortable: true }, - gpu: { label: 'GPU', isFilterable: true, isSortable: true }, - idleGpu: { label: 'Idle GPU', isFilterable: true, isSortable: true }, - lastActivity: { label: 'Last Activity', isFilterable: false, isSortable: true }, - connect: { label: '', isFilterable: false, isSortable: false }, - actions: { label: '', isFilterable: false, isSortable: false }, + name: { label: 'Name', isFilterable: true, isSortable: true, width: 35 }, + image: { label: 'Image', isFilterable: true, isSortable: true, width: 25 }, + kind: { label: 'Kind', isFilterable: true, isSortable: true, width: 15 }, + namespace: { label: 'Namespace', isFilterable: true, isSortable: true, width: 15 }, + state: { label: 'State', isFilterable: true, isSortable: true, width: 15 }, + gpu: { label: 'GPU', isFilterable: true, isSortable: true, width: 15 }, + idleGpu: { label: 'Idle GPU', isFilterable: true, isSortable: true, width: 15 }, + lastActivity: { label: 'Last activity', isFilterable: false, isSortable: true, width: 15 }, + connect: { label: '', isFilterable: false, isSortable: false, width: 25 }, + actions: { label: '', isFilterable: false, isSortable: false, width: 10 }, }); export type WorkspaceTableColumnKeys = DataFieldKey; @@ -202,16 +198,12 @@ const WorkspaceTable = React.forwardRef( return ws.namespace.match(searchValueInput); case 'image': return ws.podTemplate.options.imageConfig.current.displayName.match(searchValueInput); - case 'podConfig': - return ws.podTemplate.options.podConfig.current.displayName.match(searchValueInput); case 'state': return ws.state.match(searchValueInput); case 'gpu': return formatResourceFromWorkspace(ws, 'gpu').match(searchValueInput); case 'idleGpu': return formatWorkspaceIdleState(ws).match(searchValueInput); - case 'homeVol': - return ws.podTemplate.volumes.home?.mountPath.match(searchValueInput); default: return true; } @@ -228,11 +220,7 @@ const WorkspaceTable = React.forwardRef( kind: workspace.workspaceKind.name, namespace: workspace.namespace, image: workspace.podTemplate.options.imageConfig.current.displayName, - podConfig: workspace.podTemplate.options.podConfig.current.displayName, state: workspace.state, - homeVol: workspace.podTemplate.volumes.home?.pvcName ?? '', - cpu: formatResourceFromWorkspace(workspace, 'cpu'), - ram: formatResourceFromWorkspace(workspace, 'memory'), gpu: formatResourceFromWorkspace(workspace, 'gpu'), idleGpu: formatWorkspaceIdleState(workspace), lastActivity: workspace.activity.lastActivity, @@ -280,6 +268,37 @@ const WorkspaceTable = React.forwardRef( }; }; + // Column-specific modifiers and special properties + const getColumnModifier = ( + columnKey: WorkspaceTableColumnKeys, + ): 'wrap' | 'nowrap' | undefined => { + switch (columnKey) { + case 'name': + case 'kind': + return 'nowrap'; + case 'image': + case 'namespace': + case 'state': + case 'gpu': + return 'wrap'; + case 'lastActivity': + return 'nowrap'; + default: + return undefined; + } + }; + + const getSpecialColumnProps = (columnKey: WorkspaceTableColumnKeys) => { + switch (columnKey) { + case 'connect': + return { screenReaderText: 'Connect action', hasContent: false }; + case 'actions': + return { screenReaderText: 'Primary action', hasContent: false }; + default: + return { hasContent: true }; + } + }; + const extractStateColor = (state: WorkspaceState) => { switch (state) { case WorkspaceState.WorkspaceStateRunning: @@ -305,31 +324,41 @@ const WorkspaceTable = React.forwardRef( case 'Info': return ( - ); case 'Warning': return ( - ); case 'Danger': return ( - ); case undefined: return ( - ); default: return ( - ); } @@ -371,19 +400,32 @@ const WorkspaceTable = React.forwardRef( } /> - +
- {canExpandRows && - ))} + {canExpandRows && + ); + })} {sortedWorkspaces.length > 0 && @@ -397,6 +439,7 @@ const WorkspaceTable = React.forwardRef( {canExpandRows && ( + ); + } + + if (columnKey === 'actions') { + return ( + + ); + } + + return ( + - ); - case 'name': - return ( - - ); - case 'kind': - return ( - - ); - case 'namespace': - return ( - - ); - case 'image': - return ( - - ); - case 'podConfig': - return ( - - ); - case 'state': - return ( - - ); - case 'homeVol': - return ( - - ); - case 'cpu': - return ( - - ); - case 'ram': - return ( - - ); - case 'gpu': - return ( - - ); - case 'idleGpu': - return ( - - ); - case 'lastActivity': - return ( - - ); - case 'connect': - return ( - - ); - case 'actions': - return ( - - ); - default: - return null; - } + {formatDistanceToNow(new Date(workspace.activity.lastActivity), { + addSuffix: true, + })} + + )} + + ); })} {isWorkspaceExpanded(workspace) && ( - + )} ))} diff --git a/workspaces/frontend/src/app/filterableDataHelper.ts b/workspaces/frontend/src/app/filterableDataHelper.ts index 5f3c3704..ed7c5891 100644 --- a/workspaces/frontend/src/app/filterableDataHelper.ts +++ b/workspaces/frontend/src/app/filterableDataHelper.ts @@ -2,6 +2,7 @@ export interface DataFieldDefinition { label: string; isSortable: boolean; isFilterable: boolean; + width?: number; } export type FilterableDataFieldKey> = { diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummary.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummary.tsx index 164fee2c..f09d1102 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummary.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummary.tsx @@ -92,7 +92,7 @@ const WorkspaceKindSummary: React.FC = () => { ref={workspaceTableRef} workspaces={workspaces} canCreateWorkspaces={false} - hiddenColumns={['connect', 'kind', 'homeVol']} + hiddenColumns={['connect', 'kind']} rowActions={tableRowActions} /> diff --git a/workspaces/frontend/src/app/pages/Workspaces/DataVolumesList.tsx b/workspaces/frontend/src/app/pages/Workspaces/DataVolumesList.tsx index 80f4e3c4..9c2a87bf 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/DataVolumesList.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/DataVolumesList.tsx @@ -3,12 +3,14 @@ import { ClipboardCopy, ClipboardCopyVariant, Content, + DescriptionList, + DescriptionListGroup, + DescriptionListTerm, + DescriptionListDescription, Flex, FlexItem, List, ListItem, - Stack, - StackItem, Tooltip, } from '@patternfly/react-core'; import { DatabaseIcon, LockedIcon } from '@patternfly/react-icons'; @@ -43,32 +45,32 @@ export const DataVolumesList: React.FC = ({ workspace }) = )} - - - - Mount path:{' '} - - {data.mountPath} - - - - + + Mount path: + + + {data.mountPath} + + + ); return ( - - - Cluster storage - - - - {workspaceDataVol.map((data, index) => ( - {singleDataVolRenderer(data)} - ))} - - - + + + + Cluster storage + + + + {workspaceDataVol.map((data, index) => ( + {singleDataVolRenderer(data)} + ))} + + + + ); }; diff --git a/workspaces/frontend/src/app/pages/Workspaces/ExpandedWorkspaceRow.tsx b/workspaces/frontend/src/app/pages/Workspaces/ExpandedWorkspaceRow.tsx index 4d53caf8..bb122c4f 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/ExpandedWorkspaceRow.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/ExpandedWorkspaceRow.tsx @@ -1,38 +1,74 @@ import React from 'react'; -import { ExpandableRowContent, Td, Tr } from '@patternfly/react-table'; +import { Tr, Td, ExpandableRowContent } from '@patternfly/react-table'; import { Workspace } from '~/shared/api/backendApiTypes'; -import { DataVolumesList } from '~/app/pages/Workspaces/DataVolumesList'; import { WorkspaceTableColumnKeys } from '~/app/components/WorkspaceTable'; +import { WorkspaceStorage } from './WorkspaceStorage'; +import { WorkspacePackageDetails } from './WorkspacePackageDetails'; +import { WorkspaceConfigDetails } from './WorkspaceConfigDetails'; interface ExpandedWorkspaceRowProps { workspace: Workspace; - columnKeys: WorkspaceTableColumnKeys[]; + visibleColumnKeys: WorkspaceTableColumnKeys[]; + canExpandRows: boolean; } export const ExpandedWorkspaceRow: React.FC = ({ workspace, - columnKeys, + visibleColumnKeys, + canExpandRows, }) => { - const renderExpandedData = () => - columnKeys.map((colKey, index) => { - switch (colKey) { - case 'name': + // Calculate total number of columns (including expand column if present) + const totalColumns = visibleColumnKeys.length + (canExpandRows ? 1 : 0); + + // Find the positions where we want to show our content + // We'll show storage in the first content column, package details in the second, + // and config details in the third + const getColumnIndex = (columnKey: WorkspaceTableColumnKeys) => { + const baseIndex = canExpandRows ? 1 : 0; // Account for expand column + return baseIndex + visibleColumnKeys.indexOf(columnKey); + }; + + const storageColumnIndex = visibleColumnKeys.includes('name') ? getColumnIndex('name') : 1; + const packageColumnIndex = visibleColumnKeys.includes('image') ? getColumnIndex('image') : 2; + const configColumnIndex = visibleColumnKeys.includes('kind') ? getColumnIndex('kind') : 3; + + return ( + + {/* Render cells for each column */} + {Array.from({ length: totalColumns }, (_, index) => { + if (index === storageColumnIndex) { return ( - ); - default: - return - + ); + } + + if (index === configColumnIndex) { + return ( + + ); + } + + // Empty cell for all other columns + return ); }; diff --git a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConfigDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConfigDetails.tsx new file mode 100644 index 00000000..b59873e2 --- /dev/null +++ b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConfigDetails.tsx @@ -0,0 +1,36 @@ +import * as React from 'react'; +import { + DescriptionList, + DescriptionListTerm, + DescriptionListGroup, + DescriptionListDescription, +} from '@patternfly/react-core'; +import { Workspace } from '~/shared/api/backendApiTypes'; +import { formatResourceFromWorkspace } from '~/shared/utilities/WorkspaceUtils'; + +interface WorkspaceConfigDetailsProps { + workspace: Workspace; +} + +export const WorkspaceConfigDetails: React.FC = ({ workspace }) => ( + + + Pod config + + {workspace.podTemplate.options.podConfig.current.displayName} + + + + CPU + + {formatResourceFromWorkspace(workspace, 'cpu')} + + + + Memory + + {formatResourceFromWorkspace(workspace, 'memory')} + + + +); diff --git a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConnectAction.tsx b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConnectAction.tsx index ab2decb2..efc59fe7 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConnectAction.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConnectAction.tsx @@ -52,6 +52,7 @@ export const WorkspaceConnectAction: React.FunctionComponent) => ( Connect , diff --git a/workspaces/frontend/src/app/pages/Workspaces/WorkspacePackageDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/WorkspacePackageDetails.tsx new file mode 100644 index 00000000..1eb96fee --- /dev/null +++ b/workspaces/frontend/src/app/pages/Workspaces/WorkspacePackageDetails.tsx @@ -0,0 +1,38 @@ +import * as React from 'react'; +import { + DescriptionList, + DescriptionListTerm, + DescriptionListDescription, + ListItem, + List, + DescriptionListGroup, +} from '@patternfly/react-core'; +import { Workspace } from '~/shared/api/backendApiTypes'; +import { extractPackageLabels, formatLabelKey } from '~/shared/utilities/WorkspaceUtils'; + +interface WorkspacePackageDetailsProps { + workspace: Workspace; +} + +export const WorkspacePackageDetails: React.FC = ({ workspace }) => { + const packageLabels = extractPackageLabels(workspace); + + const renderedItems = packageLabels.map((label) => ( + {`${formatLabelKey(label.key)} v${label.value}`} + )); + + return ( + + + Packages + + {renderedItems.length > 0 ? ( + {renderedItems} + ) : ( + No package information available + )} + + + + ); +}; diff --git a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceStorage.tsx b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceStorage.tsx new file mode 100644 index 00000000..9109bdf0 --- /dev/null +++ b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceStorage.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import { + DescriptionList, + DescriptionListTerm, + DescriptionListGroup, + DescriptionListDescription, +} from '@patternfly/react-core'; +import { Workspace } from '~/shared/api/backendApiTypes'; +import { DataVolumesList } from '~/app/pages/Workspaces/DataVolumesList'; + +interface WorkspaceStorageProps { + workspace: Workspace; +} + +export const WorkspaceStorage: React.FC = ({ workspace }) => ( + + + Home volume + + {workspace.podTemplate.volumes.home?.pvcName ?? 'None'} + + + + + + +); diff --git a/workspaces/frontend/src/shared/mock/mockBuilder.ts b/workspaces/frontend/src/shared/mock/mockBuilder.ts index 2ba0862a..3efcad00 100644 --- a/workspaces/frontend/src/shared/mock/mockBuilder.ts +++ b/workspaces/frontend/src/shared/mock/mockBuilder.ts @@ -81,6 +81,10 @@ export const buildMockWorkspace = (workspace?: Partial): Workspace => key: 'pythonVersion', value: '3.11', }, + { + key: 'jupyterlabVersion', + value: '1.9.0', + }, ], }, }, @@ -290,9 +294,30 @@ export const buildMockWorkspaceList = (args: { }): Workspace[] => { const states = Object.values(WorkspaceState); const imageConfigs = [ - { id: 'jupyterlab_scipy_190', displayName: `jupyter-scipy:v1.9.0` }, - { id: 'jupyterlab_scipy_200', displayName: `jupyter-scipy:v2.0.0` }, - { id: 'jupyterlab_scipy_210', displayName: `jupyter-scipy:v2.1.0` }, + { + id: 'jupyterlab_scipy_190', + displayName: `jupyter-scipy:v1.9.0`, + labels: [ + { key: 'pythonVersion', value: '3.12' }, + { key: 'jupyterlabVersion', value: '1.9.0' }, + ], + }, + { + id: 'jupyterlab_scipy_200', + displayName: `jupyter-scipy:v2.0.0`, + labels: [ + { key: 'pythonVersion', value: '3.12' }, + { key: 'jupyterlabVersion', value: '2.0.0' }, + ], + }, + { + id: 'jupyterlab_scipy_210', + displayName: `jupyter-scipy:v2.1.0`, + labels: [ + { key: 'pythonVersion', value: '3.13' }, + { key: 'jupyterlabVersion', value: '2.1.0' }, + ], + }, ]; const podConfigs = [ { id: 'tiny_cpu', displayName: 'Tiny CPU' }, @@ -353,12 +378,7 @@ export const buildMockWorkspaceList = (args: { id: imageConfig.id, displayName: imageConfig.displayName, description: 'JupyterLab, with SciPy Packages', - labels: [ - { - key: 'pythonVersion', - value: '3.11', - }, - ], + labels: imageConfig.labels, }, }, podConfig: { diff --git a/workspaces/frontend/src/shared/style/MUI-theme.scss b/workspaces/frontend/src/shared/style/MUI-theme.scss index ec4c1396..a50ae5b2 100644 --- a/workspaces/frontend/src/shared/style/MUI-theme.scss +++ b/workspaces/frontend/src/shared/style/MUI-theme.scss @@ -15,6 +15,7 @@ // Button --mui-button-font-weight: 500; + --mui-button--BorderWidth: 1px; --mui-button--hover--BorderWidth: 1px; --mui-button--PaddingBlockStart: 6px; --mui-button--PaddingBlockEnd: 6px; @@ -76,7 +77,7 @@ --mui-table--cell--PaddingInlineEnd: 16px; --mui-table--cell--PaddingBlockStart: 16px; --mui-table--cell--PaddingBlockEnd: 16px; - --mui-table--cell--first-last-child--PaddingInline: 0px; + --mui-table--cell--first-last-child--PaddingInline: 8px; --mui-table__thead--cell--FontSize: 14px; --mui-table__sort-indicator--MarginInlineStart: 4px; @@ -116,7 +117,6 @@ } .mui-theme .pf-v6-c-action-list__item .pf-v6-c-button { - --pf-v6-c-button--BorderRadius: 50%; --pf-v6-c-button--PaddingInlineStart: none; --pf-v6-c-button--PaddingInlineEnd: none; } @@ -152,7 +152,6 @@ --pf-v6-c-button--PaddingInlineStart: var(--mui-button--PaddingInlineStart); --pf-v6-c-button--PaddingInlineEnd: var(--mui-button--PaddingInlineEnd); --pf-v6-c-button--LineHeight: var(--mui-button--LineHeight); - --pf-v6-c-button--m-plain--BorderRadius: 50%; text-transform: var(--mui-text-transform); letter-spacing: 0.02857em; @@ -197,10 +196,6 @@ --pf-v6-c-card--m-selectable--m-selected--BorderColor: none; } -.mui-theme .pf-v6-c-description-list { - --pf-v6-c-description-list--RowGap: var(--pf-t--global--spacer--gap--group-to-group--horizontal--compact); -} - .mui-theme .pf-v6-c-description-list__term { font: var(--mui-font-subtitle2); } @@ -237,20 +232,26 @@ --pf-v6-c-form__section-title--MarginInlineEnd: 0px; } +// Base form label styles .mui-theme .pf-v6-c-form__label { + color: var(--mui-palette-grey-600); + pointer-events: none; + transition: all 0.2s ease; + --pf-v6-c-form__label-required--Color: currentColor; +} + +// Text input labels (text fields, textareas, selects) +.mui-theme .pf-v6-c-form__group:has(.pf-v6-c-form-control) .pf-v6-c-form__label, +.mui-theme .pf-v6-c-form__group:has(.pf-v6-c-text-input-group) .pf-v6-c-form__label { position: absolute; top: 35%; left: 12px; font-size: 14px; - color: var(--mui-palette-grey-600); - pointer-events: none; - transition: all 0.2s ease; transform-origin: left center; transform: translateY(-50%) scale(0.75); background-color: var(--mui-palette-common-white); padding: 0 4px; z-index: 1; - --pf-v6-c-form__label-required--Color: currentColor; } .mui-theme .pf-v6-c-form-control input::placeholder { @@ -514,10 +515,6 @@ align-self: stretch; } -.mui-theme .pf-v6-c-menu-toggle.pf-m-plain { - --pf-v6-c-menu-toggle--BorderRadius: 50%; -} - .mui-theme .pf-v6-c-menu-toggle.pf-m-primary { --pf-v6-c-menu-toggle--expanded--Color: var(--mui-palette-common-white); --pf-v6-c-menu-toggle--expanded--BackgroundColor: var(--mui-palette-primary-main); @@ -532,6 +529,13 @@ --pf-v6-c-menu-toggle--m-split-button--m-action--m-primary--child--BackgroundColor: var(--mui-palette-primary-dark); } +.pf-v6-c-menu-toggle.pf-m-secondary.pf-m-split-button { + --pf-v6-c-menu-toggle--BorderColor: var(--mui-palette-primary-main); + --pf-v6-c-menu-toggle--BorderWidth: var(--mui-button--BorderWidth); + --pf-v6-c-menu-toggle--BorderStyle: solid; + --pf-v6-c-menu-toggle--expanded--Color: var(--mui-palette-primary-dark); +} + .mui-theme .pf-v6-c-menu-toggle__button:has(.pf-v6-c-menu-toggle__toggle-icon) { --pf-v6-c-menu-toggle--PaddingBlockStart: var(--mui-spacing-4); --pf-v6-c-menu-toggle--PaddingBlockEnd: var(--mui-spacing-4); @@ -725,6 +729,12 @@ align-content: center; } +// Updates the expand button to be 36.5px wide to match menu toggle button width +.mui-theme .pf-v6-c-table__td.pf-v6-c-table__toggle .pf-v6-c-button.pf-m-plain { + --pf-v6-c-button--PaddingInlineStart: 6px; + --pf-v6-c-button--PaddingInlineEnd: 6px; +} + .mui-theme .pf-v6-c-label { --pf-v6-c-label--BorderRadius: 16px; --pf-v6-c-label--FontSize: 0.8125rem; @@ -775,10 +785,6 @@ --pf-v6-c-modal-box--BoxShadow: var(--mui-shadows-24); } -.mui-theme .pf-v6-c-button.pf-m-plain { - --pf-v6-c-button--BorderRadius: 50%; -} - .mui-theme .pf-v6-c-page__main-container { --pf-v6-c-page__main-container--BorderWidth: 0px; --pf-v6-c-page__main-container--BorderRadius: var(--mui-shape-borderRadius); diff --git a/workspaces/frontend/src/shared/utilities/WorkspaceUtils.ts b/workspaces/frontend/src/shared/utilities/WorkspaceUtils.ts index ff07e661..cc08a44c 100644 --- a/workspaces/frontend/src/shared/utilities/WorkspaceUtils.ts +++ b/workspaces/frontend/src/shared/utilities/WorkspaceUtils.ts @@ -1,4 +1,4 @@ -import { Workspace, WorkspaceState } from '~/shared/api/backendApiTypes'; +import { Workspace, WorkspaceState, WorkspaceOptionLabel } from '~/shared/api/backendApiTypes'; import { CPU_UNITS, MEMORY_UNITS_FOR_PARSING, @@ -102,3 +102,24 @@ export const countGpusFromWorkspaces = (workspaces: Workspace[]): number => const [gpuValue] = splitValueUnit(extractResourceValue(workspace, 'gpu') || '0', OTHER); return total + (gpuValue ?? 0); }, 0); + +// Helper function to format label keys into human-readable names +export const formatLabelKey = (key: string): string => { + // Handle camelCase version labels (e.g., pythonVersion -> Python) + if (key.endsWith('Version')) { + const baseName = key.slice(0, -7); // Remove 'Version' suffix + return baseName.charAt(0).toUpperCase() + baseName.slice(1); + } + + // Otherwise just capitalize the first letter + return key.charAt(0).toUpperCase() + key.slice(1); +}; + +// Check if a label represents version/package information +export const isPackageLabel = (key: string): boolean => key.endsWith('Version'); + +// Extract package labels from workspace image config +export const extractPackageLabels = (workspace: Workspace): WorkspaceOptionLabel[] => + workspace.podTemplate.options.imageConfig.current.labels.filter((label) => + isPackageLabel(label.key), + ); From 3fed049233ef2fe4eecf3fd7b70d891dfe7773e4 Mon Sep 17 00:00:00 2001 From: Paulo Rego <832830+paulovmr@users.noreply.github.com> Date: Wed, 9 Jul 2025 08:16:51 -0300 Subject: [PATCH 30/68] chore(ws): upgrade deprecated rimraf transitive dependency (#474) Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> --- workspaces/frontend/package-lock.json | 152 ++++++++------------------ workspaces/frontend/package.json | 3 + 2 files changed, 46 insertions(+), 109 deletions(-) diff --git a/workspaces/frontend/package-lock.json b/workspaces/frontend/package-lock.json index a283be97..f8702895 100644 --- a/workspaces/frontend/package-lock.json +++ b/workspaces/frontend/package-lock.json @@ -2584,16 +2584,16 @@ } }, "node_modules/@cypress/code-coverage": { - "version": "3.13.6", - "resolved": "https://registry.npmjs.org/@cypress/code-coverage/-/code-coverage-3.13.6.tgz", - "integrity": "sha512-nNVDYDK6r9zPqDIv9k7FibPP9/dATGRR3us9Ued/ldcxPz5x8WbVthjV5OIjqotRKEmS7wxiXFHSDhKJqaZNuw==", + "version": "3.14.5", + "resolved": "https://registry.npmjs.org/@cypress/code-coverage/-/code-coverage-3.14.5.tgz", + "integrity": "sha512-sSyCSiYpChgKIaO7Bglxp1Pjf1l6EQDejq6yIc4REcGXCVxrtjP5G5j2TsjH/zcceDvyShXH5DyLD21M9ryaeg==", "dev": true, "license": "MIT", "dependencies": { "@cypress/webpack-preprocessor": "^6.0.0", "chalk": "4.1.2", "dayjs": "1.11.13", - "debug": "4.3.7", + "debug": "4.4.0", "execa": "4.1.0", "globby": "11.1.0", "istanbul-lib-coverage": "^3.0.0", @@ -2603,7 +2603,7 @@ "peerDependencies": { "@babel/core": "^7.0.1", "@babel/preset-env": "^7.0.0", - "babel-loader": "^8.3 || ^9", + "babel-loader": "^8.3 || ^9 || ^10", "cypress": "*", "webpack": "^4 || ^5" } @@ -3124,7 +3124,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -3142,7 +3142,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=12" @@ -3155,7 +3155,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=12" @@ -3168,14 +3168,14 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@isaacs/cliui/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, + "devOptional": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -3193,7 +3193,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -3209,7 +3209,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -9578,9 +9578,9 @@ "dev": true }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -10079,7 +10079,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "devOptional": true }, "node_modules/ecc-jsbn": { "version": "0.1.2", @@ -10122,7 +10122,7 @@ "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 + "devOptional": true }, "node_modules/emojis-list": { "version": "3.0.0", @@ -11962,21 +11962,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "devOptional": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/flatted": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", @@ -12023,7 +12008,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", @@ -12040,7 +12025,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, + "devOptional": true, "license": "ISC", "engines": { "node": ">=14" @@ -12317,7 +12302,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "devOptional": true + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -12508,7 +12493,7 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "devOptional": true, + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -13331,7 +13316,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "devOptional": true, + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -13341,7 +13326,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "devOptional": true + "dev": true }, "node_modules/ini": { "version": "2.0.0", @@ -13598,7 +13583,7 @@ "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, + "devOptional": true, "engines": { "node": ">=8" } @@ -14068,23 +14053,6 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-processinfo/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -14195,7 +14163,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", - "dev": true, + "devOptional": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -16904,7 +16872,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, + "devOptional": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -18052,23 +18020,6 @@ "node": ">=8" } }, - "node_modules/nyc/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/nyc/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -18286,7 +18237,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "devOptional": true, + "dev": true, "dependencies": { "wrappy": "1" } @@ -18476,7 +18427,7 @@ "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, + "devOptional": true, "license": "BlueOak-1.0.0" }, "node_modules/param-case": { @@ -18551,7 +18502,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.10.0" } @@ -18581,7 +18532,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dev": true, + "devOptional": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^11.0.0", @@ -18598,7 +18549,7 @@ "version": "11.0.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", - "dev": true, + "devOptional": true, "license": "ISC", "engines": { "node": "20 || >=22" @@ -20152,7 +20103,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "glob": "^11.0.0", @@ -20172,7 +20123,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -20182,7 +20133,7 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -20206,7 +20157,7 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -21159,23 +21110,6 @@ "node": ">=8.0.0" } }, - "node_modules/spawn-wrap/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -21367,7 +21301,7 @@ "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, + "devOptional": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -21382,7 +21316,7 @@ "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, + "devOptional": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -21526,7 +21460,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -23644,7 +23578,7 @@ "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, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -23662,7 +23596,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -23678,7 +23612,7 @@ "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, + "devOptional": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -23691,7 +23625,7 @@ "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, + "devOptional": true, "license": "MIT" }, "node_modules/wrap-ansi/node_modules/ansi-styles": { @@ -23731,7 +23665,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "devOptional": true + "dev": true }, "node_modules/write-file-atomic": { "version": "4.0.2", diff --git a/workspaces/frontend/package.json b/workspaces/frontend/package.json index a4e19392..d79141af 100644 --- a/workspaces/frontend/package.json +++ b/workspaces/frontend/package.json @@ -6,6 +6,9 @@ "homepage": "https://github.com/kubeflow/notebooks", "license": "Apache-2.0", "private": true, + "overrides": { + "rimraf": "^6.0.1" + }, "engines": { "node": ">=20.0.0" }, From 3feccf7fca5a7b195efc848b06246a44a6b6158e Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Wed, 9 Jul 2025 07:20:51 -0400 Subject: [PATCH 31/68] fix(ws): Improve workspace form drawer details and wizard flow (#467) Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> move formatLabel to separate util add title, divider, and fix wizard buttons to align with PF design guidelines Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> prevent wizard button from active state when no selection Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> rebase Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> handle standard infra resource types --- .../pages/Workspaces/Form/WorkspaceForm.tsx | 60 ++++++++++++------- .../Form/image/WorkspaceFormImageDetails.tsx | 31 +++++++--- .../Form/kind/WorkspaceFormKindDetails.tsx | 2 +- .../Form/labelFilter/FilterByLabels.tsx | 3 +- .../WorkspaceFormPodConfigDetails.tsx | 33 +++++++--- .../frontend/src/shared/style/MUI-theme.scss | 14 ++++- .../src/shared/utilities/WorkspaceUtils.ts | 5 ++ 7 files changed, 106 insertions(+), 42 deletions(-) diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx index cf377e9f..89a29825 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx @@ -86,14 +86,29 @@ const WorkspaceForm: React.FC = () => { const canGoToPreviousStep = useMemo(() => currentStep > 0, [currentStep]); + const isCurrentStepValid = useMemo(() => { + switch (currentStep) { + case WorkspaceFormSteps.KindSelection: + return !!data.kind; + case WorkspaceFormSteps.ImageSelection: + return !!data.image; + case WorkspaceFormSteps.PodConfigSelection: + return !!data.podConfig; + case WorkspaceFormSteps.Properties: + return !!data.properties.workspaceName.trim(); + default: + return false; + } + }, [currentStep, data]); + const canGoToNextStep = useMemo( () => currentStep < Object.keys(WorkspaceFormSteps).length / 2 - 1, [currentStep], ); const canSubmit = useMemo( - () => !isSubmitting && !canGoToNextStep, - [canGoToNextStep, isSubmitting], + () => !isSubmitting && !canGoToNextStep && isCurrentStepValid, + [canGoToNextStep, isSubmitting, isCurrentStepValid], ); const handleSubmit = useCallback(async () => { @@ -255,8 +270,8 @@ const WorkspaceForm: React.FC = () => { - - - - + {canGoToNextStep ? ( + + ) : ( + + )} ); diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx index feb1ba68..0972c68d 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx @@ -1,8 +1,10 @@ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Button, Content, ContentVariants, + EmptyState, + EmptyStateBody, Flex, FlexItem, PageGroup, @@ -11,18 +13,22 @@ import { StackItem, } from '@patternfly/react-core'; import { t_global_spacer_sm as SmallPadding } from '@patternfly/react-tokens'; +import { ExclamationCircleIcon } from '@patternfly/react-icons'; import { ValidationErrorAlert } from '~/app/components/ValidationErrorAlert'; -import { useTypedNavigate } from '~/app/routerHelper'; +import useWorkspaceKindByName from '~/app/hooks/useWorkspaceKindByName'; +import { WorkspaceKind, ValidationError } from '~/shared/api/backendApiTypes'; +import { useTypedNavigate, useTypedParams } from '~/app/routerHelper'; import { useCurrentRouteKey } from '~/app/hooks/useCurrentRouteKey'; import useGenericObjectState from '~/app/hooks/useGenericObjectState'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { WorkspaceKindFormData } from '~/app/types'; import { ErrorEnvelopeException } from '~/shared/api/apiUtils'; -import { ValidationError } from '~/shared/api/backendApiTypes'; import { WorkspaceKindFileUpload } from './fileUpload/WorkspaceKindFileUpload'; import { WorkspaceKindFormProperties } from './properties/WorkspaceKindFormProperties'; import { WorkspaceKindFormImage } from './image/WorkspaceKindFormImage'; import { WorkspaceKindFormPodConfig } from './podConfig/WorkspaceKindFormPodConfig'; +import { WorkspaceKindFormPodTemplate } from './podTemplate/WorkspaceKindFormPodTemplate'; +import { EMPTY_WORKSPACE_KIND_FORM_DATA } from './helpers'; export enum WorkspaceKindFormView { Form, @@ -30,6 +36,19 @@ export enum WorkspaceKindFormView { } export type ValidationStatus = 'success' | 'error' | 'default'; +export type FormMode = 'edit' | 'create'; + +const convertToFormData = (initialData: WorkspaceKind): WorkspaceKindFormData => { + const { podTemplate, ...properties } = initialData; + const { options, ...spec } = podTemplate; + const { podConfig, imageConfig } = options; + return { + properties, + podConfig, + imageConfig, + podTemplate: spec, + }; +}; export const WorkspaceKindForm: React.FC = () => { const navigate = useTypedNavigate(); @@ -38,28 +57,23 @@ export const WorkspaceKindForm: React.FC = () => { const [yamlValue, setYamlValue] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [validated, setValidated] = useState('default'); - const mode = useCurrentRouteKey() === 'workspaceKindCreate' ? 'create' : 'edit'; - const [specErrors, setSpecErrors] = useState([]); + const mode: FormMode = useCurrentRouteKey() === 'workspaceKindCreate' ? 'create' : 'edit'; + const [specErrors, setSpecErrors] = useState<(ValidationError | ErrorEnvelopeException)[]>([]); - const [data, setData, resetData] = useGenericObjectState({ - properties: { - displayName: '', - description: '', - deprecated: false, - deprecationMessage: '', - hidden: false, - icon: { url: '' }, - logo: { url: '' }, - }, - imageConfig: { - default: '', - values: [], - }, - podConfig: { - default: '', - values: [], - }, - }); + const { kind } = useTypedParams<'workspaceKindEdit'>(); + const [initialFormData, initialFormDataLoaded, initialFormDataError] = + useWorkspaceKindByName(kind); + + const [data, setData, resetData, replaceData] = useGenericObjectState( + initialFormData ? convertToFormData(initialFormData) : EMPTY_WORKSPACE_KIND_FORM_DATA, + ); + + useEffect(() => { + if (!initialFormDataLoaded || initialFormData === null || mode === 'create') { + return; + } + replaceData(convertToFormData(initialFormData)); + }, [initialFormData, initialFormDataLoaded, mode, replaceData]); const handleSubmit = useCallback(async () => { setIsSubmitting(true); @@ -71,14 +85,20 @@ export const WorkspaceKindForm: React.FC = () => { console.info('New workspace kind created:', JSON.stringify(newWorkspaceKind)); navigate('workspaceKinds'); } + // TODO: Finish when WSKind API is finalized + // const updatedWorkspace = await api.updateWorkspaceKind({}, kind, { data: {} }); + // console.info('Workspace Kind updated:', JSON.stringify(updatedWorkspace)); + // navigate('workspaceKinds'); } catch (err) { if (err instanceof ErrorEnvelopeException) { const validationErrors = err.envelope.error?.cause?.validation_errors; if (validationErrors && validationErrors.length > 0) { - setSpecErrors(validationErrors); + setSpecErrors((prev) => [...prev, ...validationErrors]); setValidated('error'); return; } + setSpecErrors((prev) => [...prev, err]); + setValidated('error'); } // TODO: alert user about error console.error(`Error ${mode === 'edit' ? 'editing' : 'creating'} workspace kind: ${err}`); @@ -88,14 +108,26 @@ export const WorkspaceKindForm: React.FC = () => { }, [navigate, mode, api, yamlValue]); const canSubmit = useMemo( - () => !isSubmitting && yamlValue.length > 0 && validated === 'success', - [yamlValue, isSubmitting, validated], + () => !isSubmitting && validated === 'success', + [isSubmitting, validated], ); const cancel = useCallback(() => { navigate('workspaceKinds'); }, [navigate]); + if (mode === 'edit' && initialFormDataError) { + return ( + + {initialFormDataError.message} + + ); + } return ( <> @@ -159,6 +191,12 @@ export const WorkspaceKindForm: React.FC = () => { setData('podConfig', podConfig); }} /> + { + setData('podTemplate', podTemplate); + }} + /> )} @@ -169,9 +207,10 @@ export const WorkspaceKindForm: React.FC = () => { variant="primary" ouiaId="Primary" onClick={handleSubmit} - isDisabled={!canSubmit} + // TODO: button is always disabled on edit mode. Need to modify when WorkspaceKind edit is finalized + isDisabled={!canSubmit || mode === 'edit'} > - {mode === 'create' ? 'Create' : 'Edit'} + {mode === 'create' ? 'Create' : 'Save'} diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindFormPaginatedTable.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindFormPaginatedTable.tsx new file mode 100644 index 00000000..d7887f2f --- /dev/null +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindFormPaginatedTable.tsx @@ -0,0 +1,142 @@ +import React, { useMemo, useState } from 'react'; +import { Table, Thead, Tr, Td, Tbody, Th } from '@patternfly/react-table'; +import { + Dropdown, + DropdownItem, + getUniqueId, + Label, + MenuToggle, + PageSection, + Pagination, + PaginationVariant, + Radio, +} from '@patternfly/react-core'; +import { EllipsisVIcon } from '@patternfly/react-icons'; + +import { WorkspaceKindImageConfigValue } from '~/app/types'; +import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes'; + +interface PaginatedTableProps { + rows: WorkspaceKindImageConfigValue[] | WorkspacePodConfigValue[]; + defaultId: string; + setDefaultId: (id: string) => void; + handleEdit: (index: number) => void; + openDeleteModal: (index: number) => void; + ariaLabel: string; +} + +export const WorkspaceKindFormPaginatedTable: React.FC = ({ + rows, + defaultId, + setDefaultId, + handleEdit, + openDeleteModal, + ariaLabel, +}) => { + const [dropdownOpen, setDropdownOpen] = useState(null); + const [page, setPage] = useState(1); + const [perPage, setPerPage] = useState(10); + const rowPages = useMemo(() => { + const pages = []; + for (let i = 0; i < rows.length; i += perPage) { + pages.push(rows.slice(i, i + perPage)); + } + return pages; + }, [perPage, rows]); + + const onSetPage = ( + _event: React.MouseEvent | React.KeyboardEvent | MouseEvent, + newPage: number, + ) => { + setPage(newPage); + }; + + const onPerPageSelect = ( + _event: React.MouseEvent | React.KeyboardEvent | MouseEvent, + newPerPage: number, + newPage: number, + ) => { + setPerPage(newPerPage); + setPage(newPage); + }; + return ( + +
} - {visibleColumnKeys.map((columnKey) => ( - - {wsTableColumns[columnKey].label} - } + {visibleColumnKeys.map((columnKey) => { + const specialProps = getSpecialColumnProps(columnKey); + const modifier = getColumnModifier(columnKey); + + return ( + + {specialProps.hasContent ? wsTableColumns[columnKey].label : undefined} +
( /> )} {visibleColumnKeys.map((columnKey) => { - switch (columnKey) { - case 'redirectStatus': - return ( - + if (columnKey === 'connect') { + return ( + + + + ({ + ...action, + 'data-testid': `action-${action.id || ''}`, + }))} + /> + + {columnKey === 'name' && workspace.name} + {columnKey === 'image' && ( + + {workspace.podTemplate.options.imageConfig.current.displayName}{' '} {workspaceRedirectStatus[workspace.workspaceKind.name] ? getRedirectStatusIcon( workspaceRedirectStatus[workspace.workspaceKind.name]?.message @@ -421,143 +497,57 @@ const WorkspaceTable = React.forwardRef( ?.text || 'No API response available', ) : getRedirectStatusIcon(undefined, 'No API response available')} - + )} + {columnKey === 'kind' && ( + + } > - {workspace.name} - - ( + + {workspace.workspaceKind.name} - } - > - {(validSrc) => ( - - {workspace.workspaceKind.name} - - )} - - - {workspace.namespace} - - {workspace.podTemplate.options.imageConfig.current.displayName} - + )} + + )} + {columnKey === 'namespace' && workspace.namespace} + {columnKey === 'state' && ( + + )} + {columnKey === 'gpu' && formatResourceFromWorkspace(workspace, 'gpu')} + {columnKey === 'idleGpu' && formatWorkspaceIdleState(workspace)} + {columnKey === 'lastActivity' && ( + - {workspace.podTemplate.options.podConfig.current.displayName} - - - - {workspace.podTemplate.volumes.home?.pvcName ?? ''} - - {formatResourceFromWorkspace(workspace, 'cpu')} - - {formatResourceFromWorkspace(workspace, 'memory')} - - {formatResourceFromWorkspace(workspace, 'gpu')} - - {formatWorkspaceIdleState(workspace)} - - - {formatDistanceToNow(new Date(workspace.activity.lastActivity), { - addSuffix: true, - })} - - - - - ({ - ...action, - 'data-testid': `action-${action.id || ''}`, - }))} - /> -
+ - + ; - } - }); + } - return ( -
- {renderExpandedData()} + if (index === packageColumnIndex) { + return ( + + + + + + + + + ; + })}
+ + + + + + + + + + {rowPages[page - 1].map((row, index) => ( + + + + + + + + ))} + +
Display NameIDDefaultLabels +
{row.displayName}{row.id} + { + console.log(row.id); + setDefaultId(row.id); + }} + aria-label={`Select ${row.id} as default`} + /> + + {row.labels.length > 0 && + row.labels.map((label) => ( + + ))} + + ( + setDropdownOpen(dropdownOpen === index ? null : index)} + variant="plain" + aria-label="plain kebab" + > + + + )} + isOpen={dropdownOpen === index} + onSelect={() => setDropdownOpen(null)} + popperProps={{ position: 'right' }} + > + handleEdit(perPage * (page - 1) + index)}> + Edit + + openDeleteModal(perPage * (page - 1) + index)}> + Remove + + +
+ +
+ ); +}; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/helpers.ts b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/helpers.ts index aad5f622..786670e7 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/helpers.ts +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/helpers.ts @@ -104,6 +104,45 @@ export const emptyPodConfig: WorkspacePodConfigValue = { to: '', }, }; + +export const EMPTY_WORKSPACE_KIND_FORM_DATA = { + properties: { + displayName: '', + description: '', + deprecated: false, + deprecationMessage: '', + hidden: false, + icon: { url: '' }, + logo: { url: '' }, + }, + imageConfig: { + default: '', + values: [], + }, + podConfig: { + default: '', + values: [], + }, + podTemplate: { + podMetadata: { + labels: {}, + annotations: {}, + }, + volumeMounts: { + home: '', + }, + extraVolumeMounts: [], + culling: { + enabled: false, + maxInactiveSeconds: 86400, + activityProbe: { + jupyter: { + lastActivity: true, + }, + }, + }, + }, +}; // convert from k8s resource object {limits: {}, requests{}} to array of {type: '', limit: '', request: ''} for each type of resource (e.g. CPU, memory, nvidia.com/gpu) export const getResources = (currConfig: WorkspaceKindPodConfigValue): PodResourceEntry[] => { const grouped = new Map([ diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImage.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImage.tsx index ca625585..9f50e5a0 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImage.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImage.tsx @@ -2,9 +2,6 @@ import React, { useCallback, useState } from 'react'; import { Button, Content, - Dropdown, - MenuToggle, - DropdownItem, Modal, ModalHeader, ModalFooter, @@ -13,14 +10,13 @@ import { EmptyStateFooter, EmptyStateActions, EmptyStateBody, - Label, - getUniqueId, ExpandableSection, } from '@patternfly/react-core'; -import { Table, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table'; -import { PlusCircleIcon, EllipsisVIcon, CubesIcon } from '@patternfly/react-icons'; +import { PlusCircleIcon, CubesIcon } from '@patternfly/react-icons'; import { WorkspaceKindImageConfigData, WorkspaceKindImageConfigValue } from '~/app/types'; import { emptyImage } from '~/app/pages/WorkspaceKinds/Form/helpers'; +import { WorkspaceKindFormPaginatedTable } from '~/app/pages/WorkspaceKinds/Form/WorkspaceKindFormPaginatedTable'; + import { WorkspaceKindFormImageModal } from './WorkspaceKindFormImageModal'; interface WorkspaceKindFormImageProps { @@ -38,7 +34,6 @@ export const WorkspaceKindFormImage: React.FC = ({ const [defaultId, setDefaultId] = useState(imageConfig.default || ''); const [isModalOpen, setIsModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - const [dropdownOpen, setDropdownOpen] = useState(null); const [editIndex, setEditIndex] = useState(null); const [deleteIndex, setDeleteIndex] = useState(null); const [image, setImage] = useState({ ...emptyImage }); @@ -125,70 +120,17 @@ export const WorkspaceKindFormImage: React.FC = ({ )} {imageConfig.values.length > 0 && (
- - - - - - - - - - - - {imageConfig.values.map((img, index) => ( - - - - - - - - - - ))} - -
Display NameIDDefaultHiddenLabels -
{img.displayName}{img.id} - { - setDefaultId(img.id); - updateImageConfig({ ...imageConfig, default: img.id }); - }} - aria-label={`Select ${img.id} as default`} - /> - {img.hidden ? 'Yes' : 'No'} - {img.labels.length > 0 && - img.labels.map((label) => ( - - ))} - - ( - setDropdownOpen(dropdownOpen === index ? null : index)} - variant="plain" - aria-label="plain kebab" - > - - - )} - isOpen={dropdownOpen === index} - onSelect={() => setDropdownOpen(null)} - popperProps={{ position: 'right' }} - > - handleEdit(index)}>Edit - openDeleteModal(index)}>Remove - -
+ { + updateImageConfig({ ...imageConfig, default: id }); + setDefaultId(id); + }} + handleEdit={handleEdit} + openDeleteModal={openDeleteModal} + /> {addImageBtn}
)} diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageModal.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageModal.tsx index 99a7a22b..607d3aad 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageModal.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageModal.tsx @@ -138,7 +138,7 @@ export const WorkspaceKindFormImageModal: React.FC setImage({ ...image, ports })} /> {mode === 'edit' && ( diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/ResourceInputWrapper.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/ResourceInputWrapper.tsx index 3c8ab7bb..5c0371b3 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/ResourceInputWrapper.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/ResourceInputWrapper.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { FormSelect, FormSelectOption, @@ -6,13 +6,18 @@ import { Split, SplitItem, } from '@patternfly/react-core'; -import { CPU_UNITS, MEMORY_UNITS_FOR_SELECTION, UnitOption } from '~/shared/utilities/valueUnits'; +import { + CPU_UNITS, + MEMORY_UNITS_FOR_SELECTION, + TIME_UNIT_FOR_SELECTION, + UnitOption, +} from '~/shared/utilities/valueUnits'; import { parseResourceValue } from '~/shared/utilities/WorkspaceUtils'; interface ResourceInputWrapperProps { value: string; onChange: (value: string) => void; - type: 'cpu' | 'memory' | 'custom'; + type: 'cpu' | 'memory' | 'time' | 'custom'; min?: number; max?: number; step?: number; @@ -26,6 +31,7 @@ const unitMap: { } = { memory: MEMORY_UNITS_FOR_SELECTION, cpu: CPU_UNITS, + time: TIME_UNIT_FOR_SELECTION, }; const DEFAULT_STEP = 1; @@ -34,7 +40,6 @@ const DEFAULT_UNITS = { memory: 'Mi', cpu: '', }; - export const ResourceInputWrapper: React.FC = ({ value, onChange, @@ -48,22 +53,47 @@ export const ResourceInputWrapper: React.FC = ({ }) => { const [inputValue, setInputValue] = useState(value); const [unit, setUnit] = useState(''); + const isTimeInitialized = useRef(false); useEffect(() => { - if (type === 'custom') { - setInputValue(value); - return; + if (type === 'time') { + // Initialize time only once + if (!isTimeInitialized.current) { + const seconds = parseFloat(value) || 0; + let defaultUnit = 60; // Default to minutes + if (seconds >= 86400) { + defaultUnit = 86400; // Days + } else if (seconds >= 3600) { + defaultUnit = 3600; // Hours + } else if (seconds >= 60) { + defaultUnit = 60; // Minutes + } else { + defaultUnit = 1; // Seconds + } + setUnit(defaultUnit.toString()); + setInputValue((seconds / defaultUnit).toString()); + isTimeInitialized.current = true; + } + } else { + if (type === 'custom') { + setInputValue(value); + return; + } + const [numericValue, extractedUnit] = parseResourceValue(value, type); + setInputValue(String(numericValue || '')); + setUnit(extractedUnit?.unit || DEFAULT_UNITS[type]); } - const [numericValue, extractedUnit] = parseResourceValue(value, type); - setInputValue(String(numericValue || '')); - setUnit(extractedUnit?.unit || DEFAULT_UNITS[type]); - }, [value, type]); + }, [type, value]); const handleInputChange = useCallback( (newValue: string) => { setInputValue(newValue); if (type === 'custom') { onChange(newValue); + } else if (type === 'time') { + const numericValue = parseFloat(newValue) || 0; + const unitMultiplier = parseFloat(unit) || 1; + onChange(String(numericValue * unitMultiplier)); } else { onChange(newValue ? `${newValue}${unit}` : ''); } @@ -73,12 +103,24 @@ export const ResourceInputWrapper: React.FC = ({ const handleUnitChange = useCallback( (newUnit: string) => { - setUnit(newUnit); - if (inputValue) { - onChange(`${inputValue}${newUnit}`); + if (type === 'time') { + const currentValue = parseFloat(inputValue) || 0; + const oldUnitMultiplier = parseFloat(unit) || 1; + const newUnitMultiplier = parseFloat(newUnit) || 1; + // Convert the current value to the new unit + const valueInSeconds = currentValue * oldUnitMultiplier; + const valueInNewUnit = valueInSeconds / newUnitMultiplier; + setUnit(newUnit); + setInputValue(valueInNewUnit.toString()); + onChange(String(valueInSeconds)); + } else { + setUnit(newUnit); + if (inputValue) { + onChange(`${inputValue}${newUnit}`); + } } }, - [inputValue, onChange], + [inputValue, onChange, type, unit], ); const handleIncrement = useCallback(() => { @@ -104,7 +146,13 @@ export const ResourceInputWrapper: React.FC = ({ const unitOptions = useMemo( () => type !== 'custom' - ? unitMap[type].map((u) => ) + ? unitMap[type].map((u) => ( + + )) : [], [type], ); @@ -136,6 +184,7 @@ export const ResourceInputWrapper: React.FC = ({ onChange={(_, v) => handleUnitChange(v)} id={`${ariaLabel}-unit-select`} isDisabled={isDisabled} + className="workspace-kind-unit-select" > {unitOptions} diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfig.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfig.tsx index 400d1da0..5f18678f 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfig.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfig.tsx @@ -2,9 +2,6 @@ import React, { useCallback, useState } from 'react'; import { Button, Content, - Dropdown, - MenuToggle, - DropdownItem, Modal, ModalHeader, ModalFooter, @@ -14,14 +11,11 @@ import { EmptyStateActions, ExpandableSection, EmptyStateBody, - Label, - getUniqueId, } from '@patternfly/react-core'; -import { Table, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table'; -import { PlusCircleIcon, EllipsisVIcon, CubesIcon } from '@patternfly/react-icons'; +import { PlusCircleIcon, CubesIcon } from '@patternfly/react-icons'; import { emptyPodConfig } from '~/app/pages/WorkspaceKinds/Form/helpers'; import { WorkspaceKindPodConfigValue, WorkspaceKindPodConfigData } from '~/app/types'; - +import { WorkspaceKindFormPaginatedTable } from '~/app/pages/WorkspaceKinds/Form/WorkspaceKindFormPaginatedTable'; import { WorkspaceKindFormPodConfigModal } from './WorkspaceKindFormPodConfigModal'; interface WorkspaceKindFormPodConfigProps { @@ -37,7 +31,6 @@ export const WorkspaceKindFormPodConfig: React.FC(null); const [editIndex, setEditIndex] = useState(null); const [deleteIndex, setDeleteIndex] = useState(null); const [currConfig, setCurrConfig] = useState({ ...emptyPodConfig }); @@ -128,69 +121,17 @@ export const WorkspaceKindFormPodConfig: React.FC 0 && ( <> - - - - - - - - - - - - {podConfig.values.map((config, index) => ( - - - - - - - - - ))} - -
Display NameIDDefaultHiddenLabels -
{config.displayName}{config.id} - { - setDefaultId(config.id); - updatePodConfig({ ...podConfig, default: config.id }); - }} - aria-label={`Select ${config.id} as default`} - /> - {config.hidden ? 'Yes' : 'No'} - {config.labels.length > 0 && - config.labels.map((label) => ( - - ))} - - ( - setDropdownOpen(dropdownOpen === index ? null : index)} - variant="plain" - aria-label="plain kebab" - > - - - )} - isOpen={dropdownOpen === index} - onSelect={() => setDropdownOpen(null)} - popperProps={{ position: 'right' }} - > - handleEdit(index)}>Edit - openDeleteModal(index)}>Remove - -
+ { + updatePodConfig({ ...podConfig, default: id }); + setDefaultId(id); + }} + handleEdit={handleEdit} + openDeleteModal={openDeleteModal} + /> {addConfigBtn} )} diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormResource.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormResource.tsx index 8127f3c4..08106117 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormResource.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormResource.tsx @@ -312,6 +312,7 @@ export const WorkspaceKindFormResource: React.FC onChange={(_event, value) => handleChange(res.id, 'type', value)} /> +
)} - diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes.tsx index 9feb7374..7fb0b118 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes.tsx @@ -14,7 +14,7 @@ import { Switch, TextInput, } from '@patternfly/react-core'; -import { EllipsisVIcon } from '@patternfly/react-icons'; +import { EllipsisVIcon, PlusCircleIcon } from '@patternfly/react-icons'; import { Table, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import { WorkspacePodVolumeMount } from '~/shared/api/backendApiTypes'; @@ -126,9 +126,10 @@ export const WorkspaceFormPropertiesVolumes: React.FC )} - - ))} - - ); -}; +}) => ( + ({ + id: image.id, + displayName: image.displayName, + kindName: workspaceKind.name, + workspaceCountRouteState: { + imageId: image.id, + }, + workspaceCount: + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + workspaceCountPerKind[workspaceKind.name] + ? workspaceCountPerKind[workspaceKind.name].countByImage[image.id] ?? 0 + : 0, + }))} + tableKind="image" + /> +); diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsNamespaces.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsNamespaces.tsx index 7929fdef..ca6c76fb 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsNamespaces.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsNamespaces.tsx @@ -1,8 +1,7 @@ import React from 'react'; -import { Button, List, ListItem } from '@patternfly/react-core'; import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import { WorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind'; -import { useTypedNavigate } from '~/app/routerHelper'; +import { WorkspaceKindDetailsTable } from './WorkspaceKindDetailsTable'; type WorkspaceDetailsNamespacesProps = { workspaceKind: WorkspaceKind; @@ -11,42 +10,25 @@ type WorkspaceDetailsNamespacesProps = { export const WorkspaceKindDetailsNamespaces: React.FunctionComponent< WorkspaceDetailsNamespacesProps -> = ({ workspaceKind, workspaceCountPerKind }) => { - const navigate = useTypedNavigate(); - - return ( - - {Object.keys( - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - workspaceCountPerKind[workspaceKind.name] - ? workspaceCountPerKind[workspaceKind.name].countByNamespace - : [], - ).map((namespace, rowIndex) => ( - - {namespace}:{' '} - - - ))} - - ); -}; +> = ({ workspaceKind, workspaceCountPerKind }) => ( + ({ + id: String(rowIndex), + displayName: namespace, + kindName: workspaceKind.name, + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + workspaceCount: workspaceCountPerKind[workspaceKind.name] + ? workspaceCountPerKind[workspaceKind.name].countByNamespace[namespace] + : 0, + workspaceCountRouteState: { + namespace, + }, + }))} + tableKind="namespace" + /> +); diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsPodConfigs.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsPodConfigs.tsx index a461e76a..7ca13f4b 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsPodConfigs.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsPodConfigs.tsx @@ -1,8 +1,7 @@ import React from 'react'; -import { Button, List, ListItem } from '@patternfly/react-core'; import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import { WorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind'; -import { useTypedNavigate } from '~/app/routerHelper'; +import { WorkspaceKindDetailsTable } from './WorkspaceKindDetailsTable'; type WorkspaceDetailsPodConfigsProps = { workspaceKind: WorkspaceKind; @@ -11,37 +10,20 @@ type WorkspaceDetailsPodConfigsProps = { export const WorkspaceKindDetailsPodConfigs: React.FunctionComponent< WorkspaceDetailsPodConfigsProps -> = ({ workspaceKind, workspaceCountPerKind }) => { - const navigate = useTypedNavigate(); - - return ( - - {workspaceKind.podTemplate.options.podConfig.values.map((podConfig, rowIndex) => ( - - {podConfig.displayName}:{' '} - - - ))} - - ); -}; +> = ({ workspaceKind, workspaceCountPerKind }) => ( + ({ + id: podConfig.id, + displayName: podConfig.displayName, + kindName: workspaceKind.name, + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + workspaceCount: workspaceCountPerKind[workspaceKind.name] + ? workspaceCountPerKind[workspaceKind.name].countByPodConfig[podConfig.id] ?? 0 + : 0, + workspaceCountRouteState: { + podConfigId: podConfig.id, + }, + }))} + tableKind="podConfig" + /> +); diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsTable.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsTable.tsx new file mode 100644 index 00000000..12b3b597 --- /dev/null +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsTable.tsx @@ -0,0 +1,95 @@ +import React, { useMemo, useState } from 'react'; +import { Table, Thead, Tr, Td, Tbody, Th } from '@patternfly/react-table'; +import { Button, Content, Pagination, PaginationVariant } from '@patternfly/react-core'; +import { useTypedNavigate } from '~/app/routerHelper'; +import { RouteStateMap } from '~/app/routes'; + +export interface WorkspaceKindDetailsTableRow { + id: string; + displayName: string; + kindName: string; + workspaceCount: number; + workspaceCountRouteState: RouteStateMap['workspaceKindSummary']; +} + +interface WorkspaceKindDetailsTableProps { + rows: WorkspaceKindDetailsTableRow[]; + tableKind: 'image' | 'podConfig' | 'namespace'; +} + +export const WorkspaceKindDetailsTable: React.FC = ({ + rows, + tableKind, +}) => { + const navigate = useTypedNavigate(); + + const [page, setPage] = useState(1); + const [perPage, setPerPage] = useState(10); + const rowPages = useMemo(() => { + const pages = []; + for (let i = 0; i < rows.length; i += perPage) { + pages.push(rows.slice(i, i + perPage)); + } + return pages; + }, [perPage, rows]); + + const onSetPage = ( + _event: React.MouseEvent | React.KeyboardEvent | MouseEvent, + newPage: number, + ) => { + setPage(newPage); + }; + + const onPerPageSelect = ( + _event: React.MouseEvent | React.KeyboardEvent | MouseEvent, + newPerPage: number, + newPage: number, + ) => { + setPerPage(newPerPage); + setPage(newPage); + }; + return ( + + + + + + + + + + {rowPages[page - 1].map((row) => ( + + + + + ))} + +
NameWorkspaces
{row.displayName} + +
+ +
+ ); +}; From 3218768df032cc173d91933e8c8a7ab5ff5eb229 Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Mon, 21 Jul 2025 11:08:58 -0400 Subject: [PATCH 37/68] fix(ws): Implement dual scrolling for workspace kind wizard (#484) Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> fix(ws): remove extra DrawerPanelBody remove unused file Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> fix(ws): remove comment and hide drawer on previousStep callback Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> fix(ws): when navigating between wizard steps, show drawer for steps that have drawer content --- .../pages/Workspaces/Form/WorkspaceForm.tsx | 414 +++++++++++------- .../Form/image/WorkspaceFormImageDetails.tsx | 4 +- .../image/WorkspaceFormImageSelection.tsx | 59 +-- .../Form/kind/WorkspaceFormKindDetails.tsx | 4 +- .../Form/kind/WorkspaceFormKindSelection.tsx | 47 +- .../WorkspaceFormPodConfigDetails.tsx | 6 +- .../WorkspaceFormPodConfigSelection.tsx | 59 +-- 7 files changed, 301 insertions(+), 292 deletions(-) diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx index 89a29825..78d4d709 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx @@ -2,14 +2,21 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Button, Content, + Drawer, + DrawerContent, + DrawerContentBody, + DrawerPanelContent, + DrawerHead, + DrawerActions, + DrawerCloseButton, + DrawerPanelBody, Flex, FlexItem, - PageGroup, PageSection, ProgressStep, ProgressStepper, Stack, - StackItem, + Title, } from '@patternfly/react-core'; import useGenericObjectState from '~/app/hooks/useGenericObjectState'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; @@ -18,10 +25,18 @@ import { WorkspaceFormKindSelection } from '~/app/pages/Workspaces/Form/kind/Wor import { WorkspaceFormPodConfigSelection } from '~/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection'; import { WorkspaceFormPropertiesSelection } from '~/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSelection'; import { WorkspaceFormData } from '~/app/types'; -import { WorkspaceCreate } from '~/shared/api/backendApiTypes'; +import { + WorkspaceCreate, + WorkspaceKind, + WorkspaceImageConfigValue, + WorkspacePodConfigValue, +} from '~/shared/api/backendApiTypes'; import useWorkspaceFormData from '~/app/hooks/useWorkspaceFormData'; import { useTypedNavigate } from '~/app/routerHelper'; import { useWorkspaceFormLocationData } from '~/app/hooks/useWorkspaceFormLocationData'; +import { WorkspaceFormKindDetails } from '~/app/pages/Workspaces/Form/kind/WorkspaceFormKindDetails'; +import { WorkspaceFormImageDetails } from '~/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails'; +import { WorkspaceFormPodConfigDetails } from '~/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigDetails'; enum WorkspaceFormSteps { KindSelection, @@ -52,6 +67,7 @@ const WorkspaceForm: React.FC = () => { const [isSubmitting, setIsSubmitting] = useState(false); const [currentStep, setCurrentStep] = useState(WorkspaceFormSteps.KindSelection); + const [drawerExpanded, setDrawerExpanded] = useState(false); const [data, setData, resetData, replaceData] = useGenericObjectState(initialFormData); @@ -76,30 +92,46 @@ const WorkspaceForm: React.FC = () => { [currentStep], ); + const isStepValid = useCallback( + (step: WorkspaceFormSteps) => { + switch (step) { + case WorkspaceFormSteps.KindSelection: + return !!data.kind; + case WorkspaceFormSteps.ImageSelection: + return !!data.image; + case WorkspaceFormSteps.PodConfigSelection: + return !!data.podConfig; + case WorkspaceFormSteps.Properties: + return !!data.properties.workspaceName.trim(); + default: + return false; + } + }, + [data.kind, data.image, data.podConfig, data.properties.workspaceName], + ); + + const showDrawer = useCallback( + (step: WorkspaceFormSteps) => + // Only show drawer for steps that have drawer content + step !== WorkspaceFormSteps.Properties && isStepValid(step), + [isStepValid], + ); + const previousStep = useCallback(() => { - setCurrentStep(currentStep - 1); - }, [currentStep]); + const newStep = currentStep - 1; + setCurrentStep(newStep); + setDrawerExpanded(showDrawer(newStep)); + }, [currentStep, showDrawer]); const nextStep = useCallback(() => { - setCurrentStep(currentStep + 1); - }, [currentStep]); + const newStep = currentStep + 1; + setCurrentStep(newStep); + setDrawerExpanded(showDrawer(newStep)); + }, [currentStep, showDrawer]); const canGoToPreviousStep = useMemo(() => currentStep > 0, [currentStep]); - const isCurrentStepValid = useMemo(() => { - switch (currentStep) { - case WorkspaceFormSteps.KindSelection: - return !!data.kind; - case WorkspaceFormSteps.ImageSelection: - return !!data.image; - case WorkspaceFormSteps.PodConfigSelection: - return !!data.podConfig; - case WorkspaceFormSteps.Properties: - return !!data.properties.workspaceName.trim(); - default: - return false; - } - }, [currentStep, data]); + const isCurrentStepValid = useMemo(() => isStepValid(currentStep), [isStepValid, currentStep]); const canGoToNextStep = useMemo( () => currentStep < Object.keys(WorkspaceFormSteps).length / 2 - 1, @@ -168,6 +200,63 @@ const WorkspaceForm: React.FC = () => { navigate('workspaces'); }, [navigate]); + const handleKindSelect = useCallback( + (kind: WorkspaceKind | undefined) => { + if (kind) { + resetData(); + setData('kind', kind); + setDrawerExpanded(true); + } + }, + [resetData, setData], + ); + + const handleImageSelect = useCallback( + (image: WorkspaceImageConfigValue | undefined) => { + if (image) { + setData('image', image); + setDrawerExpanded(true); + } + }, + [setData], + ); + + const handlePodConfigSelect = useCallback( + (podConfig: WorkspacePodConfigValue | undefined) => { + if (podConfig) { + setData('podConfig', podConfig); + setDrawerExpanded(true); + } + }, + [setData], + ); + + const getDrawerContent = () => { + switch (currentStep) { + case WorkspaceFormSteps.KindSelection: + return ; + case WorkspaceFormSteps.ImageSelection: + return ; + case WorkspaceFormSteps.PodConfigSelection: + return ; + default: + return null; + } + }; + + const getDrawerTitle = () => { + switch (currentStep) { + case WorkspaceFormSteps.KindSelection: + return 'Workspace Kind'; + case WorkspaceFormSteps.ImageSelection: + return 'Image'; + case WorkspaceFormSteps.PodConfigSelection: + return 'Pod Config'; + default: + return ''; + } + }; + if (initialFormDataError) { return

Error loading workspace data: {initialFormDataError.message}

; // TODO: UX for error state } @@ -176,137 +265,160 @@ const WorkspaceForm: React.FC = () => { return

Loading...

; // TODO: UX for loading state } + const panelContent = ( + + + {getDrawerTitle()} + + setDrawerExpanded(false)} /> + + + + {getDrawerContent()} + + + ); + return ( - <> - - - - - - -

{`${mode === 'create' ? 'Create' : 'Edit'} workspace`}

-
-
- - - - Workspace Kind - - - Image - - - Pod Config - - - Properties - - - -
- -

{stepDescriptions[currentStep]}

-
-
-
-
- - {currentStep === WorkspaceFormSteps.KindSelection && ( - { - resetData(); - setData('kind', kind); - }} - /> - )} - {currentStep === WorkspaceFormSteps.ImageSelection && ( - setData('image', image)} - images={data.kind?.podTemplate.options.imageConfig.values ?? []} - /> - )} - {currentStep === WorkspaceFormSteps.PodConfigSelection && ( - setData('podConfig', podConfig)} - podConfigs={data.kind?.podTemplate.options.podConfig.values ?? []} - /> - )} - {currentStep === WorkspaceFormSteps.Properties && ( - setData('properties', properties)} - selectedImage={data.image} - /> - )} - - - - - - - - {canGoToNextStep ? ( - - ) : ( - - )} - - - - - - - + + + + + + + + + + +

{`${mode === 'create' ? 'Create' : 'Edit'} workspace`}

+

{stepDescriptions[currentStep]}

+
+
+ + + + Workspace Kind + + + Image + + + Pod Config + + + Properties + + + +
+
+
+
+ + + {currentStep === WorkspaceFormSteps.KindSelection && ( + + )} + {currentStep === WorkspaceFormSteps.ImageSelection && ( + + )} + {currentStep === WorkspaceFormSteps.PodConfigSelection && ( + + )} + {currentStep === WorkspaceFormSteps.Properties && ( + setData('properties', properties)} + selectedImage={data.image} + /> + )} + + + + + + + + + + {canGoToNextStep ? ( + + ) : ( + + )} + + + + + + + +
+
+
+
); }; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails.tsx index 26221492..1b6eb14d 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails.tsx @@ -16,7 +16,7 @@ type WorkspaceFormImageDetailsProps = { export const WorkspaceFormImageDetails: React.FunctionComponent = ({ workspaceImage, }) => ( -
+ <> {workspaceImage && ( <> {workspaceImage.displayName} @@ -38,5 +38,5 @@ export const WorkspaceFormImageDetails: React.FunctionComponent )} -
+ ); diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx index 83ac839a..02fc9556 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx @@ -1,10 +1,8 @@ -import React, { useCallback, useMemo, useRef, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { Content, Split, SplitItem } from '@patternfly/react-core'; -import { WorkspaceFormImageDetails } from '~/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails'; import { WorkspaceFormImageList } from '~/app/pages/Workspaces/Form/image/WorkspaceFormImageList'; import { FilterByLabels } from '~/app/pages/Workspaces/Form/labelFilter/FilterByLabels'; import { WorkspaceImageConfigValue } from '~/shared/api/backendApiTypes'; -import { WorkspaceFormDrawer } from '~/app/pages/Workspaces/Form/WorkspaceFormDrawer'; interface WorkspaceFormImageSelectionProps { images: WorkspaceImageConfigValue[]; @@ -18,26 +16,6 @@ const WorkspaceFormImageSelection: React.FunctionComponent { const [selectedLabels, setSelectedLabels] = useState>>(new Map()); - const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); - - const onExpand = useCallback(() => { - if (drawerRef.current) { - drawerRef.current.focus(); - } - }, []); - - const onClick = useCallback( - (image?: WorkspaceImageConfigValue) => { - setIsExpanded(true); - onSelect(image); - }, - [onSelect], - ); - - const onCloseClick = useCallback(() => { - setIsExpanded(false); - }, []); const imageFilterContent = useMemo( () => ( @@ -50,32 +28,19 @@ const WorkspaceFormImageSelection: React.FunctionComponent , - [selectedImage], - ); - return ( - - - {imageFilterContent} - - - - - + + {imageFilterContent} + + + + ); }; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindDetails.tsx index 6edbf7b0..105a4890 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindDetails.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindDetails.tsx @@ -9,12 +9,12 @@ type WorkspaceFormKindDetailsProps = { export const WorkspaceFormKindDetails: React.FunctionComponent = ({ workspaceKind, }) => ( -
+ <> {workspaceKind && ( <> {workspaceKind.displayName}

{workspaceKind.description}

)} -
+ ); diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx index 342a4825..17bf4dd8 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx @@ -1,10 +1,8 @@ -import React, { useCallback, useMemo, useRef, useState } from 'react'; +import React from 'react'; import { Content } from '@patternfly/react-core'; import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds'; -import { WorkspaceFormKindDetails } from '~/app/pages/Workspaces/Form/kind/WorkspaceFormKindDetails'; import { WorkspaceFormKindList } from '~/app/pages/Workspaces/Form/kind/WorkspaceFormKindList'; -import { WorkspaceFormDrawer } from '~/app/pages/Workspaces/Form/WorkspaceFormDrawer'; interface WorkspaceFormKindSelectionProps { selectedKind: WorkspaceKind | undefined; @@ -16,31 +14,6 @@ const WorkspaceFormKindSelection: React.FunctionComponent { const [workspaceKinds, loaded, error] = useWorkspaceKinds(); - const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); - - const onExpand = useCallback(() => { - if (drawerRef.current) { - drawerRef.current.focus(); - } - }, []); - - const onClick = useCallback( - (kind?: WorkspaceKind) => { - setIsExpanded(true); - onSelect(kind); - }, - [onSelect], - ); - - const onCloseClick = useCallback(() => { - setIsExpanded(false); - }, []); - - const kindDetailsContent = useMemo( - () => , - [selectedKind], - ); if (error) { return

Error loading workspace kinds: {error.message}

; // TODO: UX for error state @@ -52,19 +25,11 @@ const WorkspaceFormKindSelection: React.FunctionComponent - - - + ); }; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigDetails.tsx index dee1dd20..bf7f3fb5 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigDetails.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigDetails.tsx @@ -19,10 +19,12 @@ export const WorkspaceFormPodConfigDetails: React.FunctionComponent< > = ({ workspacePodConfig }) => ( <> {workspacePodConfig && ( -
+ <> {workspacePodConfig.displayName}{' '}

{workspacePodConfig.description}

+
+
{workspacePodConfig.labels.map((label) => ( ))} -
+ )} ); diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx index d3c04744..2aac1cff 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx @@ -1,9 +1,7 @@ -import React, { useCallback, useMemo, useRef, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { Content, Split, SplitItem } from '@patternfly/react-core'; -import { WorkspaceFormPodConfigDetails } from '~/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigDetails'; import { WorkspaceFormPodConfigList } from '~/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList'; import { FilterByLabels } from '~/app/pages/Workspaces/Form/labelFilter/FilterByLabels'; -import { WorkspaceFormDrawer } from '~/app/pages/Workspaces/Form/WorkspaceFormDrawer'; import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes'; interface WorkspaceFormPodConfigSelectionProps { @@ -16,26 +14,6 @@ const WorkspaceFormPodConfigSelection: React.FunctionComponent< WorkspaceFormPodConfigSelectionProps > = ({ podConfigs, selectedPodConfig, onSelect }) => { const [selectedLabels, setSelectedLabels] = useState>>(new Map()); - const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); - - const onExpand = useCallback(() => { - if (drawerRef.current) { - drawerRef.current.focus(); - } - }, []); - - const onClick = useCallback( - (podConfig?: WorkspacePodConfigValue) => { - setIsExpanded(true); - onSelect(podConfig); - }, - [onSelect], - ); - - const onCloseClick = useCallback(() => { - setIsExpanded(false); - }, []); const podConfigFilterContent = useMemo( () => ( @@ -48,32 +26,19 @@ const WorkspaceFormPodConfigSelection: React.FunctionComponent< [podConfigs, selectedLabels, setSelectedLabels], ); - const podConfigDetailsContent = useMemo( - () => , - [selectedPodConfig], - ); - return ( - - - {podConfigFilterContent} - - - - - + + {podConfigFilterContent} + + + + ); }; From 296f63f3ca5e91a93a1be43a9b1223f1337b9972 Mon Sep 17 00:00:00 2001 From: Paulo Rego <832830+paulovmr@users.noreply.github.com> Date: Mon, 21 Jul 2025 16:26:58 -0300 Subject: [PATCH 38/68] chore(ws): enforce component specific imports (#475) Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> --- workspaces/frontend/.eslintrc.js | 22 ++++++++++++ workspaces/frontend/src/app/App.tsx | 13 ++++--- .../src/app/EnsureAPIAvailability.tsx | 3 +- workspaces/frontend/src/app/NavSidebar.tsx | 7 ++-- .../frontend/src/app/components/LoadError.tsx | 3 +- .../src/app/components/LoadingSpinner.tsx | 3 +- .../app/components/ThemeAwareSearchInput.tsx | 6 +++- .../app/components/ValidationErrorAlert.tsx | 3 +- .../src/app/components/WorkspaceTable.tsx | 32 ++++++++--------- .../app/context/WorkspaceActionsContext.tsx | 6 +++- .../frontend/src/app/error/ErrorBoundary.tsx | 6 ++-- .../frontend/src/app/error/ErrorDetails.tsx | 6 ++-- .../frontend/src/app/error/UpdateState.tsx | 12 +++---- .../src/app/hooks/useWorkspaceRowActions.ts | 2 +- .../frontend/src/app/pages/Debug/Debug.tsx | 10 +++--- .../WorkspaceKinds/Form/EditableLabels.tsx | 11 +++--- .../WorkspaceKinds/Form/WorkspaceKindForm.tsx | 21 ++++-------- .../Form/WorkspaceKindFormPaginatedTable.tsx | 19 +++++------ .../fileUpload/WorkspaceKindFileUpload.tsx | 11 +++--- .../Form/image/WorkspaceKindFormImage.tsx | 16 +++++---- .../image/WorkspaceKindFormImageModal.tsx | 15 ++++---- .../Form/image/WorkspaceKindFormImagePort.tsx | 9 +++-- .../image/WorkspaceKindFormImageRedirect.tsx | 8 +++-- .../Form/podConfig/ResourceInputWrapper.tsx | 7 ++-- .../podConfig/WorkspaceKindFormPodConfig.tsx | 15 ++++---- .../WorkspaceKindFormPodConfigModal.tsx | 13 ++++--- .../podConfig/WorkspaceKindFormResource.tsx | 19 +++++------ .../WorkspaceKindFormPodTemplate.tsx | 11 +++--- .../WorkspaceKindFormProperties.tsx | 15 ++++---- .../pages/WorkspaceKinds/WorkspaceKinds.tsx | 32 +++++++++-------- .../details/WorkspaceKindDetails.tsx | 8 +++-- .../details/WorkspaceKindDetailsOverview.tsx | 4 +-- .../details/WorkspaceKindDetailsTable.tsx | 9 +++-- .../summary/WorkspaceKindSummary.tsx | 14 +++----- .../WorkspaceKindSummaryExpandableCard.tsx | 19 +++++------ .../app/pages/Workspaces/DataVolumesList.tsx | 17 +++++----- .../Workspaces/Details/WorkspaceDetails.tsx | 8 +++-- .../Details/WorkspaceDetailsActions.tsx | 9 +++-- .../Details/WorkspaceDetailsActivity.tsx | 6 ++-- .../Details/WorkspaceDetailsOverview.tsx | 4 +-- .../pages/Workspaces/ExpandedWorkspaceRow.tsx | 2 +- .../pages/Workspaces/Form/WorkspaceForm.tsx | 33 +++++++++--------- .../Workspaces/Form/WorkspaceFormDrawer.tsx | 4 +-- .../Form/image/WorkspaceFormImageDetails.tsx | 4 +-- .../Form/image/WorkspaceFormImageList.tsx | 9 +++-- .../image/WorkspaceFormImageSelection.tsx | 3 +- .../Form/kind/WorkspaceFormKindDetails.tsx | 2 +- .../Form/kind/WorkspaceFormKindList.tsx | 9 +++-- .../Form/kind/WorkspaceFormKindSelection.tsx | 2 +- .../WorkspaceFormPodConfigDetails.tsx | 6 ++-- .../podConfig/WorkspaceFormPodConfigList.tsx | 9 +++-- .../WorkspaceFormPodConfigSelection.tsx | 3 +- .../WorkspaceFormPropertiesSecrets.tsx | 34 +++++++++++-------- .../WorkspaceFormPropertiesSelection.tsx | 18 ++++------ .../WorkspaceFormPropertiesVolumes.tsx | 29 ++++++++++------ .../Workspaces/WorkspaceConfigDetails.tsx | 2 +- .../Workspaces/WorkspaceConnectAction.tsx | 4 ++- .../Workspaces/WorkspacePackageDetails.tsx | 5 ++- .../app/pages/Workspaces/WorkspaceStorage.tsx | 2 +- .../src/app/pages/Workspaces/Workspaces.tsx | 4 ++- .../WorkspaceRedirectInformationView.tsx | 13 +++---- .../WorkspaceRestartActionModal.tsx | 8 ++--- .../WorkspaceStartActionModal.tsx | 6 ++-- .../WorkspaceStopActionModal.tsx | 8 ++--- .../src/app/pages/notFound/NotFound.tsx | 8 ++--- .../src/shared/components/ActionButton.tsx | 2 +- .../shared/components/CustomEmptyState.tsx | 6 ++-- .../src/shared/components/DeleteModal.tsx | 14 ++++---- .../frontend/src/shared/components/Filter.tsx | 12 ++++--- .../src/shared/components/ImageFallback.tsx | 6 ++-- .../shared/components/NamespaceSelector.tsx | 17 ++++------ .../src/shared/components/WithValidImage.tsx | 2 +- 72 files changed, 394 insertions(+), 346 deletions(-) diff --git a/workspaces/frontend/.eslintrc.js b/workspaces/frontend/.eslintrc.js index 29f4f8bb..b44290b0 100644 --- a/workspaces/frontend/.eslintrc.js +++ b/workspaces/frontend/.eslintrc.js @@ -208,6 +208,28 @@ module.exports = { name: 'react-router', message: 'Use react-router-dom instead.', }, + { + name: '@patternfly/react-core', + message: + 'Use specific component imports: @patternfly/react-core/dist/esm/components/ComponentName', + }, + { + name: '@patternfly/react-table', + message: + 'Use specific component imports: @patternfly/react-table/dist/esm/components/ComponentName', + }, + { + name: '@patternfly/react-icons', + message: 'Use specific icon imports: @patternfly/react-icons/dist/esm/icons/IconName', + }, + { + name: 'date-fns', + message: 'Use specific function imports: date-fns/functionName', + }, + { + name: 'lodash', + message: 'Use specific function imports: lodash/functionName', + }, ], }, ], diff --git a/workspaces/frontend/src/app/App.tsx b/workspaces/frontend/src/app/App.tsx index 3ff011f6..239beb13 100644 --- a/workspaces/frontend/src/app/App.tsx +++ b/workspaces/frontend/src/app/App.tsx @@ -2,20 +2,19 @@ import React, { useEffect } from 'react'; import '@patternfly/patternfly/patternfly-addons.css'; import '@patternfly/react-core/dist/styles/base.css'; import './app.css'; +import { Brand } from '@patternfly/react-core/dist/esm/components/Brand'; +import { Flex } from '@patternfly/react-core/dist/esm/layouts/Flex'; import { - Brand, - Flex, Masthead, MastheadBrand, MastheadContent, MastheadLogo, MastheadMain, MastheadToggle, - Page, - PageToggleButton, - Title, -} from '@patternfly/react-core'; -import { BarsIcon } from '@patternfly/react-icons'; +} from '@patternfly/react-core/dist/esm/components/Masthead'; +import { Page, PageToggleButton } from '@patternfly/react-core/dist/esm/components/Page'; +import { Title } from '@patternfly/react-core/dist/esm/components/Title'; +import { BarsIcon } from '@patternfly/react-icons/dist/esm/icons/bars-icon'; import ErrorBoundary from '~/app/error/ErrorBoundary'; import NamespaceSelector from '~/shared/components/NamespaceSelector'; import logoDarkTheme from '~/images/logo-dark-theme.svg'; diff --git a/workspaces/frontend/src/app/EnsureAPIAvailability.tsx b/workspaces/frontend/src/app/EnsureAPIAvailability.tsx index 9fc3c24a..53d16a1e 100644 --- a/workspaces/frontend/src/app/EnsureAPIAvailability.tsx +++ b/workspaces/frontend/src/app/EnsureAPIAvailability.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { Bullseye, Spinner } from '@patternfly/react-core'; +import { Bullseye } from '@patternfly/react-core/dist/esm/layouts/Bullseye'; +import { Spinner } from '@patternfly/react-core/dist/esm/components/Spinner'; import { useNotebookAPI } from './hooks/useNotebookAPI'; interface EnsureAPIAvailabilityProps { diff --git a/workspaces/frontend/src/app/NavSidebar.tsx b/workspaces/frontend/src/app/NavSidebar.tsx index 0bd4ac81..bad77bd8 100644 --- a/workspaces/frontend/src/app/NavSidebar.tsx +++ b/workspaces/frontend/src/app/NavSidebar.tsx @@ -1,14 +1,13 @@ import React, { useState } from 'react'; import { NavLink } from 'react-router-dom'; +import { Brand } from '@patternfly/react-core/dist/esm/components/Brand'; import { - Brand, Nav, NavExpandable, NavItem, NavList, - PageSidebar, - PageSidebarBody, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/Nav'; +import { PageSidebar, PageSidebarBody } from '@patternfly/react-core/dist/esm/components/Page'; import { useTypedLocation } from '~/app/routerHelper'; import { useNavData, isNavDataGroup, NavDataHref, NavDataGroup } from './AppRoutes'; import { isMUITheme, LOGO_LIGHT } from './const'; diff --git a/workspaces/frontend/src/app/components/LoadError.tsx b/workspaces/frontend/src/app/components/LoadError.tsx index 60114e99..2ca06b98 100644 --- a/workspaces/frontend/src/app/components/LoadError.tsx +++ b/workspaces/frontend/src/app/components/LoadError.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; -import { Alert, Bullseye } from '@patternfly/react-core'; +import { Alert } from '@patternfly/react-core/dist/esm/components/Alert'; +import { Bullseye } from '@patternfly/react-core/dist/esm/layouts/Bullseye'; interface LoadErrorProps { error: Error; diff --git a/workspaces/frontend/src/app/components/LoadingSpinner.tsx b/workspaces/frontend/src/app/components/LoadingSpinner.tsx index 9750a392..945bf967 100644 --- a/workspaces/frontend/src/app/components/LoadingSpinner.tsx +++ b/workspaces/frontend/src/app/components/LoadingSpinner.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; -import { Bullseye, Spinner } from '@patternfly/react-core'; +import { Bullseye } from '@patternfly/react-core/dist/esm/layouts/Bullseye'; +import { Spinner } from '@patternfly/react-core/dist/esm/components/Spinner'; // TODO: simple LoadingSpinner component -- we should improve this later diff --git a/workspaces/frontend/src/app/components/ThemeAwareSearchInput.tsx b/workspaces/frontend/src/app/components/ThemeAwareSearchInput.tsx index 967e2d9f..ce82341a 100644 --- a/workspaces/frontend/src/app/components/ThemeAwareSearchInput.tsx +++ b/workspaces/frontend/src/app/components/ThemeAwareSearchInput.tsx @@ -1,5 +1,9 @@ import React from 'react'; -import { SearchInput, SearchInputProps, TextInput } from '@patternfly/react-core'; +import { + SearchInput, + SearchInputProps, +} from '@patternfly/react-core/dist/esm/components/SearchInput'; +import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput'; import FormFieldset from 'app/components/FormFieldset'; import { isMUITheme } from 'app/const'; diff --git a/workspaces/frontend/src/app/components/ValidationErrorAlert.tsx b/workspaces/frontend/src/app/components/ValidationErrorAlert.tsx index 417969b9..44390859 100644 --- a/workspaces/frontend/src/app/components/ValidationErrorAlert.tsx +++ b/workspaces/frontend/src/app/components/ValidationErrorAlert.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { Alert, List, ListItem } from '@patternfly/react-core'; +import { Alert } from '@patternfly/react-core/dist/esm/components/Alert'; +import { List, ListItem } from '@patternfly/react-core/dist/esm/components/List'; import { ValidationError } from '~/shared/api/backendApiTypes'; import { ErrorEnvelopeException } from '~/shared/api/apiUtils'; diff --git a/workspaces/frontend/src/app/components/WorkspaceTable.tsx b/workspaces/frontend/src/app/components/WorkspaceTable.tsx index 6254ff1c..184e8408 100644 --- a/workspaces/frontend/src/app/components/WorkspaceTable.tsx +++ b/workspaces/frontend/src/app/components/WorkspaceTable.tsx @@ -1,17 +1,19 @@ import React, { useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react'; +import { PageSection } from '@patternfly/react-core/dist/esm/components/Page'; import { - PageSection, TimestampTooltipVariant, Timestamp, - Label, +} from '@patternfly/react-core/dist/esm/components/Timestamp'; +import { Label } from '@patternfly/react-core/dist/esm/components/Label'; +import { PaginationVariant, Pagination, - Content, - Tooltip, - Bullseye, - Button, - Icon, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/Pagination'; +import { Content } from '@patternfly/react-core/dist/esm/components/Content'; +import { Tooltip } from '@patternfly/react-core/dist/esm/components/Tooltip'; +import { Bullseye } from '@patternfly/react-core/dist/esm/layouts/Bullseye'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; +import { Icon } from '@patternfly/react-core/dist/esm/components/Icon'; import { Table, Thead, @@ -22,14 +24,12 @@ import { ThProps, ActionsColumn, IActions, -} from '@patternfly/react-table'; -import { - InfoCircleIcon, - ExclamationTriangleIcon, - TimesCircleIcon, - QuestionCircleIcon, -} from '@patternfly/react-icons'; -import { formatDistanceToNow } from 'date-fns'; +} from '@patternfly/react-table/dist/esm/components/Table'; +import { InfoCircleIcon } from '@patternfly/react-icons/dist/esm/icons/info-circle-icon'; +import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; +import { TimesCircleIcon } from '@patternfly/react-icons/dist/esm/icons/times-circle-icon'; +import { QuestionCircleIcon } from '@patternfly/react-icons/dist/esm/icons/question-circle-icon'; +import { formatDistanceToNow } from 'date-fns/formatDistanceToNow'; import { Workspace, WorkspaceState } from '~/shared/api/backendApiTypes'; import { DataFieldKey, diff --git a/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx b/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx index 7a01737e..d9a70d54 100644 --- a/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx +++ b/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx @@ -1,5 +1,9 @@ import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; -import { Drawer, DrawerContent, DrawerContentBody } from '@patternfly/react-core'; +import { + Drawer, + DrawerContent, + DrawerContentBody, +} from '@patternfly/react-core/dist/esm/components/Drawer'; import { useNamespaceContext } from '~/app/context/NamespaceContextProvider'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { WorkspaceDetails } from '~/app/pages/Workspaces/Details/WorkspaceDetails'; diff --git a/workspaces/frontend/src/app/error/ErrorBoundary.tsx b/workspaces/frontend/src/app/error/ErrorBoundary.tsx index 29d8830c..e397abb9 100644 --- a/workspaces/frontend/src/app/error/ErrorBoundary.tsx +++ b/workspaces/frontend/src/app/error/ErrorBoundary.tsx @@ -1,7 +1,9 @@ import React from 'react'; import { Link } from 'react-router-dom'; -import { Button, Split, SplitItem, Title } from '@patternfly/react-core'; -import { TimesIcon } from '@patternfly/react-icons'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; +import { Split, SplitItem } from '@patternfly/react-core/dist/esm/layouts/Split'; +import { Title } from '@patternfly/react-core/dist/esm/components/Title'; +import { TimesIcon } from '@patternfly/react-icons/dist/esm/icons/times-icon'; import { AppRoutePaths } from '~/app/routes'; import ErrorDetails from '~/app/error/ErrorDetails'; import UpdateState from '~/app/error/UpdateState'; diff --git a/workspaces/frontend/src/app/error/ErrorDetails.tsx b/workspaces/frontend/src/app/error/ErrorDetails.tsx index 61ac57a5..551c3767 100644 --- a/workspaces/frontend/src/app/error/ErrorDetails.tsx +++ b/workspaces/frontend/src/app/error/ErrorDetails.tsx @@ -2,12 +2,14 @@ import React from 'react'; import { ClipboardCopy, ClipboardCopyVariant, +} from '@patternfly/react-core/dist/esm/components/ClipboardCopy'; +import { DescriptionList, DescriptionListDescription, DescriptionListGroup, DescriptionListTerm, - Title, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/DescriptionList'; +import { Title } from '@patternfly/react-core/dist/esm/components/Title'; type ErrorDetailsProps = { title: string; diff --git a/workspaces/frontend/src/app/error/UpdateState.tsx b/workspaces/frontend/src/app/error/UpdateState.tsx index ff19100b..d3cee49c 100644 --- a/workspaces/frontend/src/app/error/UpdateState.tsx +++ b/workspaces/frontend/src/app/error/UpdateState.tsx @@ -1,14 +1,14 @@ import React from 'react'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; import { - Button, EmptyState, - EmptyStateActions, EmptyStateBody, - EmptyStateFooter, EmptyStateVariant, - PageSection, -} from '@patternfly/react-core'; -import { PathMissingIcon } from '@patternfly/react-icons'; + EmptyStateActions, + EmptyStateFooter, +} from '@patternfly/react-core/dist/esm/components/EmptyState'; +import { PageSection } from '@patternfly/react-core/dist/esm/components/Page'; +import { PathMissingIcon } from '@patternfly/react-icons/dist/esm/icons/path-missing-icon'; type Props = { onClose: () => void; diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceRowActions.ts b/workspaces/frontend/src/app/hooks/useWorkspaceRowActions.ts index 06190656..4787107e 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaceRowActions.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaceRowActions.ts @@ -1,5 +1,5 @@ import { useCallback } from 'react'; -import { IActions } from '@patternfly/react-table'; +import { IActions } from '@patternfly/react-table/dist/esm/components/Table'; import { Workspace } from '~/shared/api/backendApiTypes'; import { useWorkspaceActionsContext, WorkspaceAction } from '~/app/context/WorkspaceActionsContext'; diff --git a/workspaces/frontend/src/app/pages/Debug/Debug.tsx b/workspaces/frontend/src/app/pages/Debug/Debug.tsx index 5c648316..3c59c3c7 100644 --- a/workspaces/frontend/src/app/pages/Debug/Debug.tsx +++ b/workspaces/frontend/src/app/pages/Debug/Debug.tsx @@ -1,13 +1,13 @@ import React from 'react'; -import { CubesIcon } from '@patternfly/react-icons'; +import { CubesIcon } from '@patternfly/react-icons/dist/esm/icons/cubes-icon'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; import { - Button, EmptyState, EmptyStateBody, - EmptyStateFooter, EmptyStateVariant, - PageSection, -} from '@patternfly/react-core'; + EmptyStateFooter, +} from '@patternfly/react-core/dist/esm/components/EmptyState'; +import { PageSection } from '@patternfly/react-core/dist/esm/components/Page'; const Debug: React.FunctionComponent = () => ( diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/EditableLabels.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/EditableLabels.tsx index 98b1d4df..a9858f9c 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/EditableLabels.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/EditableLabels.tsx @@ -1,15 +1,16 @@ /* eslint-disable @typescript-eslint/no-unused-expressions */ import React, { useRef } from 'react'; -import { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table'; +import { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table/dist/esm/components/Table'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; import { - Button, FormFieldGroupExpandable, FormFieldGroupHeader, - TextInput, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/Form'; +import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput'; import inlineEditStyles from '@patternfly/react-styles/css/components/InlineEdit/inline-edit'; import { css } from '@patternfly/react-styles'; -import { PlusCircleIcon, TrashAltIcon } from '@patternfly/react-icons'; +import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; +import { TrashAltIcon } from '@patternfly/react-icons/dist/esm/icons/trash-alt-icon'; import { WorkspaceOptionLabel } from '~/shared/api/backendApiTypes'; interface EditableRowInterface { diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx index d99bdb8e..c17a78c2 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx @@ -1,19 +1,12 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { - Button, - Content, - ContentVariants, - EmptyState, - EmptyStateBody, - Flex, - FlexItem, - PageGroup, - PageSection, - Stack, - StackItem, -} from '@patternfly/react-core'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; +import { Content, ContentVariants } from '@patternfly/react-core/dist/esm/components/Content'; +import { Flex, FlexItem } from '@patternfly/react-core/dist/esm/layouts/Flex'; +import { PageGroup, PageSection } from '@patternfly/react-core/dist/esm/components/Page'; +import { Stack, StackItem } from '@patternfly/react-core/dist/esm/layouts/Stack'; import { t_global_spacer_sm as SmallPadding } from '@patternfly/react-tokens'; -import { ExclamationCircleIcon } from '@patternfly/react-icons'; +import { ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; +import { EmptyState, EmptyStateBody } from '@patternfly/react-core/dist/esm/components/EmptyState'; import { ValidationErrorAlert } from '~/app/components/ValidationErrorAlert'; import useWorkspaceKindByName from '~/app/hooks/useWorkspaceKindByName'; import { WorkspaceKind, ValidationError } from '~/shared/api/backendApiTypes'; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindFormPaginatedTable.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindFormPaginatedTable.tsx index d7887f2f..c503c446 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindFormPaginatedTable.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindFormPaginatedTable.tsx @@ -1,17 +1,16 @@ import React, { useMemo, useState } from 'react'; -import { Table, Thead, Tr, Td, Tbody, Th } from '@patternfly/react-table'; +import { Table, Thead, Tr, Td, Tbody, Th } from '@patternfly/react-table/dist/esm/components/Table'; +import { getUniqueId } from '@patternfly/react-core/helpers'; +import { Label } from '@patternfly/react-core/dist/esm/components/Label'; +import { MenuToggle } from '@patternfly/react-core/dist/esm/components/MenuToggle'; +import { PageSection } from '@patternfly/react-core/dist/esm/components/Page'; import { - Dropdown, - DropdownItem, - getUniqueId, - Label, - MenuToggle, - PageSection, Pagination, PaginationVariant, - Radio, -} from '@patternfly/react-core'; -import { EllipsisVIcon } from '@patternfly/react-icons'; +} from '@patternfly/react-core/dist/esm/components/Pagination'; +import { Radio } from '@patternfly/react-core/dist/esm/components/Radio'; +import { Dropdown, DropdownItem } from '@patternfly/react-core/dist/esm/components/Dropdown'; +import { EllipsisVIcon } from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; import { WorkspaceKindImageConfigValue } from '~/app/types'; import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes'; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/fileUpload/WorkspaceKindFileUpload.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/fileUpload/WorkspaceKindFileUpload.tsx index bbd78ee2..bd0b5d1d 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/fileUpload/WorkspaceKindFileUpload.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/fileUpload/WorkspaceKindFileUpload.tsx @@ -2,13 +2,12 @@ import React, { useCallback, useState } from 'react'; import yaml, { YAMLException } from 'js-yaml'; import { FileUpload, - DropEvent, - FileUploadHelperText, - HelperText, - HelperTextItem, - Content, DropzoneErrorCode, -} from '@patternfly/react-core'; + FileUploadHelperText, +} from '@patternfly/react-core/dist/esm/components/FileUpload'; +import { DropEvent } from '@patternfly/react-core/dist/esm/helpers/typeUtils'; +import { HelperText, HelperTextItem } from '@patternfly/react-core/dist/esm/components/HelperText'; +import { Content } from '@patternfly/react-core/dist/esm/components/Content'; import { isValidWorkspaceKindYaml } from '~/app/pages/WorkspaceKinds/Form/helpers'; import { ValidationStatus } from '~/app/pages/WorkspaceKinds/Form/WorkspaceKindForm'; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImage.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImage.tsx index 9f50e5a0..05c4982f 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImage.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImage.tsx @@ -1,22 +1,24 @@ import React, { useCallback, useState } from 'react'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; +import { Content } from '@patternfly/react-core/dist/esm/components/Content'; import { - Button, - Content, Modal, ModalHeader, ModalFooter, ModalVariant, - EmptyState, +} from '@patternfly/react-core/dist/esm/components/Modal'; +import { EmptyStateFooter, EmptyStateActions, + EmptyState, EmptyStateBody, - ExpandableSection, -} from '@patternfly/react-core'; -import { PlusCircleIcon, CubesIcon } from '@patternfly/react-icons'; +} from '@patternfly/react-core/dist/esm/components/EmptyState'; +import { ExpandableSection } from '@patternfly/react-core/dist/esm/components/ExpandableSection'; +import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; +import { CubesIcon } from '@patternfly/react-icons/dist/esm/icons/cubes-icon'; import { WorkspaceKindImageConfigData, WorkspaceKindImageConfigValue } from '~/app/types'; import { emptyImage } from '~/app/pages/WorkspaceKinds/Form/helpers'; import { WorkspaceKindFormPaginatedTable } from '~/app/pages/WorkspaceKinds/Form/WorkspaceKindFormPaginatedTable'; - import { WorkspaceKindFormImageModal } from './WorkspaceKindFormImageModal'; interface WorkspaceKindFormImageProps { diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageModal.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageModal.tsx index 607d3aad..284b0fdb 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageModal.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageModal.tsx @@ -4,15 +4,16 @@ import { ModalHeader, ModalBody, ModalFooter, - Button, - Form, - FormGroup, - TextInput, +} from '@patternfly/react-core/dist/esm/components/Modal'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; +import { Form, FormGroup } from '@patternfly/react-core/dist/esm/components/Form'; +import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput'; +import { FormSelect, FormSelectOption, - Switch, - HelperText, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/FormSelect'; +import { Switch } from '@patternfly/react-core/dist/esm/components/Switch'; +import { HelperText } from '@patternfly/react-core/dist/esm/components/HelperText'; import { WorkspaceKindImageConfigValue, ImagePullPolicy } from '~/app/types'; import { EditableLabels } from '~/app/pages/WorkspaceKinds/Form/EditableLabels'; import { emptyImage } from '~/app/pages/WorkspaceKinds/Form/helpers'; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImagePort.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImagePort.tsx index 0b542fb7..2f810000 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImagePort.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImagePort.tsx @@ -1,12 +1,11 @@ import React from 'react'; import { + FormGroup, FormFieldGroupExpandable, FormFieldGroupHeader, - FormGroup, - Grid, - GridItem, - TextInput, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/Form'; +import { Grid, GridItem } from '@patternfly/react-core/dist/esm/layouts/Grid'; +import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput'; import { WorkspaceKindImagePort } from '~/app/types'; interface WorkspaceKindFormImagePortProps { diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageRedirect.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageRedirect.tsx index 1f456b7f..f250624f 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageRedirect.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageRedirect.tsx @@ -1,12 +1,14 @@ import React from 'react'; import { + FormGroup, FormFieldGroupExpandable, FormFieldGroupHeader, - FormGroup, +} from '@patternfly/react-core/dist/esm/components/Form'; +import { FormSelect, FormSelectOption, - TextInput, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/FormSelect'; +import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput'; import { WorkspaceOptionRedirect, WorkspaceRedirectMessageLevel, diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/ResourceInputWrapper.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/ResourceInputWrapper.tsx index 5c0371b3..715cb39b 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/ResourceInputWrapper.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/ResourceInputWrapper.tsx @@ -2,10 +2,9 @@ import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react' import { FormSelect, FormSelectOption, - NumberInput, - Split, - SplitItem, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/FormSelect'; +import { NumberInput } from '@patternfly/react-core/dist/esm/components/NumberInput'; +import { Split, SplitItem } from '@patternfly/react-core/dist/esm/layouts/Split'; import { CPU_UNITS, MEMORY_UNITS_FOR_SELECTION, diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfig.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfig.tsx index 5f18678f..8e70a3d9 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfig.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfig.tsx @@ -1,18 +1,21 @@ import React, { useCallback, useState } from 'react'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; +import { Content } from '@patternfly/react-core/dist/esm/components/Content'; import { - Button, - Content, Modal, ModalHeader, ModalFooter, ModalVariant, +} from '@patternfly/react-core/dist/esm/components/Modal'; +import { EmptyState, + EmptyStateBody, EmptyStateFooter, EmptyStateActions, - ExpandableSection, - EmptyStateBody, -} from '@patternfly/react-core'; -import { PlusCircleIcon, CubesIcon } from '@patternfly/react-icons'; +} from '@patternfly/react-core/dist/esm/components/EmptyState'; +import { ExpandableSection } from '@patternfly/react-core/dist/esm/components/ExpandableSection'; +import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; +import { CubesIcon } from '@patternfly/react-icons/dist/esm/icons/cubes-icon'; import { emptyPodConfig } from '~/app/pages/WorkspaceKinds/Form/helpers'; import { WorkspaceKindPodConfigValue, WorkspaceKindPodConfigData } from '~/app/types'; import { WorkspaceKindFormPaginatedTable } from '~/app/pages/WorkspaceKinds/Form/WorkspaceKindFormPaginatedTable'; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfigModal.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfigModal.tsx index 4d7ca683..cd979a5c 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfigModal.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormPodConfigModal.tsx @@ -4,13 +4,12 @@ import { ModalHeader, ModalBody, ModalFooter, - Button, - Form, - FormGroup, - TextInput, - Switch, - HelperText, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/Modal'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; +import { Form, FormGroup } from '@patternfly/react-core/dist/esm/components/Form'; +import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput'; +import { Switch } from '@patternfly/react-core/dist/esm/components/Switch'; +import { HelperText } from '@patternfly/react-core/dist/esm/components/HelperText'; import { WorkspaceKindPodConfigValue } from '~/app/types'; import { WorkspaceOptionLabel } from '~/shared/api/backendApiTypes'; import { EditableLabels } from '~/app/pages/WorkspaceKinds/Form/EditableLabels'; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormResource.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormResource.tsx index 08106117..b4f224ba 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormResource.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podConfig/WorkspaceKindFormResource.tsx @@ -1,17 +1,16 @@ import React, { useCallback, useEffect, useState, useMemo } from 'react'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; +import { Grid, GridItem } from '@patternfly/react-core/dist/esm/layouts/Grid'; +import { Title } from '@patternfly/react-core/dist/esm/components/Title'; import { - Button, - Grid, - GridItem, - Title, FormFieldGroupExpandable, FormFieldGroupHeader, - TextInput, - Checkbox, - HelperText, - HelperTextItem, -} from '@patternfly/react-core'; -import { PlusCircleIcon, TrashAltIcon } from '@patternfly/react-icons'; +} from '@patternfly/react-core/dist/esm/components/Form'; +import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput'; +import { Checkbox } from '@patternfly/react-core/dist/esm/components/Checkbox'; +import { HelperText, HelperTextItem } from '@patternfly/react-core/dist/esm/components/HelperText'; +import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; +import { TrashAltIcon } from '@patternfly/react-icons/dist/esm/icons/trash-alt-icon'; import { generateUniqueId } from '~/app/pages/WorkspaceKinds/Form/helpers'; import { isMemoryLimitLarger } from '~/shared/utilities/valueUnits'; import { ResourceInputWrapper } from './ResourceInputWrapper'; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podTemplate/WorkspaceKindFormPodTemplate.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podTemplate/WorkspaceKindFormPodTemplate.tsx index a0307077..a6c69821 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podTemplate/WorkspaceKindFormPodTemplate.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podTemplate/WorkspaceKindFormPodTemplate.tsx @@ -1,14 +1,13 @@ +import React, { useCallback, useState } from 'react'; import { - ExpandableSection, Form, FormFieldGroup, FormFieldGroupHeader, FormGroup, - HelperText, - HelperTextItem, - Switch, -} from '@patternfly/react-core'; -import React, { useCallback, useState } from 'react'; +} from '@patternfly/react-core/dist/esm/components/Form'; +import { ExpandableSection } from '@patternfly/react-core/dist/esm/components/ExpandableSection'; +import { HelperText, HelperTextItem } from '@patternfly/react-core/dist/esm/components/HelperText'; +import { Switch } from '@patternfly/react-core/dist/esm/components/Switch'; import { WorkspaceKindPodTemplateData } from '~/app/types'; import { EditableLabels } from '~/app/pages/WorkspaceKinds/Form/EditableLabels'; import { WorkspacePodVolumeMount } from '~/shared/api/backendApiTypes'; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/properties/WorkspaceKindFormProperties.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/properties/WorkspaceKindFormProperties.tsx index 6d3c9b93..652156a3 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/properties/WorkspaceKindFormProperties.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/properties/WorkspaceKindFormProperties.tsx @@ -1,13 +1,10 @@ import React, { useState } from 'react'; -import { - Content, - ExpandableSection, - Form, - FormGroup, - HelperText, - Switch, - TextInput, -} from '@patternfly/react-core'; +import { Content } from '@patternfly/react-core/dist/esm/components/Content'; +import { ExpandableSection } from '@patternfly/react-core/dist/esm/components/ExpandableSection'; +import { Form, FormGroup } from '@patternfly/react-core/dist/esm/components/Form'; +import { HelperText } from '@patternfly/react-core/dist/esm/components/HelperText'; +import { Switch } from '@patternfly/react-core/dist/esm/components/Switch'; +import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput'; import { WorkspaceKindProperties } from '~/app/types'; interface WorkspaceKindFormPropertiesProps { diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx index 56026914..daea4356 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx @@ -3,25 +3,29 @@ import { Drawer, DrawerContent, DrawerContentBody, - PageSection, - Content, - Tooltip, - Label, +} from '@patternfly/react-core/dist/esm/components/Drawer'; +import { PageSection } from '@patternfly/react-core/dist/esm/components/Page'; +import { Content } from '@patternfly/react-core/dist/esm/components/Content'; +import { Tooltip } from '@patternfly/react-core/dist/esm/components/Tooltip'; +import { Label } from '@patternfly/react-core/dist/esm/components/Label'; +import { Toolbar, ToolbarContent, ToolbarItem, + ToolbarGroup, + ToolbarFilter, + ToolbarToggleGroup, +} from '@patternfly/react-core/dist/esm/components/Toolbar'; +import { Menu, MenuContent, MenuList, MenuItem, - MenuToggle, - Popper, - ToolbarGroup, - ToolbarFilter, - ToolbarToggleGroup, - Bullseye, - Button, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/Menu'; +import { MenuToggle } from '@patternfly/react-core/dist/esm/components/MenuToggle'; +import { Popper } from '@patternfly/react-core/helpers'; +import { Bullseye } from '@patternfly/react-core/dist/esm/layouts/Bullseye'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; import { Table, Thead, @@ -32,8 +36,8 @@ import { ThProps, ActionsColumn, IActions, -} from '@patternfly/react-table'; -import { FilterIcon } from '@patternfly/react-icons'; +} from '@patternfly/react-table/dist/esm/components/Table'; +import { FilterIcon } from '@patternfly/react-icons/dist/esm/icons/filter-icon'; import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds'; import { useWorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind'; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx index 7a73090f..ebaf0531 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx @@ -3,15 +3,17 @@ import { DrawerActions, DrawerCloseButton, DrawerHead, - DrawerPanelBody, DrawerPanelContent, + DrawerPanelBody, +} from '@patternfly/react-core/dist/esm/components/Drawer'; +import { Tabs, Tab, TabTitleText, - Title, TabContentBody, TabContent, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/Tabs'; +import { Title } from '@patternfly/react-core/dist/esm/components/Title'; import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import { WorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind'; import { WorkspaceKindDetailsNamespaces } from '~/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsNamespaces'; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsOverview.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsOverview.tsx index 02cca47c..d5aa3e8f 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsOverview.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsOverview.tsx @@ -4,8 +4,8 @@ import { DescriptionListTerm, DescriptionListGroup, DescriptionListDescription, - Divider, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/DescriptionList'; +import { Divider } from '@patternfly/react-core/dist/esm/components/Divider'; import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import ImageFallback from '~/shared/components/ImageFallback'; import WithValidImage from '~/shared/components/WithValidImage'; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsTable.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsTable.tsx index 12b3b597..5ee7bc16 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsTable.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsTable.tsx @@ -1,6 +1,11 @@ import React, { useMemo, useState } from 'react'; -import { Table, Thead, Tr, Td, Tbody, Th } from '@patternfly/react-table'; -import { Button, Content, Pagination, PaginationVariant } from '@patternfly/react-core'; +import { Table, Thead, Tr, Td, Tbody, Th } from '@patternfly/react-table/dist/esm/components/Table'; +import { + Pagination, + PaginationVariant, +} from '@patternfly/react-core/dist/esm/components/Pagination'; +import { Content } from '@patternfly/react-core/dist/esm/components/Content'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; import { useTypedNavigate } from '~/app/routerHelper'; import { RouteStateMap } from '~/app/routes'; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummary.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummary.tsx index f09d1102..8a6e02dc 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummary.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummary.tsx @@ -1,13 +1,9 @@ import React, { useCallback, useRef, useState } from 'react'; -import { - Button, - Content, - ContentVariants, - PageSection, - Stack, - StackItem, -} from '@patternfly/react-core'; -import { ArrowLeftIcon } from '@patternfly/react-icons'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; +import { Content, ContentVariants } from '@patternfly/react-core/dist/esm/components/Content'; +import { PageSection } from '@patternfly/react-core/dist/esm/components/Page'; +import { Stack, StackItem } from '@patternfly/react-core/dist/esm/layouts/Stack'; +import { ArrowLeftIcon } from '@patternfly/react-icons/dist/esm/icons/arrow-left-icon'; import { useTypedLocation, useTypedNavigate, useTypedParams } from '~/app/routerHelper'; import WorkspaceTable, { WorkspaceTableFilteredColumn, diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard.tsx index 4aa85d6c..a1c3bee8 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard.tsx @@ -1,20 +1,17 @@ import React, { useMemo } from 'react'; +import { Bullseye } from '@patternfly/react-core/dist/esm/layouts/Bullseye'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; import { - Bullseye, - Button, Card, CardBody, - CardExpandableContent, CardHeader, CardTitle, - Content, - ContentVariants, - Divider, - Flex, - FlexItem, - Stack, - StackItem, -} from '@patternfly/react-core'; + CardExpandableContent, +} from '@patternfly/react-core/dist/esm/components/Card'; +import { Content, ContentVariants } from '@patternfly/react-core/dist/esm/components/Content'; +import { Divider } from '@patternfly/react-core/dist/esm/components/Divider'; +import { Flex, FlexItem } from '@patternfly/react-core/dist/esm/layouts/Flex'; +import { Stack, StackItem } from '@patternfly/react-core/dist/esm/layouts/Stack'; import { t_global_spacer_md as MediumPadding, t_global_font_size_4xl as LargeFontSize, diff --git a/workspaces/frontend/src/app/pages/Workspaces/DataVolumesList.tsx b/workspaces/frontend/src/app/pages/Workspaces/DataVolumesList.tsx index 9c2a87bf..d509e078 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/DataVolumesList.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/DataVolumesList.tsx @@ -2,18 +2,19 @@ import React from 'react'; import { ClipboardCopy, ClipboardCopyVariant, - Content, +} from '@patternfly/react-core/dist/esm/components/ClipboardCopy'; +import { Content } from '@patternfly/react-core/dist/esm/components/Content'; +import { DescriptionList, DescriptionListGroup, DescriptionListTerm, DescriptionListDescription, - Flex, - FlexItem, - List, - ListItem, - Tooltip, -} from '@patternfly/react-core'; -import { DatabaseIcon, LockedIcon } from '@patternfly/react-icons'; +} from '@patternfly/react-core/dist/esm/components/DescriptionList'; +import { Flex, FlexItem } from '@patternfly/react-core/dist/esm/layouts/Flex'; +import { List, ListItem } from '@patternfly/react-core/dist/esm/components/List'; +import { Tooltip } from '@patternfly/react-core/dist/esm/components/Tooltip'; +import { DatabaseIcon } from '@patternfly/react-icons/dist/esm/icons/database-icon'; +import { LockedIcon } from '@patternfly/react-icons/dist/esm/icons/locked-icon'; import { Workspace } from '~/shared/api/backendApiTypes'; interface DataVolumesListProps { diff --git a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetails.tsx index 0a5f6052..226291c2 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetails.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetails.tsx @@ -3,15 +3,17 @@ import { DrawerActions, DrawerCloseButton, DrawerHead, - DrawerPanelBody, DrawerPanelContent, + DrawerPanelBody, +} from '@patternfly/react-core/dist/esm/components/Drawer'; +import { Tabs, Tab, TabTitleText, - Title, TabContentBody, TabContent, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/Tabs'; +import { Title } from '@patternfly/react-core/dist/esm/components/Title'; import { Workspace } from '~/shared/api/backendApiTypes'; import { WorkspaceDetailsOverview } from '~/app/pages/Workspaces/Details/WorkspaceDetailsOverview'; import { WorkspaceDetailsActions } from '~/app/pages/Workspaces/Details/WorkspaceDetailsActions'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsActions.tsx b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsActions.tsx index 6b67b349..1c770dca 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsActions.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsActions.tsx @@ -1,12 +1,11 @@ import React, { useState } from 'react'; import { Dropdown, - DropdownList, - MenuToggle, DropdownItem, - Flex, - FlexItem, -} from '@patternfly/react-core'; + DropdownList, +} from '@patternfly/react-core/dist/esm/components/Dropdown'; +import { MenuToggle } from '@patternfly/react-core/dist/esm/components/MenuToggle'; +import { Flex, FlexItem } from '@patternfly/react-core/dist/esm/layouts/Flex'; interface WorkspaceDetailsActionsProps { // TODO: Uncomment when edit action is fully supported diff --git a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsActivity.tsx b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsActivity.tsx index befa43dc..30601dce 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsActivity.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsActivity.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import { format } from 'date-fns'; +import { format } from 'date-fns/format'; import { DescriptionList, DescriptionListTerm, DescriptionListGroup, DescriptionListDescription, - Divider, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/DescriptionList'; +import { Divider } from '@patternfly/react-core/dist/esm/components/Divider'; import { Workspace } from '~/shared/api/backendApiTypes'; const DATE_FORMAT = 'PPpp'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsOverview.tsx b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsOverview.tsx index 6b00b383..55f5ae92 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsOverview.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsOverview.tsx @@ -4,8 +4,8 @@ import { DescriptionListTerm, DescriptionListGroup, DescriptionListDescription, - Divider, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/DescriptionList'; +import { Divider } from '@patternfly/react-core/dist/esm/components/Divider'; import { Workspace } from '~/shared/api/backendApiTypes'; type WorkspaceDetailsOverviewProps = { diff --git a/workspaces/frontend/src/app/pages/Workspaces/ExpandedWorkspaceRow.tsx b/workspaces/frontend/src/app/pages/Workspaces/ExpandedWorkspaceRow.tsx index bb122c4f..38c1b02a 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/ExpandedWorkspaceRow.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/ExpandedWorkspaceRow.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Tr, Td, ExpandableRowContent } from '@patternfly/react-table'; +import { Tr, Td, ExpandableRowContent } from '@patternfly/react-table/dist/esm/components/Table'; import { Workspace } from '~/shared/api/backendApiTypes'; import { WorkspaceTableColumnKeys } from '~/app/components/WorkspaceTable'; import { WorkspaceStorage } from './WorkspaceStorage'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx index 78d4d709..ae39ca00 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx @@ -1,23 +1,24 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; +import { Content } from '@patternfly/react-core/dist/esm/components/Content'; +import { Flex, FlexItem } from '@patternfly/react-core/dist/esm/layouts/Flex'; +import { PageSection } from '@patternfly/react-core/dist/esm/components/Page'; import { - Button, - Content, - Drawer, - DrawerContent, - DrawerContentBody, - DrawerPanelContent, - DrawerHead, - DrawerActions, - DrawerCloseButton, - DrawerPanelBody, - Flex, - FlexItem, - PageSection, ProgressStep, ProgressStepper, - Stack, - Title, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/ProgressStepper'; +import { Stack } from '@patternfly/react-core/dist/esm/layouts/Stack'; +import { + Drawer, + DrawerActions, + DrawerCloseButton, + DrawerContent, + DrawerContentBody, + DrawerHead, + DrawerPanelBody, + DrawerPanelContent, +} from '@patternfly/react-core/dist/esm/components/Drawer'; +import { Title } from '@patternfly/react-core/dist/esm/components/Title'; import useGenericObjectState from '~/app/hooks/useGenericObjectState'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { WorkspaceFormImageSelection } from '~/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceFormDrawer.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceFormDrawer.tsx index 8338e981..46c58c62 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceFormDrawer.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceFormDrawer.tsx @@ -7,8 +7,8 @@ import { DrawerHead, DrawerActions, DrawerCloseButton, - Title, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/Drawer'; +import { Title } from '@patternfly/react-core/dist/esm/components/Title'; interface WorkspaceFormDrawerProps { children: React.ReactNode; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails.tsx index 1b6eb14d..d47a85d2 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails.tsx @@ -4,8 +4,8 @@ import { DescriptionListTerm, DescriptionListGroup, DescriptionListDescription, - Title, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/DescriptionList'; +import { Title } from '@patternfly/react-core/dist/esm/components/Title'; import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes'; import { formatLabelKey } from '~/shared/utilities/WorkspaceUtils'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageList.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageList.tsx index bce20293..08e39205 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageList.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageList.tsx @@ -1,14 +1,13 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; import { CardTitle, - Gallery, - PageSection, - Toolbar, - ToolbarContent, Card, CardHeader, CardBody, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/Card'; +import { Gallery } from '@patternfly/react-core/dist/esm/layouts/Gallery'; +import { PageSection } from '@patternfly/react-core/dist/esm/components/Page'; +import { Toolbar, ToolbarContent } from '@patternfly/react-core/dist/esm/components/Toolbar'; import Filter, { FilteredColumn, FilterRef } from '~/shared/components/Filter'; import { WorkspaceImageConfigValue } from '~/shared/api/backendApiTypes'; import CustomEmptyState from '~/shared/components/CustomEmptyState'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx index 02fc9556..aaf807c9 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx @@ -1,5 +1,6 @@ import React, { useMemo, useState } from 'react'; -import { Content, Split, SplitItem } from '@patternfly/react-core'; +import { Content } from '@patternfly/react-core/dist/esm/components/Content'; +import { Split, SplitItem } from '@patternfly/react-core/dist/esm/layouts/Split'; import { WorkspaceFormImageList } from '~/app/pages/Workspaces/Form/image/WorkspaceFormImageList'; import { FilterByLabels } from '~/app/pages/Workspaces/Form/labelFilter/FilterByLabels'; import { WorkspaceImageConfigValue } from '~/shared/api/backendApiTypes'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindDetails.tsx index 105a4890..2363bf97 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindDetails.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindDetails.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Title } from '@patternfly/react-core'; +import { Title } from '@patternfly/react-core/dist/esm/components/Title'; import { WorkspaceKind } from '~/shared/api/backendApiTypes'; type WorkspaceFormKindDetailsProps = { diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx index 30240e62..8530bcab 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx @@ -2,13 +2,12 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; import { CardBody, CardTitle, - Gallery, - PageSection, - Toolbar, - ToolbarContent, Card, CardHeader, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/Card'; +import { Gallery } from '@patternfly/react-core/dist/esm/layouts/Gallery'; +import { PageSection } from '@patternfly/react-core/dist/esm/components/Page'; +import { Toolbar, ToolbarContent } from '@patternfly/react-core/dist/esm/components/Toolbar'; import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import Filter, { FilteredColumn, FilterRef } from '~/shared/components/Filter'; import CustomEmptyState from '~/shared/components/CustomEmptyState'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx index 17bf4dd8..9903abaf 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Content } from '@patternfly/react-core'; +import { Content } from '@patternfly/react-core/dist/esm/components/Content'; import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds'; import { WorkspaceFormKindList } from '~/app/pages/Workspaces/Form/kind/WorkspaceFormKindList'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigDetails.tsx index bf7f3fb5..d3a9ba8e 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigDetails.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigDetails.tsx @@ -4,9 +4,9 @@ import { DescriptionListTerm, DescriptionListGroup, DescriptionListDescription, - Title, - Divider, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/DescriptionList'; +import { Title } from '@patternfly/react-core/dist/esm/components/Title'; +import { Divider } from '@patternfly/react-core/dist/esm/components/Divider'; import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes'; import { formatLabelKey } from '~/shared/utilities/WorkspaceUtils'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList.tsx index 810ac3dd..06b9aa8b 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList.tsx @@ -1,14 +1,13 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; import { CardTitle, - Gallery, - PageSection, - Toolbar, - ToolbarContent, Card, CardHeader, CardBody, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/Card'; +import { Gallery } from '@patternfly/react-core/dist/esm/layouts/Gallery'; +import { PageSection } from '@patternfly/react-core/dist/esm/components/Page'; +import { Toolbar, ToolbarContent } from '@patternfly/react-core/dist/esm/components/Toolbar'; import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes'; import Filter, { FilteredColumn, FilterRef } from '~/shared/components/Filter'; import CustomEmptyState from '~/shared/components/CustomEmptyState'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx index 2aac1cff..dfe4b843 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx @@ -1,5 +1,6 @@ import React, { useMemo, useState } from 'react'; -import { Content, Split, SplitItem } from '@patternfly/react-core'; +import { Content } from '@patternfly/react-core/dist/esm/components/Content'; +import { Split, SplitItem } from '@patternfly/react-core/dist/esm/layouts/Split'; import { WorkspaceFormPodConfigList } from '~/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList'; import { FilterByLabels } from '~/app/pages/Workspaces/Form/labelFilter/FilterByLabels'; import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSecrets.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSecrets.tsx index 4f667bdb..96f1db95 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSecrets.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSecrets.tsx @@ -1,23 +1,29 @@ import React, { useCallback, useState } from 'react'; -import { EllipsisVIcon, PlusCircleIcon } from '@patternfly/react-icons'; -import { Table, Thead, Tbody, Tr, Th, Td, TableVariant } from '@patternfly/react-table'; +import { EllipsisVIcon } from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; +import { + Table, + Thead, + Tbody, + Tr, + Th, + Td, + TableVariant, +} from '@patternfly/react-table/dist/esm/components/Table'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; import { - Button, Modal, - ModalVariant, - TextInput, - Dropdown, - DropdownItem, - MenuToggle, ModalBody, ModalFooter, - Form, - FormGroup, ModalHeader, - ValidatedOptions, - HelperText, - HelperTextItem, -} from '@patternfly/react-core'; + ModalVariant, +} from '@patternfly/react-core/dist/esm/components/Modal'; +import { ValidatedOptions } from '@patternfly/react-core/helpers'; +import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput'; +import { Dropdown, DropdownItem } from '@patternfly/react-core/dist/esm/components/Dropdown'; +import { MenuToggle } from '@patternfly/react-core/dist/esm/components/MenuToggle'; +import { Form, FormGroup } from '@patternfly/react-core/dist/esm/components/Form'; +import { HelperText, HelperTextItem } from '@patternfly/react-core/dist/esm/components/HelperText'; +import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; import { WorkspacePodSecretMount } from '~/shared/api/backendApiTypes'; interface WorkspaceFormPropertiesSecretsProps { diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSelection.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSelection.tsx index fcc03a1b..d3c64b83 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSelection.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSelection.tsx @@ -1,15 +1,11 @@ import React, { useMemo, useState } from 'react'; -import { - Checkbox, - Content, - Divider, - ExpandableSection, - Form, - FormGroup, - Split, - SplitItem, - TextInput, -} from '@patternfly/react-core'; +import { Checkbox } from '@patternfly/react-core/dist/esm/components/Checkbox'; +import { Content } from '@patternfly/react-core/dist/esm/components/Content'; +import { Divider } from '@patternfly/react-core/dist/esm/components/Divider'; +import { ExpandableSection } from '@patternfly/react-core/dist/esm/components/ExpandableSection'; +import { Form, FormGroup } from '@patternfly/react-core/dist/esm/components/Form'; +import { Split, SplitItem } from '@patternfly/react-core/dist/esm/layouts/Split'; +import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput'; import { WorkspaceFormImageDetails } from '~/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails'; import { WorkspaceFormPropertiesVolumes } from '~/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes'; import { WorkspaceFormProperties } from '~/app/types'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes.tsx index 7fb0b118..780e4ca7 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes.tsx @@ -1,21 +1,28 @@ import React, { useCallback, useState } from 'react'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; +import { Dropdown, DropdownItem } from '@patternfly/react-core/dist/esm/components/Dropdown'; +import { Form, FormGroup } from '@patternfly/react-core/dist/esm/components/Form'; +import { MenuToggle } from '@patternfly/react-core/dist/esm/components/MenuToggle'; import { - Button, - Dropdown, - DropdownItem, - Form, - FormGroup, - MenuToggle, Modal, ModalBody, ModalFooter, ModalHeader, ModalVariant, - Switch, - TextInput, -} from '@patternfly/react-core'; -import { EllipsisVIcon, PlusCircleIcon } from '@patternfly/react-icons'; -import { Table, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; +} from '@patternfly/react-core/dist/esm/components/Modal'; +import { Switch } from '@patternfly/react-core/dist/esm/components/Switch'; +import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput'; +import { EllipsisVIcon } from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; +import { + Table, + TableVariant, + Tbody, + Td, + Th, + Thead, + Tr, +} from '@patternfly/react-table/dist/esm/components/Table'; +import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; import { WorkspacePodVolumeMount } from '~/shared/api/backendApiTypes'; interface WorkspaceFormPropertiesVolumesProps { diff --git a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConfigDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConfigDetails.tsx index b59873e2..1270943c 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConfigDetails.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConfigDetails.tsx @@ -4,7 +4,7 @@ import { DescriptionListTerm, DescriptionListGroup, DescriptionListDescription, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/DescriptionList'; import { Workspace } from '~/shared/api/backendApiTypes'; import { formatResourceFromWorkspace } from '~/shared/utilities/WorkspaceUtils'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConnectAction.tsx b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConnectAction.tsx index efc59fe7..4b231354 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConnectAction.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConnectAction.tsx @@ -3,10 +3,12 @@ import { Dropdown, DropdownItem, DropdownList, +} from '@patternfly/react-core/dist/esm/components/Dropdown'; +import { MenuToggle, MenuToggleElement, MenuToggleAction, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/MenuToggle'; import { Workspace, WorkspaceState } from '~/shared/api/backendApiTypes'; type WorkspaceConnectActionProps = { diff --git a/workspaces/frontend/src/app/pages/Workspaces/WorkspacePackageDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/WorkspacePackageDetails.tsx index 1eb96fee..48e187b8 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/WorkspacePackageDetails.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/WorkspacePackageDetails.tsx @@ -3,10 +3,9 @@ import { DescriptionList, DescriptionListTerm, DescriptionListDescription, - ListItem, - List, DescriptionListGroup, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/DescriptionList'; +import { ListItem, List } from '@patternfly/react-core/dist/esm/components/List'; import { Workspace } from '~/shared/api/backendApiTypes'; import { extractPackageLabels, formatLabelKey } from '~/shared/utilities/WorkspaceUtils'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceStorage.tsx b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceStorage.tsx index 9109bdf0..9f55940a 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceStorage.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceStorage.tsx @@ -4,7 +4,7 @@ import { DescriptionListTerm, DescriptionListGroup, DescriptionListDescription, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/DescriptionList'; import { Workspace } from '~/shared/api/backendApiTypes'; import { DataVolumesList } from '~/app/pages/Workspaces/DataVolumesList'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx b/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx index cf611bfe..e7749751 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; -import { Content, ContentVariants, PageSection, Stack, StackItem } from '@patternfly/react-core'; +import { Content, ContentVariants } from '@patternfly/react-core/dist/esm/components/Content'; +import { PageSection } from '@patternfly/react-core/dist/esm/components/Page'; +import { Stack, StackItem } from '@patternfly/react-core/dist/esm/layouts/Stack'; import WorkspaceTable from '~/app/components/WorkspaceTable'; import { useNamespaceContext } from '~/app/context/NamespaceContextProvider'; import { useWorkspacesByNamespace } from '~/app/hooks/useWorkspaces'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView.tsx b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView.tsx index a50f6d24..e3086a2f 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView.tsx @@ -1,10 +1,11 @@ import React, { useEffect, useState } from 'react'; -import { ExpandableSection, Icon, Tab, Tabs, TabTitleText, Content } from '@patternfly/react-core'; -import { - ExclamationCircleIcon, - ExclamationTriangleIcon, - InfoCircleIcon, -} from '@patternfly/react-icons'; +import { ExpandableSection } from '@patternfly/react-core/dist/esm/components/ExpandableSection'; +import { Icon } from '@patternfly/react-core/dist/esm/components/Icon'; +import { Tab, Tabs, TabTitleText } from '@patternfly/react-core/dist/esm/components/Tabs'; +import { Content } from '@patternfly/react-core/dist/esm/components/Content'; +import { ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; +import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; +import { InfoCircleIcon } from '@patternfly/react-icons/dist/esm/icons/info-circle-icon'; import useWorkspaceKindByName from '~/app/hooks/useWorkspaceKindByName'; import { WorkspaceKind } from '~/shared/api/backendApiTypes'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRestartActionModal.tsx b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRestartActionModal.tsx index 507d9fc6..d2eccc40 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRestartActionModal.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRestartActionModal.tsx @@ -1,13 +1,13 @@ import React from 'react'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; +import { Content } from '@patternfly/react-core/dist/esm/components/Content'; import { - Button, - Content, Modal, ModalBody, ModalFooter, ModalHeader, - TabTitleText, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/Modal'; +import { TabTitleText } from '@patternfly/react-core/dist/esm/components/Tabs'; import { Workspace } from '~/shared/api/backendApiTypes'; import { WorkspaceRedirectInformationView } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal.tsx b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal.tsx index d25d8c47..bbd0c532 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal.tsx @@ -1,12 +1,12 @@ import React, { useCallback, useState } from 'react'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; import { - Button, Modal, ModalBody, ModalFooter, ModalHeader, - TabTitleText, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/Modal'; +import { TabTitleText } from '@patternfly/react-core/dist/esm/components/Tabs'; import { WorkspaceRedirectInformationView } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView'; import { Workspace, WorkspacePauseState } from '~/shared/api/backendApiTypes'; import { ActionButton } from '~/shared/components/ActionButton'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal.tsx b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal.tsx index 12edb5a9..3375b6dd 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal.tsx @@ -1,13 +1,13 @@ import React, { useCallback, useState } from 'react'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; +import { Content } from '@patternfly/react-core/dist/esm/components/Content'; import { - Button, - Content, Modal, ModalBody, ModalFooter, ModalHeader, - TabTitleText, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/Modal'; +import { TabTitleText } from '@patternfly/react-core/dist/esm/components/Tabs'; import { WorkspaceRedirectInformationView } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView'; import { Workspace, WorkspacePauseState } from '~/shared/api/backendApiTypes'; import { ActionButton } from '~/shared/components/ActionButton'; diff --git a/workspaces/frontend/src/app/pages/notFound/NotFound.tsx b/workspaces/frontend/src/app/pages/notFound/NotFound.tsx index 9e376102..60f61be7 100644 --- a/workspaces/frontend/src/app/pages/notFound/NotFound.tsx +++ b/workspaces/frontend/src/app/pages/notFound/NotFound.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import { ExclamationTriangleIcon } from '@patternfly/react-icons'; +import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; import { - Button, EmptyState, EmptyStateBody, EmptyStateFooter, - PageSection, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/EmptyState'; +import { PageSection } from '@patternfly/react-core/dist/esm/components/Page'; import { useTypedNavigate } from '~/app/routerHelper'; const NotFound: React.FunctionComponent = () => { diff --git a/workspaces/frontend/src/shared/components/ActionButton.tsx b/workspaces/frontend/src/shared/components/ActionButton.tsx index b740a561..ea813c92 100644 --- a/workspaces/frontend/src/shared/components/ActionButton.tsx +++ b/workspaces/frontend/src/shared/components/ActionButton.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useState } from 'react'; -import { Button } from '@patternfly/react-core'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; type ActionButtonProps = { action: string; diff --git a/workspaces/frontend/src/shared/components/CustomEmptyState.tsx b/workspaces/frontend/src/shared/components/CustomEmptyState.tsx index e1c365b6..ef5bf2fb 100644 --- a/workspaces/frontend/src/shared/components/CustomEmptyState.tsx +++ b/workspaces/frontend/src/shared/components/CustomEmptyState.tsx @@ -4,9 +4,9 @@ import { EmptyStateBody, EmptyStateFooter, EmptyStateActions, - Button, -} from '@patternfly/react-core'; -import { SearchIcon } from '@patternfly/react-icons'; +} from '@patternfly/react-core/dist/esm/components/EmptyState'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; +import { SearchIcon } from '@patternfly/react-icons/dist/esm/icons/search-icon'; interface CustomEmptyStateProps { onClearFilters: () => void; diff --git a/workspaces/frontend/src/shared/components/DeleteModal.tsx b/workspaces/frontend/src/shared/components/DeleteModal.tsx index 1ac13067..f5d8efd9 100644 --- a/workspaces/frontend/src/shared/components/DeleteModal.tsx +++ b/workspaces/frontend/src/shared/components/DeleteModal.tsx @@ -5,14 +5,12 @@ import { ModalFooter, ModalHeader, ModalVariant, - Button, - TextInput, - Stack, - StackItem, - FlexItem, - HelperText, - HelperTextItem, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/Modal'; +import { Button } from '@patternfly/react-core/dist/esm/components/Button'; +import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput'; +import { Stack, StackItem } from '@patternfly/react-core/dist/esm/layouts/Stack'; +import { FlexItem } from '@patternfly/react-core/dist/esm/layouts/Flex'; +import { HelperText, HelperTextItem } from '@patternfly/react-core/dist/esm/components/HelperText'; import { default as ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; import { ActionButton } from '~/shared/components/ActionButton'; diff --git a/workspaces/frontend/src/shared/components/Filter.tsx b/workspaces/frontend/src/shared/components/Filter.tsx index 5e788211..a2c59570 100644 --- a/workspaces/frontend/src/shared/components/Filter.tsx +++ b/workspaces/frontend/src/shared/components/Filter.tsx @@ -11,17 +11,21 @@ import { MenuContent, MenuItem, MenuList, +} from '@patternfly/react-core/dist/esm/components/Menu'; +import { MenuToggle, MenuToggleElement, - Popper, +} from '@patternfly/react-core/dist/esm/components/MenuToggle'; +import { Popper } from '@patternfly/react-core/helpers'; +import { Toolbar, ToolbarContent, - ToolbarFilter, ToolbarGroup, ToolbarItem, + ToolbarFilter, ToolbarToggleGroup, -} from '@patternfly/react-core'; -import { FilterIcon } from '@patternfly/react-icons'; +} from '@patternfly/react-core/dist/esm/components/Toolbar'; +import { FilterIcon } from '@patternfly/react-icons/dist/esm/icons/filter-icon'; import ThemeAwareSearchInput from '~/app/components/ThemeAwareSearchInput'; export interface FilterProps { diff --git a/workspaces/frontend/src/shared/components/ImageFallback.tsx b/workspaces/frontend/src/shared/components/ImageFallback.tsx index 2002cfa9..dae90fd8 100644 --- a/workspaces/frontend/src/shared/components/ImageFallback.tsx +++ b/workspaces/frontend/src/shared/components/ImageFallback.tsx @@ -1,6 +1,8 @@ import React from 'react'; -import { ExclamationCircleIcon } from '@patternfly/react-icons'; -import { Content, ContentVariants, Flex, FlexItem, Tooltip } from '@patternfly/react-core'; +import { ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; +import { Content, ContentVariants } from '@patternfly/react-core/dist/esm/components/Content'; +import { Flex, FlexItem } from '@patternfly/react-core/dist/esm/layouts/Flex'; +import { Tooltip } from '@patternfly/react-core/dist/esm/components/Tooltip'; type ImageFallbackProps = { extended?: boolean; diff --git a/workspaces/frontend/src/shared/components/NamespaceSelector.tsx b/workspaces/frontend/src/shared/components/NamespaceSelector.tsx index 039c27ba..0b6dce7f 100644 --- a/workspaces/frontend/src/shared/components/NamespaceSelector.tsx +++ b/workspaces/frontend/src/shared/components/NamespaceSelector.tsx @@ -2,18 +2,15 @@ import React, { FC, useEffect, useMemo, useState } from 'react'; import { Dropdown, DropdownItem, - MenuToggle, DropdownList, DropdownProps, - MenuSearch, - MenuSearchInput, - InputGroup, - InputGroupItem, - SearchInput, - Button, - ButtonVariant, - Divider, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/dist/esm/components/Dropdown'; +import { MenuToggle } from '@patternfly/react-core/dist/esm/components/MenuToggle'; +import { MenuSearch, MenuSearchInput } from '@patternfly/react-core/dist/esm/components/Menu'; +import { InputGroup, InputGroupItem } from '@patternfly/react-core/dist/esm/components/InputGroup'; +import { SearchInput } from '@patternfly/react-core/dist/esm/components/SearchInput'; +import { Button, ButtonVariant } from '@patternfly/react-core/dist/esm/components/Button'; +import { Divider } from '@patternfly/react-core/dist/esm/components/Divider'; import { SearchIcon } from '@patternfly/react-icons/dist/esm/icons/search-icon'; import { useNamespaceContext } from '~/app/context/NamespaceContextProvider'; diff --git a/workspaces/frontend/src/shared/components/WithValidImage.tsx b/workspaces/frontend/src/shared/components/WithValidImage.tsx index 3743b53b..f4f72f3d 100644 --- a/workspaces/frontend/src/shared/components/WithValidImage.tsx +++ b/workspaces/frontend/src/shared/components/WithValidImage.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { Skeleton, SkeletonProps } from '@patternfly/react-core'; +import { Skeleton, SkeletonProps } from '@patternfly/react-core/dist/esm/components/Skeleton'; type WithValidImageProps = { imageSrc: string | undefined | null; From b21cf69174d22be227535a7a6fde8ee98de83033 Mon Sep 17 00:00:00 2001 From: Paulo Rego <832830+paulovmr@users.noreply.github.com> Date: Wed, 23 Jul 2025 08:15:00 -0300 Subject: [PATCH 39/68] chore(ws): Upgrade vulnerable packages (#495) Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> --- workspaces/frontend/package-lock.json | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/workspaces/frontend/package-lock.json b/workspaces/frontend/package-lock.json index 2d7b91dc..3e2c72b9 100644 --- a/workspaces/frontend/package-lock.json +++ b/workspaces/frontend/package-lock.json @@ -10372,13 +10372,15 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -12229,13 +12231,16 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { From b18812a567105f362a1203aca5175e3f037fa3ba Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Thu, 24 Jul 2025 09:29:01 -0400 Subject: [PATCH 40/68] fix(ws): Apply sentence case to text elements across UI (#497) Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> fix(ws): align nav item names with corresponding page headers Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> fix(ws): apply sentence case, fix tests Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> fix(ws): apply correct sentence case to TableTitleText --- .../src/__tests__/cypress/cypress/pages/home.ts | 2 +- workspaces/frontend/src/app/AppRoutes.tsx | 4 ++-- .../src/app/components/WorkspaceTable.tsx | 2 +- .../src/app/context/WorkspaceActionsContext.tsx | 2 +- .../properties/WorkspaceKindFormProperties.tsx | 4 ++-- .../app/pages/WorkspaceKinds/WorkspaceKinds.tsx | 16 ++++++++-------- .../details/WorkspaceKindDetails.tsx | 2 +- .../details/WorkspaceKindDetailsOverview.tsx | 2 +- .../WorkspaceKindSummaryExpandableCard.tsx | 8 ++++---- .../Details/WorkspaceDetailsActivity.tsx | 8 ++++---- .../src/app/pages/Workspaces/Workspaces.tsx | 2 +- .../WorkspaceRedirectInformationView.tsx | 4 ++-- .../WorkspaceRestartActionModal.tsx | 4 ++-- .../WorkspaceStopActionModal.tsx | 6 +++--- .../frontend/src/shared/components/Filter.tsx | 12 ++++++++---- .../frontend/src/shared/style/MUI-theme.scss | 5 ----- 16 files changed, 41 insertions(+), 42 deletions(-) diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/pages/home.ts b/workspaces/frontend/src/__tests__/cypress/cypress/pages/home.ts index 106ad8fc..f630eaba 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/pages/home.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/pages/home.ts @@ -4,7 +4,7 @@ class Home { } findButton() { - return cy.get('button:contains("Create Workspace")'); + return cy.get('button:contains("Create workspace")'); } } diff --git a/workspaces/frontend/src/app/AppRoutes.tsx b/workspaces/frontend/src/app/AppRoutes.tsx index 0251d74e..6b0136b4 100644 --- a/workspaces/frontend/src/app/AppRoutes.tsx +++ b/workspaces/frontend/src/app/AppRoutes.tsx @@ -43,7 +43,7 @@ export const useAdminDebugSettings = (): NavDataItem[] => { children: [{ label: 'Notebooks', path: '/notebookDebugSettings' }], }, { - label: 'Workspace Kinds', + label: 'Workspace kinds', path: AppRoutePaths.workspaceKinds, }, ]; @@ -51,7 +51,7 @@ export const useAdminDebugSettings = (): NavDataItem[] => { export const useNavData = (): NavDataItem[] => [ { - label: 'Notebooks', + label: 'Workspaces', path: AppRoutePaths.workspaces, }, ...useAdminDebugSettings(), diff --git a/workspaces/frontend/src/app/components/WorkspaceTable.tsx b/workspaces/frontend/src/app/components/WorkspaceTable.tsx index 184e8408..8be07a03 100644 --- a/workspaces/frontend/src/app/components/WorkspaceTable.tsx +++ b/workspaces/frontend/src/app/components/WorkspaceTable.tsx @@ -394,7 +394,7 @@ const WorkspaceTable = React.forwardRef( toolbarActions={ canCreateWorkspaces && ( ) } diff --git a/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx b/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx index d9a70d54..aac7efd9 100644 --- a/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx +++ b/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx @@ -213,7 +213,7 @@ export const WorkspaceActionsContextProvider: React.FC setActiveWsAction(null)} onDelete={async () => executeDeleteAction()} /> diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/properties/WorkspaceKindFormProperties.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/properties/WorkspaceKindFormProperties.tsx index 652156a3..4aafe35e 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/properties/WorkspaceKindFormProperties.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/properties/WorkspaceKindFormProperties.tsx @@ -76,9 +76,9 @@ export const WorkspaceKindFormProperties: React.FC updateField({ ...properties, deprecationMessage: value })} id="workspace-kind-deprecated-msg" /> diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx index daea4356..d0868e40 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx @@ -478,7 +478,7 @@ export const WorkspaceKinds: React.FunctionComponent = () => { -

Kubeflow Workspace Kinds

+

Workspace kinds

View your existing workspace kinds.


@@ -499,9 +499,9 @@ export const WorkspaceKinds: React.FunctionComponent = () => { @@ -520,9 +520,9 @@ export const WorkspaceKinds: React.FunctionComponent = () => { @@ -537,7 +537,7 @@ export const WorkspaceKinds: React.FunctionComponent = () => { diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx index ebaf0531..aaa7a150 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx @@ -71,7 +71,7 @@ export const WorkspaceKindDetails: React.FunctionComponent Pod Configs} + title={Pod configs} tabContentId="podConfigsTabContent" aria-label="Pod Configs" /> diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsOverview.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsOverview.tsx index d5aa3e8f..7a0b58d8 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsOverview.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsOverview.tsx @@ -42,7 +42,7 @@ export const WorkspaceKindDetailsOverview: React.FunctionComponent< - Deprecation Message + Deprecation message {workspaceKind.deprecationMessage} diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard.tsx index a1c3bee8..b5415036 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard.tsx @@ -54,7 +54,7 @@ const WorkspaceKindSummaryExpandableCard: React.FC - Workspaces Summary + Workspaces summary @@ -71,7 +71,7 @@ const WorkspaceKindSummaryExpandableCard: React.FC - + + )} + + Workspace Kinds + + Workspaces in {kind} + + {kind} @@ -83,7 +79,7 @@ const WorkspaceKindSummary: React.FC = () => { onAddFilter={onAddFilter} /> - + + Workspaces summary @@ -62,7 +57,7 @@ const WorkspaceKindSummaryExpandableCard: React.FC - + {countGpusFromWorkspaces(filterRunningWorkspaces(workspaces))} GPUs @@ -73,29 +68,25 @@ const WorkspaceKindSummaryExpandableCard: React.FC - - - + - - Idle GPU workspaces - + Idle GPU Workspaces - - - + + + {topGpuConsumersByNamespace.length > 0 ? ( topGpuConsumersByNamespace.map(([ns, record]) => ( @@ -112,7 +103,7 @@ const WorkspaceKindSummaryExpandableCard: React.FC )} - + @@ -134,7 +125,6 @@ const SectionFlex: React.FC = ({ children, title }) => ( {title} From f1f1c8336b0f9e48c439502b740fae212491b5c8 Mon Sep 17 00:00:00 2001 From: Roee Afriat <75727362+roee1313@users.noreply.github.com> Date: Thu, 24 Jul 2025 22:36:01 +0300 Subject: [PATCH 42/68] feat(ws): add ws counts to backend wsk model (#368) Signed-off-by: rafriat Co-authored-by: rafriat --- workspaces/backend/api/suite_test.go | 29 +++++++++++ .../internal/models/workspacekinds/funcs.go | 30 ++++++++++-- .../internal/models/workspacekinds/types.go | 49 +++++++++++-------- workspaces/backend/openapi/docs.go | 17 +++++++ workspaces/backend/openapi/swagger.json | 17 +++++++ 5 files changed, 117 insertions(+), 25 deletions(-) diff --git a/workspaces/backend/api/suite_test.go b/workspaces/backend/api/suite_test.go index ce03bdad..9c0c77fe 100644 --- a/workspaces/backend/api/suite_test.go +++ b/workspaces/backend/api/suite_test.go @@ -455,5 +455,34 @@ func NewExampleWorkspaceKind(name string) *kubefloworgv1beta1.WorkspaceKind { }, }, }, + Status: kubefloworgv1beta1.WorkspaceKindStatus{ + Workspaces: 1, + PodTemplateOptions: kubefloworgv1beta1.PodTemplateOptionsMetrics{ + ImageConfig: []kubefloworgv1beta1.OptionMetric{ + { + Id: "jupyterlab_scipy_180", + Workspaces: 1, + }, + { + Id: "jupyterlab_scipy_190", + Workspaces: 0, + }, + }, + PodConfig: []kubefloworgv1beta1.OptionMetric{ + { + Id: "tiny_cpu", + Workspaces: 1, + }, + { + Id: "small_cpu", + Workspaces: 0, + }, + { + Id: "big_gpu", + Workspaces: 0, + }, + }, + }, + }, } } diff --git a/workspaces/backend/internal/models/workspacekinds/funcs.go b/workspaces/backend/internal/models/workspacekinds/funcs.go index 98c77f0f..268b458e 100644 --- a/workspaces/backend/internal/models/workspacekinds/funcs.go +++ b/workspaces/backend/internal/models/workspacekinds/funcs.go @@ -36,6 +36,8 @@ func NewWorkspaceKindModelFromWorkspaceKind(wsk *kubefloworgv1beta1.WorkspaceKin podAnnotations[k] = v } } + statusImageConfigMap := buildOptionMetricsMap(wsk.Status.PodTemplateOptions.ImageConfig) + statusPodConfigMap := buildOptionMetricsMap(wsk.Status.PodTemplateOptions.PodConfig) // TODO: icons can either be a remote URL or read from a ConfigMap. // in BOTH cases, we should cache and serve the image under a path on the backend API: @@ -60,6 +62,10 @@ func NewWorkspaceKindModelFromWorkspaceKind(wsk *kubefloworgv1beta1.WorkspaceKin Hidden: ptr.Deref(wsk.Spec.Spawner.Hidden, false), Icon: iconRef, Logo: logoRef, + // TODO: in the future will need to support including exactly one of clusterMetrics or namespaceMetrics based on request context + ClusterMetrics: clusterMetrics{ + Workspaces: wsk.Status.Workspaces, + }, PodTemplate: PodTemplate{ PodMetadata: PodMetadata{ Labels: podLabels, @@ -71,18 +77,26 @@ func NewWorkspaceKindModelFromWorkspaceKind(wsk *kubefloworgv1beta1.WorkspaceKin Options: PodTemplateOptions{ ImageConfig: ImageConfig{ Default: wsk.Spec.PodTemplate.Options.ImageConfig.Spawner.Default, - Values: buildImageConfigValues(wsk.Spec.PodTemplate.Options.ImageConfig), + Values: buildImageConfigValues(wsk.Spec.PodTemplate.Options.ImageConfig, statusImageConfigMap), }, PodConfig: PodConfig{ Default: wsk.Spec.PodTemplate.Options.PodConfig.Spawner.Default, - Values: buildPodConfigValues(wsk.Spec.PodTemplate.Options.PodConfig), + Values: buildPodConfigValues(wsk.Spec.PodTemplate.Options.PodConfig, statusPodConfigMap), }, }, }, } } -func buildImageConfigValues(imageConfig kubefloworgv1beta1.ImageConfig) []ImageConfigValue { +func buildOptionMetricsMap(metrics []kubefloworgv1beta1.OptionMetric) map[string]int32 { + resultMap := make(map[string]int32) + for _, metric := range metrics { + resultMap[metric.Id] = metric.Workspaces + } + return resultMap +} + +func buildImageConfigValues(imageConfig kubefloworgv1beta1.ImageConfig, statusImageConfigMap map[string]int32) []ImageConfigValue { imageConfigValues := make([]ImageConfigValue, len(imageConfig.Values)) for i := range imageConfig.Values { option := imageConfig.Values[i] @@ -93,12 +107,16 @@ func buildImageConfigValues(imageConfig kubefloworgv1beta1.ImageConfig) []ImageC Labels: buildOptionLabels(option.Spawner.Labels), Hidden: ptr.Deref(option.Spawner.Hidden, false), Redirect: buildOptionRedirect(option.Redirect), + // TODO: in the future will need to support including exactly one of clusterMetrics or namespaceMetrics based on request context + ClusterMetrics: clusterMetrics{ + Workspaces: statusImageConfigMap[option.Id], + }, } } return imageConfigValues } -func buildPodConfigValues(podConfig kubefloworgv1beta1.PodConfig) []PodConfigValue { +func buildPodConfigValues(podConfig kubefloworgv1beta1.PodConfig, statusPodConfigMap map[string]int32) []PodConfigValue { podConfigValues := make([]PodConfigValue, len(podConfig.Values)) for i := range podConfig.Values { option := podConfig.Values[i] @@ -109,6 +127,10 @@ func buildPodConfigValues(podConfig kubefloworgv1beta1.PodConfig) []PodConfigVal Labels: buildOptionLabels(option.Spawner.Labels), Hidden: ptr.Deref(option.Spawner.Hidden, false), Redirect: buildOptionRedirect(option.Redirect), + // TODO: in the future will need to support including exactly one of clusterMetrics or namespaceMetrics based on request context + ClusterMetrics: clusterMetrics{ + Workspaces: statusPodConfigMap[option.Id], + }, } } return podConfigValues diff --git a/workspaces/backend/internal/models/workspacekinds/types.go b/workspaces/backend/internal/models/workspacekinds/types.go index c7947ea8..2996f466 100644 --- a/workspaces/backend/internal/models/workspacekinds/types.go +++ b/workspaces/backend/internal/models/workspacekinds/types.go @@ -17,15 +17,20 @@ limitations under the License. package workspacekinds type WorkspaceKind struct { - Name string `json:"name"` - DisplayName string `json:"displayName"` - Description string `json:"description"` - Deprecated bool `json:"deprecated"` - DeprecationMessage string `json:"deprecationMessage"` - Hidden bool `json:"hidden"` - Icon ImageRef `json:"icon"` - Logo ImageRef `json:"logo"` - PodTemplate PodTemplate `json:"podTemplate"` + Name string `json:"name"` + DisplayName string `json:"displayName"` + Description string `json:"description"` + Deprecated bool `json:"deprecated"` + DeprecationMessage string `json:"deprecationMessage"` + Hidden bool `json:"hidden"` + Icon ImageRef `json:"icon"` + Logo ImageRef `json:"logo"` + ClusterMetrics clusterMetrics `json:"clusterMetrics,omitempty"` + PodTemplate PodTemplate `json:"podTemplate"` +} + +type clusterMetrics struct { + Workspaces int32 `json:"workspacesCount"` } type ImageRef struct { @@ -58,12 +63,13 @@ type ImageConfig struct { } type ImageConfigValue struct { - Id string `json:"id"` - DisplayName string `json:"displayName"` - Description string `json:"description"` - Labels []OptionLabel `json:"labels"` - Hidden bool `json:"hidden"` - Redirect *OptionRedirect `json:"redirect,omitempty"` + Id string `json:"id"` + DisplayName string `json:"displayName"` + Description string `json:"description"` + Labels []OptionLabel `json:"labels"` + Hidden bool `json:"hidden"` + Redirect *OptionRedirect `json:"redirect,omitempty"` + ClusterMetrics clusterMetrics `json:"clusterMetrics,omitempty"` } type PodConfig struct { @@ -72,12 +78,13 @@ type PodConfig struct { } type PodConfigValue struct { - Id string `json:"id"` - DisplayName string `json:"displayName"` - Description string `json:"description"` - Labels []OptionLabel `json:"labels"` - Hidden bool `json:"hidden"` - Redirect *OptionRedirect `json:"redirect,omitempty"` + Id string `json:"id"` + DisplayName string `json:"displayName"` + Description string `json:"description"` + Labels []OptionLabel `json:"labels"` + Hidden bool `json:"hidden"` + Redirect *OptionRedirect `json:"redirect,omitempty"` + ClusterMetrics clusterMetrics `json:"clusterMetrics,omitempty"` } type OptionLabel struct { diff --git a/workspaces/backend/openapi/docs.go b/workspaces/backend/openapi/docs.go index ba09bcc6..7fa3f97d 100644 --- a/workspaces/backend/openapi/docs.go +++ b/workspaces/backend/openapi/docs.go @@ -778,6 +778,9 @@ const docTemplate = `{ "workspacekinds.ImageConfigValue": { "type": "object", "properties": { + "clusterMetrics": { + "$ref": "#/definitions/workspacekinds.clusterMetrics" + }, "description": { "type": "string" }, @@ -848,6 +851,9 @@ const docTemplate = `{ "workspacekinds.PodConfigValue": { "type": "object", "properties": { + "clusterMetrics": { + "$ref": "#/definitions/workspacekinds.clusterMetrics" + }, "description": { "type": "string" }, @@ -948,6 +954,9 @@ const docTemplate = `{ "workspacekinds.WorkspaceKind": { "type": "object", "properties": { + "clusterMetrics": { + "$ref": "#/definitions/workspacekinds.clusterMetrics" + }, "deprecated": { "type": "boolean" }, @@ -977,6 +986,14 @@ const docTemplate = `{ } } }, + "workspacekinds.clusterMetrics": { + "type": "object", + "properties": { + "workspacesCount": { + "type": "integer" + } + } + }, "workspaces.Activity": { "type": "object", "properties": { diff --git a/workspaces/backend/openapi/swagger.json b/workspaces/backend/openapi/swagger.json index 31c2b554..b5c0e085 100644 --- a/workspaces/backend/openapi/swagger.json +++ b/workspaces/backend/openapi/swagger.json @@ -776,6 +776,9 @@ "workspacekinds.ImageConfigValue": { "type": "object", "properties": { + "clusterMetrics": { + "$ref": "#/definitions/workspacekinds.clusterMetrics" + }, "description": { "type": "string" }, @@ -846,6 +849,9 @@ "workspacekinds.PodConfigValue": { "type": "object", "properties": { + "clusterMetrics": { + "$ref": "#/definitions/workspacekinds.clusterMetrics" + }, "description": { "type": "string" }, @@ -946,6 +952,9 @@ "workspacekinds.WorkspaceKind": { "type": "object", "properties": { + "clusterMetrics": { + "$ref": "#/definitions/workspacekinds.clusterMetrics" + }, "deprecated": { "type": "boolean" }, @@ -975,6 +984,14 @@ } } }, + "workspacekinds.clusterMetrics": { + "type": "object", + "properties": { + "workspacesCount": { + "type": "integer" + } + } + }, "workspaces.Activity": { "type": "object", "properties": { From dd94a8f44f6d342572b13bd07ecf6f01f1814a45 Mon Sep 17 00:00:00 2001 From: Noa Limoy <84776878+Noa-limoy@users.noreply.github.com> Date: Thu, 24 Jul 2025 19:41:01 +0000 Subject: [PATCH 43/68] feat(ws): containerize frontend component (#394) Signed-off-by: Noa --- workspaces/frontend/.dockerignore | 2 + workspaces/frontend/Dockerfile | 60 ++++++++++++++++++++++++++++ workspaces/frontend/nginx.conf | 66 +++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 workspaces/frontend/.dockerignore create mode 100644 workspaces/frontend/Dockerfile create mode 100644 workspaces/frontend/nginx.conf diff --git a/workspaces/frontend/.dockerignore b/workspaces/frontend/.dockerignore new file mode 100644 index 00000000..763301fc --- /dev/null +++ b/workspaces/frontend/.dockerignore @@ -0,0 +1,2 @@ +dist/ +node_modules/ \ No newline at end of file diff --git a/workspaces/frontend/Dockerfile b/workspaces/frontend/Dockerfile new file mode 100644 index 00000000..32a729c6 --- /dev/null +++ b/workspaces/frontend/Dockerfile @@ -0,0 +1,60 @@ +# ---------- Builder stage ---------- +FROM node:20-slim AS builder + +# Set working directory +WORKDIR /usr/src/app + +# Copy package files to the container +COPY package*.json ./ + +# Install the dependencies and build +RUN npm cache clean --force \ + && npm ci + +# Copy source code +COPY . . + +# Build the application +RUN npm run build:prod + + +# ---------- Production stage ---------- +FROM nginx:alpine + +USER root + +# Install envsubst (gettext package) +RUN apk add --no-cache gettext + +# Copy built assets from builder stage +COPY --from=builder /usr/src/app/dist /usr/share/nginx/html + +# Copy nginx config +COPY nginx.conf /etc/nginx/nginx.conf + +# Create directories and set permissions for non-root user +RUN mkdir -p /var/cache/nginx/client_temp \ + /var/cache/nginx/proxy_temp \ + /var/cache/nginx/fastcgi_temp \ + /var/cache/nginx/uwsgi_temp \ + /var/cache/nginx/scgi_temp \ + /var/run/nginx \ + /tmp/nginx && \ + # Change ownership of nginx directories to nginx user (UID 101) + chown -R 101:101 /var/cache/nginx \ + /var/run/nginx \ + /usr/share/nginx/html \ + /tmp/nginx \ + /etc/nginx + +# Switch to nginx user (UID 101) +USER 101:101 + +# Expose port +EXPOSE 8080 + +# Set environment variables +ENV PORT=8080 + +# Start the production server +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/workspaces/frontend/nginx.conf b/workspaces/frontend/nginx.conf new file mode 100644 index 00000000..60ade588 --- /dev/null +++ b/workspaces/frontend/nginx.conf @@ -0,0 +1,66 @@ +worker_processes auto; + +error_log /dev/stderr warn; +pid /tmp/nginx.pid; + +events { + worker_connections 1024; +} + +http { + log_format main '$remote_addr - $remote_user [$time_local] - $http_x_api_version - "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /dev/stdout main; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Temporary file paths for non-root user + client_body_temp_path /var/cache/nginx/client_temp; + proxy_temp_path /var/cache/nginx/proxy_temp; + fastcgi_temp_path /var/cache/nginx/fastcgi_temp; + uwsgi_temp_path /var/cache/nginx/uwsgi_temp; + scgi_temp_path /var/cache/nginx/scgi_temp; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; + + # Gzip Compression + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/yaml application/xml application/xml+rss text/javascript image/svg+xml; + gzip_comp_level 5; + gzip_min_length 1000; + gzip_proxied any; + gzip_vary on; + gzip_disable "msie6"; + + server { + listen 8080; + + # Health check endpoint + location /health { + access_log off; + return 200 'healthy\n'; + } + + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; + } + + # Static assets (cache enabled) + location ~* \.(css|js|gif|jpeg|jpg|png|ico|woff|woff2|ttf|otf|svg|eot)$ { + root /usr/share/nginx/html; + expires 30d; + add_header Cache-Control "public, no-transform"; + try_files $uri =404; + } + } +} From f02f07c5411ccd42dcfa790374649bf3ecc0c710 Mon Sep 17 00:00:00 2001 From: Andy Stoneberg Date: Thu, 24 Jul 2025 16:05:02 -0400 Subject: [PATCH 44/68] feat(ws): add workspace pause actions backend API (#340) related: #298 - Added PauseActionWorkspaceHandler to handle pausing or unpausing a given workspace - Introduced single new route for starting and pausing workspaces in the API. - `api/v1/workspaces/{namespace}/{name}/actions/pause` - pausing or unpausing operation is specified in the request payload - Created a new WorkspaceActionPauseEnvelope type for successful responses. - Leveraging JSONPatch / client.RawPatch to ensure Workspace in "valid state" before attempting action - for `start`: `spec.paused` must be `true`, and `status.state` must be `Paused` - for `pause`: `spec.paused` must be `false` - note: I would love to have a `status.state` check here of `status.state != Paused`, but that type of comparison is not supported in [JSONPatch](https://datatracker.ietf.org/doc/html/rfc6902#section-4.6) - Added tests for the new API, including success and error cases. - Updated README/OpenAPI documentation to include the new endpoints. --- As an interesting "edge case" worth calling out, the following payload is currently honored by the API: ``` { "data": {} } ``` Given the `WorkspaceActionPause` struct is simply `{"paused": true|false}`, the "empty" Envelope presented above deserializes the JSON using the zero value of `bool` (which is `false`). Our validation today is always performed against the **deserialized** object, and as such impossible to distinguish the following cases: ``` { "data": {} } ``` vs ``` { "data": { "paused": false } } ``` The effort and (relative) complexity to prevent this and return a `422` in this scenario was not deemed "worth it" for the time being. As a result, a test case has been added for this specific scenario to at minimum document this "strange" behavior. - Clients, however, should **NOT** rely on this behavior and always provide a fully defined `WorkspaceActionPause` JSON object to ensure future compatibility. Signed-off-by: Andy Stoneberg --- workspaces/backend/README.md | 65 ++- workspaces/backend/api/app.go | 3 + .../backend/api/workspace_actions_handler.go | 128 +++++ .../api/workspace_actions_handler_test.go | 496 ++++++++++++++++++ .../models/workspaces/actions/funcs.go | 28 + .../models/workspaces/actions/types.go | 22 + .../internal/repositories/workspaces/repo.go | 74 ++- workspaces/backend/openapi/docs.go | 114 ++++ workspaces/backend/openapi/swagger.json | 114 ++++ 9 files changed, 1023 insertions(+), 21 deletions(-) create mode 100644 workspaces/backend/api/workspace_actions_handler.go create mode 100644 workspaces/backend/api/workspace_actions_handler_test.go create mode 100644 workspaces/backend/internal/models/workspaces/actions/funcs.go create mode 100644 workspaces/backend/internal/models/workspaces/actions/types.go diff --git a/workspaces/backend/README.md b/workspaces/backend/README.md index f5cbd683..b9517b60 100644 --- a/workspaces/backend/README.md +++ b/workspaces/backend/README.md @@ -27,29 +27,30 @@ make run If you want to use a different port: ```shell -make run PORT=8000 +make run PORT=8000 ``` ### Endpoints -| URL Pattern | Handler | Action | -|----------------------------------------------|------------------------|-----------------------------------------| -| GET /api/v1/healthcheck | healthcheck_handler | Show application information | -| GET /api/v1/namespaces | namespaces_handler | Get all Namespaces | -| GET /api/v1/swagger/ | swagger_handler | Swagger API documentation | -| GET /api/v1/workspaces | workspaces_handler | Get all Workspaces | -| GET /api/v1/workspaces/{namespace} | workspaces_handler | Get all Workspaces from a namespace | -| POST /api/v1/workspaces/{namespace} | workspaces_handler | Create a Workspace in a given namespace | -| GET /api/v1/workspaces/{namespace}/{name} | workspaces_handler | Get a Workspace entity | -| PATCH /api/v1/workspaces/{namespace}/{name} | TBD | Patch a Workspace entity | -| PUT /api/v1/workspaces/{namespace}/{name} | TBD | Update a Workspace entity | -| DELETE /api/v1/workspaces/{namespace}/{name} | workspaces_handler | Delete a Workspace entity | -| GET /api/v1/workspacekinds | workspacekinds_handler | Get all WorkspaceKind | -| POST /api/v1/workspacekinds | TBD | Create a WorkspaceKind | -| GET /api/v1/workspacekinds/{name} | workspacekinds_handler | Get a WorkspaceKind entity | -| PATCH /api/v1/workspacekinds/{name} | TBD | Patch a WorkspaceKind entity | -| PUT /api/v1/workspacekinds/{name} | TBD | Update a WorkspaceKind entity | -| DELETE /api/v1/workspacekinds/{name} | TBD | Delete a WorkspaceKind entity | +| URL Pattern | Handler | Action | +|-----------------------------------------------------------|---------------------------|-----------------------------------------| +| GET /api/v1/healthcheck | healthcheck_handler | Show application information | +| GET /api/v1/namespaces | namespaces_handler | Get all Namespaces | +| GET /api/v1/swagger/ | swagger_handler | Swagger API documentation | +| GET /api/v1/workspaces | workspaces_handler | Get all Workspaces | +| GET /api/v1/workspaces/{namespace} | workspaces_handler | Get all Workspaces from a namespace | +| POST /api/v1/workspaces/{namespace} | workspaces_handler | Create a Workspace in a given namespace | +| GET /api/v1/workspaces/{namespace}/{name} | workspaces_handler | Get a Workspace entity | +| PATCH /api/v1/workspaces/{namespace}/{name} | TBD | Patch a Workspace entity | +| PUT /api/v1/workspaces/{namespace}/{name} | TBD | Update a Workspace entity | +| DELETE /api/v1/workspaces/{namespace}/{name} | workspaces_handler | Delete a Workspace entity | +| POST /api/v1/workspaces/{namespace}/{name}/actions/pause | workspace_actions_handler | Set paused state of a workspace | +| GET /api/v1/workspacekinds | workspacekinds_handler | Get all WorkspaceKind | +| POST /api/v1/workspacekinds | TBD | Create a WorkspaceKind | +| GET /api/v1/workspacekinds/{name} | workspacekinds_handler | Get a WorkspaceKind entity | +| PATCH /api/v1/workspacekinds/{name} | TBD | Patch a WorkspaceKind entity | +| PUT /api/v1/workspacekinds/{name} | TBD | Update a WorkspaceKind entity | +| DELETE /api/v1/workspacekinds/{name} | TBD | Delete a WorkspaceKind entity | ### Sample local calls @@ -128,6 +129,32 @@ Get a Workspace: curl -i localhost:4000/api/v1/workspaces/default/dora ``` +Pause a Workspace: + +```shell +# POST /api/v1/workspaces/{namespace}/{name}/actions/pause +curl -X POST localhost:4000/api/v1/workspaces/default/dora/actions/pause \ + -H "Content-Type: application/json" \ + -d '{ + "data": { + "paused": true + } +}' +``` + +Start a Workspace: + +```shell +# POST /api/v1/workspaces/{namespace}/{name}/actions/pause +curl -X POST localhost:4000/api/v1/workspaces/default/dora/actions/pause \ + -H "Content-Type: application/json" \ + -d '{ + "data": { + "paused": false + } +}' +``` + Delete a Workspace: ```shell diff --git a/workspaces/backend/api/app.go b/workspaces/backend/api/app.go index 2f76c252..e533eac8 100644 --- a/workspaces/backend/api/app.go +++ b/workspaces/backend/api/app.go @@ -50,6 +50,8 @@ const ( AllWorkspacesPath = PathPrefix + "/workspaces" WorkspacesByNamespacePath = AllWorkspacesPath + "/:" + NamespacePathParam WorkspacesByNamePath = AllWorkspacesPath + "/:" + NamespacePathParam + "/:" + ResourceNamePathParam + WorkspaceActionsPath = WorkspacesByNamePath + "/actions" + PauseWorkspacePath = WorkspaceActionsPath + "/pause" // workspacekinds AllWorkspaceKindsPath = PathPrefix + "/workspacekinds" @@ -116,6 +118,7 @@ func (a *App) Routes() http.Handler { router.GET(WorkspacesByNamePath, a.GetWorkspaceHandler) router.POST(WorkspacesByNamespacePath, a.CreateWorkspaceHandler) router.DELETE(WorkspacesByNamePath, a.DeleteWorkspaceHandler) + router.POST(PauseWorkspacePath, a.PauseActionWorkspaceHandler) // workspacekinds router.GET(AllWorkspaceKindsPath, a.GetWorkspaceKindsHandler) diff --git a/workspaces/backend/api/workspace_actions_handler.go b/workspaces/backend/api/workspace_actions_handler.go new file mode 100644 index 00000000..75e1001d --- /dev/null +++ b/workspaces/backend/api/workspace_actions_handler.go @@ -0,0 +1,128 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package api + +import ( + "errors" + "fmt" + "net/http" + + "github.com/julienschmidt/httprouter" + kubefloworgv1beta1 "github.com/kubeflow/notebooks/workspaces/controller/api/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/kubeflow/notebooks/workspaces/backend/internal/auth" + "github.com/kubeflow/notebooks/workspaces/backend/internal/helper" + models "github.com/kubeflow/notebooks/workspaces/backend/internal/models/workspaces/actions" + repository "github.com/kubeflow/notebooks/workspaces/backend/internal/repositories/workspaces" +) + +type WorkspaceActionPauseEnvelope Envelope[*models.WorkspaceActionPause] + +// PauseActionWorkspaceHandler handles setting the paused state of a workspace. +// +// @Summary Pause or unpause a workspace +// @Description Pauses or unpauses a workspace, stopping or resuming all associated pods. +// @Tags workspaces +// @Accept json +// @Produce json +// @Param namespace path string true "Namespace of the workspace" extensions(x-example=default) +// @Param workspaceName path string true "Name of the workspace" extensions(x-example=my-workspace) +// @Param body body WorkspaceActionPauseEnvelope true "Intended pause state of the workspace" +// @Success 200 {object} WorkspaceActionPauseEnvelope "Successful action. Returns the current pause state." +// @Failure 400 {object} ErrorEnvelope "Bad Request." +// @Failure 401 {object} ErrorEnvelope "Unauthorized. Authentication is required." +// @Failure 403 {object} ErrorEnvelope "Forbidden. User does not have permission to access the workspace." +// @Failure 404 {object} ErrorEnvelope "Not Found. Workspace does not exist." +// @Failure 413 {object} ErrorEnvelope "Request Entity Too Large. The request body is too large." +// @Failure 415 {object} ErrorEnvelope "Unsupported Media Type. Content-Type header is not correct." +// @Failure 422 {object} ErrorEnvelope "Unprocessable Entity. Workspace is not in appropriate state." +// @Failure 500 {object} ErrorEnvelope "Internal server error. An unexpected error occurred on the server." +// @Router /workspaces/{namespace}/{workspaceName}/actions/pause [post] +func (a *App) PauseActionWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + namespace := ps.ByName(NamespacePathParam) + workspaceName := ps.ByName(ResourceNamePathParam) + + var valErrs field.ErrorList + valErrs = append(valErrs, helper.ValidateFieldIsDNS1123Subdomain(field.NewPath(NamespacePathParam), namespace)...) + valErrs = append(valErrs, helper.ValidateFieldIsDNS1123Subdomain(field.NewPath(ResourceNamePathParam), workspaceName)...) + if len(valErrs) > 0 { + a.failedValidationResponse(w, r, errMsgPathParamsInvalid, valErrs, nil) + return + } + + if success := a.ValidateContentType(w, r, "application/json"); !success { + return + } + + bodyEnvelope := &WorkspaceActionPauseEnvelope{} + err := a.DecodeJSON(r, bodyEnvelope) + if err != nil { + if a.IsMaxBytesError(err) { + a.requestEntityTooLargeResponse(w, r, err) + return + } + a.badRequestResponse(w, r, fmt.Errorf("error decoding request body: %w", err)) + return + } + + dataPath := field.NewPath("data") + if bodyEnvelope.Data == nil { + valErrs = field.ErrorList{field.Required(dataPath, "data is required")} + a.failedValidationResponse(w, r, errMsgRequestBodyInvalid, valErrs, nil) + return + } + + workspaceActionPause := bodyEnvelope.Data + + // =========================== AUTH =========================== + authPolicies := []*auth.ResourcePolicy{ + auth.NewResourcePolicy( + auth.ResourceVerbUpdate, + &kubefloworgv1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: workspaceName, + }, + }, + ), + } + if success := a.requireAuth(w, r, authPolicies); !success { + return + } + // ============================================================ + + workspaceActionPauseState, err := a.repositories.Workspace.HandlePauseAction(r.Context(), namespace, workspaceName, workspaceActionPause) + if err != nil { + if errors.Is(err, repository.ErrWorkspaceNotFound) { + a.notFoundResponse(w, r) + return + } + if errors.Is(err, repository.ErrWorkspaceInvalidState) { + a.failedValidationResponse(w, r, err.Error(), nil, nil) + return + } + a.serverErrorResponse(w, r, err) + return + } + + responseEnvelope := &WorkspaceActionPauseEnvelope{ + Data: workspaceActionPauseState, + } + a.dataResponse(w, r, responseEnvelope) +} diff --git a/workspaces/backend/api/workspace_actions_handler_test.go b/workspaces/backend/api/workspace_actions_handler_test.go new file mode 100644 index 00000000..57b70958 --- /dev/null +++ b/workspaces/backend/api/workspace_actions_handler_test.go @@ -0,0 +1,496 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package api + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strings" + + "github.com/julienschmidt/httprouter" + kubefloworgv1beta1 "github.com/kubeflow/notebooks/workspaces/controller/api/v1beta1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" + + models "github.com/kubeflow/notebooks/workspaces/backend/internal/models/workspaces/actions" +) + +var _ = Describe("Workspace Actions Handler", func() { + + // NOTE: the tests in this context work on the same resources, they must be run in order. + // also, they assume a specific state of the cluster, so cannot be run in parallel with other tests. + // therefore, we run them using the `Ordered` and `Serial` Ginkgo decorators. + Context("with existing Workspaces", Serial, Ordered, func() { + + const namespaceName1 = "ws-ops-ns1" + + var ( + workspaceName1 string + workspaceKey1 types.NamespacedName + workspaceKindName string + ) + + BeforeAll(func() { + uniqueName := "ws-ops-test" + workspaceName1 = fmt.Sprintf("workspace-1-%s", uniqueName) + workspaceKey1 = types.NamespacedName{Name: workspaceName1, Namespace: namespaceName1} + workspaceKindName = fmt.Sprintf("workspacekind-%s", uniqueName) + + By("creating Namespace 1") + namespace1 := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespaceName1, + }, + } + Expect(k8sClient.Create(ctx, namespace1)).To(Succeed()) + + By("creating a WorkspaceKind") + workspaceKind := NewExampleWorkspaceKind(workspaceKindName) + Expect(k8sClient.Create(ctx, workspaceKind)).To(Succeed()) + + By("creating Workspace 1 in Namespace 1") + workspace1 := NewExampleWorkspace(workspaceName1, namespaceName1, workspaceKindName) + Expect(k8sClient.Create(ctx, workspace1)).To(Succeed()) + }) + + AfterAll(func() { + By("deleting Workspace 1 from Namespace 1") + workspace1 := &kubefloworgv1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{ + Name: workspaceName1, + Namespace: namespaceName1, + }, + } + Expect(k8sClient.Delete(ctx, workspace1)).To(Succeed()) + + By("deleting WorkspaceKind") + workspaceKind := &kubefloworgv1beta1.WorkspaceKind{ + ObjectMeta: metav1.ObjectMeta{ + Name: workspaceKindName, + }, + } + Expect(k8sClient.Delete(ctx, workspaceKind)).To(Succeed()) + + By("deleting Namespace 1") + namespace1 := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespaceName1, + }, + } + Expect(k8sClient.Delete(ctx, namespace1)).To(Succeed()) + }) + + It("should pause a workspace successfully", func() { + By("creating the request body") + requestBody := &WorkspaceActionPauseEnvelope{ + Data: &models.WorkspaceActionPause{ + Paused: true, + }, + } + bodyBytes, err := json.Marshal(requestBody) + Expect(err).NotTo(HaveOccurred()) + + By("creating the HTTP request") + path := strings.Replace(PauseWorkspacePath, ":"+NamespacePathParam, namespaceName1, 1) + path = strings.Replace(path, ":"+ResourceNamePathParam, workspaceName1, 1) + req, err := http.NewRequest(http.MethodPost, path, strings.NewReader(string(bodyBytes))) + Expect(err).NotTo(HaveOccurred()) + + By("setting the auth headers") + req.Header.Set(userIdHeader, adminUser) + req.Header.Set("Content-Type", "application/json") + + By("executing PauseActionWorkspaceHandler") + ps := httprouter.Params{ + httprouter.Param{Key: NamespacePathParam, Value: namespaceName1}, + httprouter.Param{Key: ResourceNamePathParam, Value: workspaceName1}, + } + rr := httptest.NewRecorder() + a.PauseActionWorkspaceHandler(rr, req, ps) + rs := rr.Result() + defer rs.Body.Close() + + By("verifying the HTTP response status code") + Expect(rs.StatusCode).To(Equal(http.StatusOK), descUnexpectedHTTPStatus, rr.Body.String()) + + By("reading the HTTP response body") + body, err := io.ReadAll(rs.Body) + Expect(err).NotTo(HaveOccurred()) + + By("verifying the response contains the pause state") + var response WorkspaceActionPauseEnvelope + err = json.Unmarshal(body, &response) + Expect(err).NotTo(HaveOccurred()) + Expect(response.Data).NotTo(BeNil()) + Expect(response.Data.Paused).To(BeTrue()) + + By("getting the Workspace from the Kubernetes API") + workspace := &kubefloworgv1beta1.Workspace{} + Expect(k8sClient.Get(ctx, workspaceKey1, workspace)).To(Succeed()) + + By("ensuring the workspace is paused") + Expect(workspace.Spec.Paused).To(Equal(ptr.To(true))) + }) + + It("should start a workspace successfully", func() { + By("setting the workspace's status state to Paused") + workspace := &kubefloworgv1beta1.Workspace{} + Expect(k8sClient.Get(ctx, workspaceKey1, workspace)).To(Succeed()) + workspace.Status.State = kubefloworgv1beta1.WorkspaceStatePaused + Expect(k8sClient.Status().Update(ctx, workspace)).To(Succeed()) + + By("creating the request body") + requestBody := &WorkspaceActionPauseEnvelope{ + Data: &models.WorkspaceActionPause{ + Paused: false, + }, + } + bodyBytes, err := json.Marshal(requestBody) + Expect(err).NotTo(HaveOccurred()) + + By("creating the HTTP request") + path := strings.Replace(PauseWorkspacePath, ":"+NamespacePathParam, namespaceName1, 1) + path = strings.Replace(path, ":"+ResourceNamePathParam, workspaceName1, 1) + req, err := http.NewRequest(http.MethodPost, path, strings.NewReader(string(bodyBytes))) + Expect(err).NotTo(HaveOccurred()) + + By("setting the auth headers") + req.Header.Set(userIdHeader, adminUser) + req.Header.Set("Content-Type", "application/json") + + By("executing PauseActionWorkspaceHandler") + ps := httprouter.Params{ + httprouter.Param{Key: NamespacePathParam, Value: namespaceName1}, + httprouter.Param{Key: ResourceNamePathParam, Value: workspaceName1}, + } + rr := httptest.NewRecorder() + a.PauseActionWorkspaceHandler(rr, req, ps) + rs := rr.Result() + defer rs.Body.Close() + + By("verifying the HTTP response status code") + Expect(rs.StatusCode).To(Equal(http.StatusOK), descUnexpectedHTTPStatus, rr.Body.String()) + + By("reading the HTTP response body") + body, err := io.ReadAll(rs.Body) + Expect(err).NotTo(HaveOccurred()) + + By("verifying the response contains the pause state") + var response WorkspaceActionPauseEnvelope + err = json.Unmarshal(body, &response) + Expect(err).NotTo(HaveOccurred()) + Expect(response.Data).NotTo(BeNil()) + Expect(response.Data.Paused).To(BeFalse()) + + By("getting the Workspace from the Kubernetes API") + workspace = &kubefloworgv1beta1.Workspace{} + Expect(k8sClient.Get(ctx, workspaceKey1, workspace)).To(Succeed()) + + By("ensuring the workspace is not paused") + Expect(workspace.Spec.Paused).To(Equal(ptr.To(false))) + }) + + It("should return 404 for a non-existent workspace when starting", func() { + missingWorkspaceName := "non-existent-workspace" + + By("creating the request body") + requestBody := &WorkspaceActionPauseEnvelope{ + Data: &models.WorkspaceActionPause{ + Paused: false, + }, + } + bodyBytes, err := json.Marshal(requestBody) + Expect(err).NotTo(HaveOccurred()) + + By("creating the HTTP request") + path := strings.Replace(PauseWorkspacePath, ":"+NamespacePathParam, namespaceName1, 1) + path = strings.Replace(path, ":"+ResourceNamePathParam, missingWorkspaceName, 1) + req, err := http.NewRequest(http.MethodPost, path, strings.NewReader(string(bodyBytes))) + Expect(err).NotTo(HaveOccurred()) + + By("setting the auth headers") + req.Header.Set(userIdHeader, adminUser) + req.Header.Set("Content-Type", "application/json") + + By("executing PauseActionWorkspaceHandler") + ps := httprouter.Params{ + httprouter.Param{Key: NamespacePathParam, Value: namespaceName1}, + httprouter.Param{Key: ResourceNamePathParam, Value: missingWorkspaceName}, + } + rr := httptest.NewRecorder() + a.PauseActionWorkspaceHandler(rr, req, ps) + rs := rr.Result() + defer rs.Body.Close() + + By("verifying the HTTP response status code") + Expect(rs.StatusCode).To(Equal(http.StatusNotFound), descUnexpectedHTTPStatus, rr.Body.String()) + }) + + It("should return 404 for a non-existent workspace when pausing", func() { + missingWorkspaceName := "non-existent-workspace" + + By("creating the request body") + requestBody := &WorkspaceActionPauseEnvelope{ + Data: &models.WorkspaceActionPause{ + Paused: true, + }, + } + bodyBytes, err := json.Marshal(requestBody) + Expect(err).NotTo(HaveOccurred()) + + By("creating the HTTP request") + path := strings.Replace(PauseWorkspacePath, ":"+NamespacePathParam, namespaceName1, 1) + path = strings.Replace(path, ":"+ResourceNamePathParam, missingWorkspaceName, 1) + req, err := http.NewRequest(http.MethodPost, path, strings.NewReader(string(bodyBytes))) + Expect(err).NotTo(HaveOccurred()) + + By("setting the auth headers") + req.Header.Set(userIdHeader, adminUser) + req.Header.Set("Content-Type", "application/json") + + By("executing PauseActionWorkspaceHandler") + ps := httprouter.Params{ + httprouter.Param{Key: NamespacePathParam, Value: namespaceName1}, + httprouter.Param{Key: ResourceNamePathParam, Value: missingWorkspaceName}, + } + rr := httptest.NewRecorder() + a.PauseActionWorkspaceHandler(rr, req, ps) + rs := rr.Result() + defer rs.Body.Close() + + By("verifying the HTTP response status code") + Expect(rs.StatusCode).To(Equal(http.StatusNotFound), descUnexpectedHTTPStatus, rr.Body.String()) + }) + + It("should return 422 when starting a workspace that is not in Paused state", func() { + By("setting the workspace's status state to Unknown and spec.paused to false") + workspace := &kubefloworgv1beta1.Workspace{} + Expect(k8sClient.Get(ctx, workspaceKey1, workspace)).To(Succeed()) + workspace.Spec.Paused = ptr.To(false) + workspace.Status.State = kubefloworgv1beta1.WorkspaceStateUnknown + Expect(k8sClient.Update(ctx, workspace)).To(Succeed()) + Expect(k8sClient.Status().Update(ctx, workspace)).To(Succeed()) + + By("creating the request body") + requestBody := &WorkspaceActionPauseEnvelope{ + Data: &models.WorkspaceActionPause{ + Paused: false, + }, + } + bodyBytes, err := json.Marshal(requestBody) + Expect(err).NotTo(HaveOccurred()) + + By("creating the HTTP request") + path := strings.Replace(PauseWorkspacePath, ":"+NamespacePathParam, namespaceName1, 1) + path = strings.Replace(path, ":"+ResourceNamePathParam, workspaceName1, 1) + req, err := http.NewRequest(http.MethodPost, path, strings.NewReader(string(bodyBytes))) + Expect(err).NotTo(HaveOccurred()) + + By("setting the auth headers") + req.Header.Set(userIdHeader, adminUser) + req.Header.Set("Content-Type", "application/json") + + By("executing PauseActionWorkspaceHandler") + ps := httprouter.Params{ + httprouter.Param{Key: NamespacePathParam, Value: namespaceName1}, + httprouter.Param{Key: ResourceNamePathParam, Value: workspaceName1}, + } + rr := httptest.NewRecorder() + a.PauseActionWorkspaceHandler(rr, req, ps) + rs := rr.Result() + defer rs.Body.Close() + + By("verifying the HTTP response status code is 422") + Expect(rs.StatusCode).To(Equal(http.StatusUnprocessableEntity), descUnexpectedHTTPStatus, rr.Body.String()) + }) + + It("should return 422 when pausing a workspace that is already paused", func() { + By("setting the workspace's spec.paused to true") + workspace := &kubefloworgv1beta1.Workspace{} + Expect(k8sClient.Get(ctx, workspaceKey1, workspace)).To(Succeed()) + workspace.Spec.Paused = ptr.To(true) + Expect(k8sClient.Update(ctx, workspace)).To(Succeed()) + + By("creating the request body") + requestBody := &WorkspaceActionPauseEnvelope{ + Data: &models.WorkspaceActionPause{ + Paused: true, + }, + } + bodyBytes, err := json.Marshal(requestBody) + Expect(err).NotTo(HaveOccurred()) + + By("creating the HTTP request") + path := strings.Replace(PauseWorkspacePath, ":"+NamespacePathParam, namespaceName1, 1) + path = strings.Replace(path, ":"+ResourceNamePathParam, workspaceName1, 1) + req, err := http.NewRequest(http.MethodPost, path, strings.NewReader(string(bodyBytes))) + Expect(err).NotTo(HaveOccurred()) + + By("setting the auth headers") + req.Header.Set(userIdHeader, adminUser) + req.Header.Set("Content-Type", "application/json") + + By("executing PauseActionWorkspaceHandler") + ps := httprouter.Params{ + httprouter.Param{Key: NamespacePathParam, Value: namespaceName1}, + httprouter.Param{Key: ResourceNamePathParam, Value: workspaceName1}, + } + rr := httptest.NewRecorder() + a.PauseActionWorkspaceHandler(rr, req, ps) + rs := rr.Result() + defer rs.Body.Close() + + By("verifying the HTTP response status code is 422") + Expect(rs.StatusCode).To(Equal(http.StatusUnprocessableEntity), descUnexpectedHTTPStatus, rr.Body.String()) + }) + + It("should return 422 when request body is missing data field", func() { + By("creating the request body without data field") + requestBody := map[string]interface{}{} + bodyBytes, err := json.Marshal(requestBody) + Expect(err).NotTo(HaveOccurred()) + + By("creating the HTTP request") + path := strings.Replace(PauseWorkspacePath, ":"+NamespacePathParam, namespaceName1, 1) + path = strings.Replace(path, ":"+ResourceNamePathParam, workspaceName1, 1) + req, err := http.NewRequest(http.MethodPost, path, strings.NewReader(string(bodyBytes))) + Expect(err).NotTo(HaveOccurred()) + + By("setting the auth headers") + req.Header.Set(userIdHeader, adminUser) + req.Header.Set("Content-Type", "application/json") + + By("executing PauseActionWorkspaceHandler") + ps := httprouter.Params{ + httprouter.Param{Key: NamespacePathParam, Value: namespaceName1}, + httprouter.Param{Key: ResourceNamePathParam, Value: workspaceName1}, + } + rr := httptest.NewRecorder() + a.PauseActionWorkspaceHandler(rr, req, ps) + rs := rr.Result() + defer rs.Body.Close() + + By("verifying the HTTP response status code is 422") + Expect(rs.StatusCode).To(Equal(http.StatusUnprocessableEntity), descUnexpectedHTTPStatus, rr.Body.String()) + }) + + It("should return 415 when Content-Type is not application/json", func() { + By("creating the request body") + requestBody := &WorkspaceActionPauseEnvelope{ + Data: &models.WorkspaceActionPause{ + Paused: true, + }, + } + bodyBytes, err := json.Marshal(requestBody) + Expect(err).NotTo(HaveOccurred()) + + By("creating the HTTP request") + path := strings.Replace(PauseWorkspacePath, ":"+NamespacePathParam, namespaceName1, 1) + path = strings.Replace(path, ":"+ResourceNamePathParam, workspaceName1, 1) + req, err := http.NewRequest(http.MethodPost, path, strings.NewReader(string(bodyBytes))) + Expect(err).NotTo(HaveOccurred()) + + By("setting the auth headers with wrong Content-Type") + req.Header.Set(userIdHeader, adminUser) + req.Header.Set("Content-Type", "application/merge-patch+json") + + By("executing PauseActionWorkspaceHandler") + ps := httprouter.Params{ + httprouter.Param{Key: NamespacePathParam, Value: namespaceName1}, + httprouter.Param{Key: ResourceNamePathParam, Value: workspaceName1}, + } + rr := httptest.NewRecorder() + a.PauseActionWorkspaceHandler(rr, req, ps) + rs := rr.Result() + defer rs.Body.Close() + + By("verifying the HTTP response status code is 415") + Expect(rs.StatusCode).To(Equal(http.StatusUnsupportedMediaType), descUnexpectedHTTPStatus, rr.Body.String()) + }) + + // This test highlights that when the pause API receives a payload of {"data":{}}, + // the zero value for the 'Paused' field (false) is used. This is equivalent to + // explicitly setting "paused": false. This test case is included to make the behavior + // obvious for future maintainers. While this is not necessarily desired behavior, + // the effort to add sufficient validation to the API is not worth the effort as it would + // require a "framework" to validate the raw JSON payload before it is deserialized. + It("should handle empty data object payload correctly", func() { + By("setting the workspace's spec.paused to true and status state to Paused") + workspace := &kubefloworgv1beta1.Workspace{} + Expect(k8sClient.Get(ctx, workspaceKey1, workspace)).To(Succeed()) + workspace.Spec.Paused = ptr.To(true) + Expect(k8sClient.Update(ctx, workspace)).To(Succeed()) + workspace.Status.State = kubefloworgv1beta1.WorkspaceStatePaused + Expect(k8sClient.Status().Update(ctx, workspace)).To(Succeed()) + + By("creating the request body with empty data object") + requestBody := map[string]interface{}{ + "data": map[string]interface{}{}, + } + bodyBytes, err := json.Marshal(requestBody) + Expect(err).NotTo(HaveOccurred()) + + By("creating the HTTP request") + path := strings.Replace(PauseWorkspacePath, ":"+NamespacePathParam, namespaceName1, 1) + path = strings.Replace(path, ":"+ResourceNamePathParam, workspaceName1, 1) + req, err := http.NewRequest(http.MethodPost, path, strings.NewReader(string(bodyBytes))) + Expect(err).NotTo(HaveOccurred()) + + By("setting the auth headers") + req.Header.Set(userIdHeader, adminUser) + req.Header.Set("Content-Type", "application/json") + + By("executing PauseActionWorkspaceHandler") + ps := httprouter.Params{ + httprouter.Param{Key: NamespacePathParam, Value: namespaceName1}, + httprouter.Param{Key: ResourceNamePathParam, Value: workspaceName1}, + } + rr := httptest.NewRecorder() + a.PauseActionWorkspaceHandler(rr, req, ps) + rs := rr.Result() + defer rs.Body.Close() + + By("verifying the HTTP response status code") + Expect(rs.StatusCode).To(Equal(http.StatusOK), descUnexpectedHTTPStatus, rr.Body.String()) + + By("reading the HTTP response body") + body, err := io.ReadAll(rs.Body) + Expect(err).NotTo(HaveOccurred()) + + By("verifying the response contains the pause state") + var response WorkspaceActionPauseEnvelope + err = json.Unmarshal(body, &response) + Expect(err).NotTo(HaveOccurred()) + Expect(response.Data).NotTo(BeNil()) + Expect(response.Data.Paused).To(BeFalse()) + + By("getting the Workspace from the Kubernetes API") + workspace = &kubefloworgv1beta1.Workspace{} + Expect(k8sClient.Get(ctx, workspaceKey1, workspace)).To(Succeed()) + + By("ensuring the workspace is not paused (empty data object results in false)") + Expect(workspace.Spec.Paused).To(Equal(ptr.To(false))) + }) + }) +}) diff --git a/workspaces/backend/internal/models/workspaces/actions/funcs.go b/workspaces/backend/internal/models/workspaces/actions/funcs.go new file mode 100644 index 00000000..ce048d3f --- /dev/null +++ b/workspaces/backend/internal/models/workspaces/actions/funcs.go @@ -0,0 +1,28 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package actions + +import ( + kubefloworgv1beta1 "github.com/kubeflow/notebooks/workspaces/controller/api/v1beta1" + "k8s.io/utils/ptr" +) + +func NewWorkspaceActionPauseFromWorkspace(ws *kubefloworgv1beta1.Workspace) *WorkspaceActionPause { + return &WorkspaceActionPause{ + Paused: ptr.Deref(ws.Spec.Paused, false), + } +} diff --git a/workspaces/backend/internal/models/workspaces/actions/types.go b/workspaces/backend/internal/models/workspaces/actions/types.go new file mode 100644 index 00000000..6716b740 --- /dev/null +++ b/workspaces/backend/internal/models/workspaces/actions/types.go @@ -0,0 +1,22 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package actions + +// WorkspaceActionPause represents the outcome of pause/start workspace actions +type WorkspaceActionPause struct { + Paused bool `json:"paused"` +} diff --git a/workspaces/backend/internal/repositories/workspaces/repo.go b/workspaces/backend/internal/repositories/workspaces/repo.go index 82f3eada..9b0743aa 100644 --- a/workspaces/backend/internal/repositories/workspaces/repo.go +++ b/workspaces/backend/internal/repositories/workspaces/repo.go @@ -18,19 +18,25 @@ package workspaces import ( "context" + "encoding/json" "fmt" kubefloworgv1beta1 "github.com/kubeflow/notebooks/workspaces/controller/api/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" models "github.com/kubeflow/notebooks/workspaces/backend/internal/models/workspaces" + action_models "github.com/kubeflow/notebooks/workspaces/backend/internal/models/workspaces/actions" ) -var ErrWorkspaceNotFound = fmt.Errorf("workspace not found") -var ErrWorkspaceAlreadyExists = fmt.Errorf("workspace already exists") +var ( + ErrWorkspaceNotFound = fmt.Errorf("workspace not found") + ErrWorkspaceAlreadyExists = fmt.Errorf("workspace already exists") + ErrWorkspaceInvalidState = fmt.Errorf("workspace is in an invalid state for this operation") +) type WorkspaceRepository struct { client client.Client @@ -214,3 +220,67 @@ func (r *WorkspaceRepository) DeleteWorkspace(ctx context.Context, namespace, wo return nil } + +// WorkspacePatchOperation represents a single JSONPatch operation +type WorkspacePatchOperation struct { + Op string `json:"op"` + Path string `json:"path"` + Value interface{} `json:"value,omitempty"` +} + +// HandlePauseAction handles pause/start operations for a workspace +func (r *WorkspaceRepository) HandlePauseAction(ctx context.Context, namespace, workspaceName string, workspaceActionPause *action_models.WorkspaceActionPause) (*action_models.WorkspaceActionPause, error) { + targetPauseState := workspaceActionPause.Paused + + // Build patch operations incrementally + patch := []WorkspacePatchOperation{ + { + Op: "test", + Path: "/spec/paused", + Value: !targetPauseState, // Test current state (opposite of target state) + }, + } + + // For start operations, add additional test for paused state + // "test" operations on JSON Patch only support strict equality checks, so we can't apply an additional test + // for pause operations on the workspace as we'd want to check the workspace state != paused. + if !targetPauseState { + patch = append(patch, WorkspacePatchOperation{ + Op: "test", + Path: "/status/state", + Value: kubefloworgv1beta1.WorkspaceStatePaused, + }) + } + + // Always add the replace operation + patch = append(patch, WorkspacePatchOperation{ + Op: "replace", + Path: "/spec/paused", + Value: targetPauseState, + }) + + patchBytes, err := json.Marshal(patch) + if err != nil { + return nil, fmt.Errorf("failed to marshal patch: %w", err) + } + + workspace := &kubefloworgv1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: workspaceName, + }, + } + + if err := r.client.Patch(ctx, workspace, client.RawPatch(types.JSONPatchType, patchBytes)); err != nil { + if apierrors.IsNotFound(err) { + return nil, ErrWorkspaceNotFound + } + if apierrors.IsInvalid(err) { + return nil, ErrWorkspaceInvalidState + } + return nil, fmt.Errorf("failed to patch workspace: %w", err) + } + + workspaceActionPauseModel := action_models.NewWorkspaceActionPauseFromWorkspace(workspace) + return workspaceActionPauseModel, nil +} diff --git a/workspaces/backend/openapi/docs.go b/workspaces/backend/openapi/docs.go index 7fa3f97d..d4324874 100644 --- a/workspaces/backend/openapi/docs.go +++ b/workspaces/backend/openapi/docs.go @@ -453,6 +453,104 @@ const docTemplate = `{ } } }, + "/workspaces/{namespace}/{workspaceName}/actions/pause": { + "post": { + "description": "Pauses or unpauses a workspace, stopping or resuming all associated pods.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "workspaces" + ], + "summary": "Pause or unpause a workspace", + "parameters": [ + { + "type": "string", + "x-example": "default", + "description": "Namespace of the workspace", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "x-example": "my-workspace", + "description": "Name of the workspace", + "name": "workspaceName", + "in": "path", + "required": true + }, + { + "description": "Intended pause state of the workspace", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.WorkspaceActionPauseEnvelope" + } + } + ], + "responses": { + "200": { + "description": "Successful action. Returns the current pause state.", + "schema": { + "$ref": "#/definitions/api.WorkspaceActionPauseEnvelope" + } + }, + "400": { + "description": "Bad Request.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "401": { + "description": "Unauthorized. Authentication is required.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "403": { + "description": "Forbidden. User does not have permission to access the workspace.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "404": { + "description": "Not Found. Workspace does not exist.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "413": { + "description": "Request Entity Too Large. The request body is too large.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "415": { + "description": "Unsupported Media Type. Content-Type header is not correct.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "422": { + "description": "Unprocessable Entity. Workspace is not in appropriate state.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "500": { + "description": "Internal server error. An unexpected error occurred on the server.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + } + } + } + }, "/workspaces/{namespace}/{workspace_name}": { "get": { "description": "Returns details of a specific workspace identified by namespace and workspace name.", @@ -592,6 +690,14 @@ const docTemplate = `{ } }, "definitions": { + "actions.WorkspaceActionPause": { + "type": "object", + "properties": { + "paused": { + "type": "boolean" + } + } + }, "api.ErrorCause": { "type": "object", "properties": { @@ -650,6 +756,14 @@ const docTemplate = `{ } } }, + "api.WorkspaceActionPauseEnvelope": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/actions.WorkspaceActionPause" + } + } + }, "api.WorkspaceCreateEnvelope": { "type": "object", "properties": { diff --git a/workspaces/backend/openapi/swagger.json b/workspaces/backend/openapi/swagger.json index b5c0e085..ff81e8bf 100644 --- a/workspaces/backend/openapi/swagger.json +++ b/workspaces/backend/openapi/swagger.json @@ -451,6 +451,104 @@ } } }, + "/workspaces/{namespace}/{workspaceName}/actions/pause": { + "post": { + "description": "Pauses or unpauses a workspace, stopping or resuming all associated pods.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "workspaces" + ], + "summary": "Pause or unpause a workspace", + "parameters": [ + { + "type": "string", + "x-example": "default", + "description": "Namespace of the workspace", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "x-example": "my-workspace", + "description": "Name of the workspace", + "name": "workspaceName", + "in": "path", + "required": true + }, + { + "description": "Intended pause state of the workspace", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.WorkspaceActionPauseEnvelope" + } + } + ], + "responses": { + "200": { + "description": "Successful action. Returns the current pause state.", + "schema": { + "$ref": "#/definitions/api.WorkspaceActionPauseEnvelope" + } + }, + "400": { + "description": "Bad Request.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "401": { + "description": "Unauthorized. Authentication is required.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "403": { + "description": "Forbidden. User does not have permission to access the workspace.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "404": { + "description": "Not Found. Workspace does not exist.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "413": { + "description": "Request Entity Too Large. The request body is too large.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "415": { + "description": "Unsupported Media Type. Content-Type header is not correct.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "422": { + "description": "Unprocessable Entity. Workspace is not in appropriate state.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + }, + "500": { + "description": "Internal server error. An unexpected error occurred on the server.", + "schema": { + "$ref": "#/definitions/api.ErrorEnvelope" + } + } + } + } + }, "/workspaces/{namespace}/{workspace_name}": { "get": { "description": "Returns details of a specific workspace identified by namespace and workspace name.", @@ -590,6 +688,14 @@ } }, "definitions": { + "actions.WorkspaceActionPause": { + "type": "object", + "properties": { + "paused": { + "type": "boolean" + } + } + }, "api.ErrorCause": { "type": "object", "properties": { @@ -648,6 +754,14 @@ } } }, + "api.WorkspaceActionPauseEnvelope": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/actions.WorkspaceActionPause" + } + } + }, "api.WorkspaceCreateEnvelope": { "type": "object", "properties": { From bdbfe1bbd2dba58f3f591b6efe6567ec0f5f23ab Mon Sep 17 00:00:00 2001 From: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> Date: Fri, 25 Jul 2025 14:35:01 -0300 Subject: [PATCH 45/68] fix(ws): update frontend to support latest start/stop API changes (#503) Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> --- .../src/app/context/WorkspaceActionsContext.tsx | 8 ++++++-- .../src/app/context/useNotebookAPIState.tsx | 4 ---- .../frontend/src/shared/api/backendApiTypes.ts | 2 -- workspaces/frontend/src/shared/api/callTypes.ts | 2 -- .../frontend/src/shared/api/notebookApi.ts | 7 +------ .../frontend/src/shared/api/notebookService.ts | 10 ++-------- .../frontend/src/shared/mock/mockBuilder.ts | 2 -- .../src/shared/mock/mockNotebookService.ts | 17 +++++------------ .../src/shared/mock/mockNotebookServiceData.ts | 4 ---- 9 files changed, 14 insertions(+), 42 deletions(-) diff --git a/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx b/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx index aac7efd9..57349d35 100644 --- a/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx +++ b/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx @@ -179,7 +179,9 @@ export const WorkspaceActionsContextProvider: React.FC - api.startWorkspace({}, selectedNamespace, activeWsAction.workspace.name) + api.pauseWorkspace({}, selectedNamespace, activeWsAction.workspace.name, { + data: { paused: false }, + }) } onActionDone={activeWsAction.onActionDone} onUpdateAndStart={async () => { @@ -200,7 +202,9 @@ export const WorkspaceActionsContextProvider: React.FC - api.pauseWorkspace({}, selectedNamespace, activeWsAction.workspace.name) + api.pauseWorkspace({}, selectedNamespace, activeWsAction.workspace.name, { + data: { paused: true }, + }) } onActionDone={activeWsAction.onActionDone} onUpdateAndStop={async () => { diff --git a/workspaces/frontend/src/app/context/useNotebookAPIState.tsx b/workspaces/frontend/src/app/context/useNotebookAPIState.tsx index ac0e9136..6b2e512e 100644 --- a/workspaces/frontend/src/app/context/useNotebookAPIState.tsx +++ b/workspaces/frontend/src/app/context/useNotebookAPIState.tsx @@ -15,7 +15,6 @@ import { patchWorkspace, patchWorkspaceKind, pauseWorkspace, - startWorkspace, updateWorkspace, updateWorkspaceKind, } from '~/shared/api/notebookService'; @@ -36,7 +35,6 @@ import { mockPatchWorkspace, mockPatchWorkspaceKind, mockPauseWorkspace, - mockStartWorkspace, mockUpdateWorkspace, mockUpdateWorkspaceKind, } from '~/shared/mock/mockNotebookService'; @@ -63,7 +61,6 @@ const useNotebookAPIState = ( patchWorkspace: patchWorkspace(path), deleteWorkspace: deleteWorkspace(path), pauseWorkspace: pauseWorkspace(path), - startWorkspace: startWorkspace(path), // WorkspaceKind listWorkspaceKinds: listWorkspaceKinds(path), createWorkspaceKind: createWorkspaceKind(path), @@ -90,7 +87,6 @@ const useNotebookAPIState = ( patchWorkspace: mockPatchWorkspace(path), deleteWorkspace: mockDeleteWorkspace(path), pauseWorkspace: mockPauseWorkspace(path), - startWorkspace: mockStartWorkspace(path), // WorkspaceKind listWorkspaceKinds: mockListWorkspaceKinds(path), createWorkspaceKind: mockCreateWorkspaceKind(path), diff --git a/workspaces/frontend/src/shared/api/backendApiTypes.ts b/workspaces/frontend/src/shared/api/backendApiTypes.ts index e84ca6b5..3f833058 100644 --- a/workspaces/frontend/src/shared/api/backendApiTypes.ts +++ b/workspaces/frontend/src/shared/api/backendApiTypes.ts @@ -278,8 +278,6 @@ export interface WorkspaceUpdate extends WorkspaceCreate {} export interface WorkspacePatch {} export interface WorkspacePauseState { - namespace: string; - workspaceName: string; paused: boolean; } diff --git a/workspaces/frontend/src/shared/api/callTypes.ts b/workspaces/frontend/src/shared/api/callTypes.ts index 04a09f2a..72a8d6bc 100644 --- a/workspaces/frontend/src/shared/api/callTypes.ts +++ b/workspaces/frontend/src/shared/api/callTypes.ts @@ -13,7 +13,6 @@ import { PatchWorkspace, PatchWorkspaceKind, PauseWorkspace, - StartWorkspace, UpdateWorkspace, UpdateWorkspaceKind, } from '~/shared/api/notebookApi'; @@ -38,7 +37,6 @@ export type UpdateWorkspaceAPI = KubeflowAPICall; export type PatchWorkspaceAPI = KubeflowAPICall; export type DeleteWorkspaceAPI = KubeflowAPICall; export type PauseWorkspaceAPI = KubeflowAPICall; -export type StartWorkspaceAPI = KubeflowAPICall; // WorkspaceKind export type ListWorkspaceKindsAPI = KubeflowAPICall; diff --git a/workspaces/frontend/src/shared/api/notebookApi.ts b/workspaces/frontend/src/shared/api/notebookApi.ts index d57a118f..bf2f2bce 100644 --- a/workspaces/frontend/src/shared/api/notebookApi.ts +++ b/workspaces/frontend/src/shared/api/notebookApi.ts @@ -52,11 +52,7 @@ export type PauseWorkspace = ( opts: APIOptions, namespace: string, workspace: string, -) => Promise; -export type StartWorkspace = ( - opts: APIOptions, - namespace: string, - workspace: string, + data: RequestData, ) => Promise; // WorkspaceKind @@ -89,7 +85,6 @@ export type NotebookAPIs = { patchWorkspace: PatchWorkspace; deleteWorkspace: DeleteWorkspace; pauseWorkspace: PauseWorkspace; - startWorkspace: StartWorkspace; // WorkspaceKind listWorkspaceKinds: ListWorkspaceKinds; getWorkspaceKind: GetWorkspaceKind; diff --git a/workspaces/frontend/src/shared/api/notebookService.ts b/workspaces/frontend/src/shared/api/notebookService.ts index 8bdaeccc..7df0890d 100644 --- a/workspaces/frontend/src/shared/api/notebookService.ts +++ b/workspaces/frontend/src/shared/api/notebookService.ts @@ -22,7 +22,6 @@ import { PatchWorkspaceAPI, PatchWorkspaceKindAPI, PauseWorkspaceAPI, - StartWorkspaceAPI, UpdateWorkspaceAPI, UpdateWorkspaceKindAPI, } from '~/shared/api/callTypes'; @@ -55,14 +54,9 @@ export const patchWorkspace: PatchWorkspaceAPI = (hostPath) => (opts, namespace, export const deleteWorkspace: DeleteWorkspaceAPI = (hostPath) => (opts, namespace, workspace) => wrapRequest(restDELETE(hostPath, `/workspaces/${namespace}/${workspace}`, {}, {}, opts), false); -export const pauseWorkspace: PauseWorkspaceAPI = (hostPath) => (opts, namespace, workspace) => +export const pauseWorkspace: PauseWorkspaceAPI = (hostPath) => (opts, namespace, workspace, data) => wrapRequest( - restCREATE(hostPath, `/workspaces/${namespace}/${workspace}/actions/pause`, {}, opts), - ); - -export const startWorkspace: StartWorkspaceAPI = (hostPath) => (opts, namespace, workspace) => - wrapRequest( - restCREATE(hostPath, `/workspaces/${namespace}/${workspace}/actions/start`, {}, opts), + restCREATE(hostPath, `/workspaces/${namespace}/${workspace}/actions/pause`, data, {}, opts), ); export const listWorkspaceKinds: ListWorkspaceKindsAPI = (hostPath) => (opts) => diff --git a/workspaces/frontend/src/shared/mock/mockBuilder.ts b/workspaces/frontend/src/shared/mock/mockBuilder.ts index 3efcad00..e15d045c 100644 --- a/workspaces/frontend/src/shared/mock/mockBuilder.ts +++ b/workspaces/frontend/src/shared/mock/mockBuilder.ts @@ -281,8 +281,6 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial): export const buildMockPauseStateResponse = ( pauseState?: Partial, ): WorkspacePauseState => ({ - namespace: 'default', - workspaceName: 'My First Jupyter Notebook', paused: true, ...pauseState, }); diff --git a/workspaces/frontend/src/shared/mock/mockNotebookService.ts b/workspaces/frontend/src/shared/mock/mockNotebookService.ts index 0bb63e16..78767f03 100644 --- a/workspaces/frontend/src/shared/mock/mockNotebookService.ts +++ b/workspaces/frontend/src/shared/mock/mockNotebookService.ts @@ -15,7 +15,6 @@ import { PatchWorkspaceAPI, PatchWorkspaceKindAPI, PauseWorkspaceAPI, - StartWorkspaceAPI, UpdateWorkspaceAPI, UpdateWorkspaceKindAPI, } from '~/shared/api/callTypes'; @@ -23,8 +22,6 @@ import { mockAllWorkspaces, mockedHealthCheckResponse, mockNamespaces, - mockPausedStateResponse, - mockStartedStateResponse, mockWorkspace1, mockWorkspaceKind1, mockWorkspaceKinds, @@ -58,15 +55,11 @@ export const mockDeleteWorkspace: DeleteWorkspaceAPI = () => async () => { await delay(1500); }; -export const mockPauseWorkspace: PauseWorkspaceAPI = () => async (_opts, namespace, workspace) => { - await delay(1500); - return { ...mockPausedStateResponse, namespace, workspaceName: workspace }; -}; - -export const mockStartWorkspace: StartWorkspaceAPI = () => async (_opts, namespace, workspace) => { - await delay(1500); - return { ...mockStartedStateResponse, namespace, workspaceName: workspace }; -}; +export const mockPauseWorkspace: PauseWorkspaceAPI = + () => async (_opts, _namespace, _workspace, requestData) => { + await delay(1500); + return { paused: requestData.data.paused }; + }; export const mockListWorkspaceKinds: ListWorkspaceKindsAPI = () => async () => mockWorkspaceKinds; diff --git a/workspaces/frontend/src/shared/mock/mockNotebookServiceData.ts b/workspaces/frontend/src/shared/mock/mockNotebookServiceData.ts index 6c689295..8ff2e181 100644 --- a/workspaces/frontend/src/shared/mock/mockNotebookServiceData.ts +++ b/workspaces/frontend/src/shared/mock/mockNotebookServiceData.ts @@ -7,7 +7,6 @@ import { import { buildMockHealthCheckResponse, buildMockNamespace, - buildMockPauseStateResponse, buildMockWorkspace, buildMockWorkspaceKind, buildMockWorkspaceKindInfo, @@ -163,6 +162,3 @@ export const mockAllWorkspaces = [ kind: mockWorkspaceKindInfo1, }), ]; - -export const mockPausedStateResponse = buildMockPauseStateResponse({ paused: true }); -export const mockStartedStateResponse = buildMockPauseStateResponse({ paused: false }); From 7bed0beec1ff5a47da76d0dab9551bf16b2742f9 Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Tue, 29 Jul 2025 07:18:47 -0400 Subject: [PATCH 46/68] fix(ws): Refactors toolbar and filter logic to fix "clear all filters" bug in workspace list view (#502) Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> remove comment fix(ws): remove set to first page when filters applied Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> fix tests for filterWorkspacesTest fix single filter test Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> fix bug in ws kind table --- .../workspaces/filterWorkspacesTest.cy.ts | 8 + .../src/app/components/WorkspaceTable.tsx | 315 ++++++++++++------ .../pages/WorkspaceKinds/WorkspaceKinds.tsx | 271 ++++----------- .../summary/WorkspaceKindSummary.tsx | 15 +- .../WorkspaceKindSummaryExpandableCard.tsx | 9 +- .../frontend/src/shared/style/MUI-theme.scss | 34 +- 6 files changed, 328 insertions(+), 324 deletions(-) diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/filterWorkspacesTest.cy.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/filterWorkspacesTest.cy.ts index 748f4dd7..968d2269 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/filterWorkspacesTest.cy.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/filterWorkspacesTest.cy.ts @@ -16,13 +16,21 @@ describe('Application', () => { cy.intercept('GET', '/api/v1/namespaces', { body: mockBFFResponse(mockNamespaces), }); + cy.intercept('GET', '/api/v1/workspaces', { + body: mockBFFResponse(mockWorkspaces), + }).as('getWorkspaces'); cy.intercept('GET', '/api/v1/workspaces/default', { body: mockBFFResponse(mockWorkspaces), }); cy.intercept('GET', '/api/namespaces/test-namespace/workspaces').as('getWorkspaces'); }); + it('filter rows with single filter', () => { home.visit(); + + // Wait for the API call before trying to interact with the UI + cy.wait('@getWorkspaces'); + useFilter('name', 'Name', 'My'); cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 2); cy.get("[id$='workspaces-table-row-1']").contains('My First Jupyter Notebook'); diff --git a/workspaces/frontend/src/app/components/WorkspaceTable.tsx b/workspaces/frontend/src/app/components/WorkspaceTable.tsx index 8be07a03..a29bf1bb 100644 --- a/workspaces/frontend/src/app/components/WorkspaceTable.tsx +++ b/workspaces/frontend/src/app/components/WorkspaceTable.tsx @@ -1,5 +1,4 @@ -import React, { useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react'; -import { PageSection } from '@patternfly/react-core/dist/esm/components/Page'; +import React, { useCallback, useImperativeHandle, useMemo, useState } from 'react'; import { TimestampTooltipVariant, Timestamp, @@ -14,6 +13,20 @@ import { Tooltip } from '@patternfly/react-core/dist/esm/components/Tooltip'; import { Bullseye } from '@patternfly/react-core/dist/esm/layouts/Bullseye'; import { Button } from '@patternfly/react-core/dist/esm/components/Button'; import { Icon } from '@patternfly/react-core/dist/esm/components/Icon'; +import { + Toolbar, + ToolbarContent, + ToolbarItem, + ToolbarGroup, + ToolbarFilter, + ToolbarToggleGroup, +} from '@patternfly/react-core/dist/esm/components/Toolbar'; +import { + Select, + SelectList, + SelectOption, +} from '@patternfly/react-core/dist/esm/components/Select'; +import { MenuToggle } from '@patternfly/react-core/dist/esm/components/MenuToggle'; import { Table, Thead, @@ -25,18 +38,14 @@ import { ActionsColumn, IActions, } from '@patternfly/react-table/dist/esm/components/Table'; +import { FilterIcon } from '@patternfly/react-icons/dist/esm/icons/filter-icon'; import { InfoCircleIcon } from '@patternfly/react-icons/dist/esm/icons/info-circle-icon'; import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; import { TimesCircleIcon } from '@patternfly/react-icons/dist/esm/icons/times-circle-icon'; import { QuestionCircleIcon } from '@patternfly/react-icons/dist/esm/icons/question-circle-icon'; import { formatDistanceToNow } from 'date-fns/formatDistanceToNow'; import { Workspace, WorkspaceState } from '~/shared/api/backendApiTypes'; -import { - DataFieldKey, - defineDataFields, - FilterableDataFieldKey, - SortableDataFieldKey, -} from '~/app/filterableDataHelper'; +import { DataFieldKey, defineDataFields, SortableDataFieldKey } from '~/app/filterableDataHelper'; import { useTypedNavigate } from '~/app/routerHelper'; import { buildKindLogoDictionary, @@ -44,8 +53,7 @@ import { } from '~/app/actions/WorkspaceKindsActions'; import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds'; import { WorkspaceConnectAction } from '~/app/pages/Workspaces/WorkspaceConnectAction'; -import CustomEmptyState from '~/shared/components/CustomEmptyState'; -import Filter, { FilteredColumn, FilterRef } from '~/shared/components/Filter'; +import ThemeAwareSearchInput from '~/app/components/ThemeAwareSearchInput'; import WithValidImage from '~/shared/components/WithValidImage'; import ImageFallback from '~/shared/components/ImageFallback'; import { @@ -53,12 +61,12 @@ import { formatWorkspaceIdleState, } from '~/shared/utilities/WorkspaceUtils'; import { ExpandedWorkspaceRow } from '~/app/pages/Workspaces/ExpandedWorkspaceRow'; +import CustomEmptyState from '~/shared/components/CustomEmptyState'; const { fields: wsTableColumns, keyArray: wsTableColumnKeyArray, sortableKeyArray: sortableWsTableColumnKeyArray, - filterableKeyArray: filterableWsTableColumnKeyArray, } = defineDataFields({ name: { label: 'Name', isFilterable: true, isSortable: true, width: 35 }, image: { label: 'Image', isFilterable: true, isSortable: true, width: 25 }, @@ -73,21 +81,40 @@ const { }); export type WorkspaceTableColumnKeys = DataFieldKey; -type WorkspaceTableFilterableColumnKeys = FilterableDataFieldKey; type WorkspaceTableSortableColumnKeys = SortableDataFieldKey; -export type WorkspaceTableFilteredColumn = FilteredColumn; interface WorkspaceTableProps { workspaces: Workspace[]; canCreateWorkspaces?: boolean; canExpandRows?: boolean; - initialFilters?: WorkspaceTableFilteredColumn[]; hiddenColumns?: WorkspaceTableColumnKeys[]; rowActions?: (workspace: Workspace) => IActions; } +const allFiltersConfig = { + name: { label: 'Name', placeholder: 'Filter by name' }, + kind: { label: 'Kind', placeholder: 'Filter by kind' }, + image: { label: 'Image', placeholder: 'Filter by image' }, + state: { label: 'State', placeholder: 'Filter by state' }, + namespace: { label: 'Namespace' }, + idleGpu: { label: 'Idle GPU' }, +} as const; + +// Defines which of the above filters should appear in the dropdown +const dropdownFilterKeys = ['name', 'kind', 'image', 'state'] as const; + +const filterConfigs = dropdownFilterKeys.map((key) => ({ + key, + label: allFiltersConfig[key].label, + placeholder: allFiltersConfig[key].placeholder!, // '!' asserts placeholder is not undefined here +})); + +type FilterKey = keyof typeof allFiltersConfig; +type FilterLabel = (typeof allFiltersConfig)[FilterKey]['label']; + export interface WorkspaceTableRef { - addFilter: (filter: WorkspaceTableFilteredColumn) => void; + clearAllFilters: () => void; + setFilter: (key: FilterKey, value: string) => void; } const WorkspaceTable = React.forwardRef( @@ -96,7 +123,6 @@ const WorkspaceTable = React.forwardRef( workspaces, canCreateWorkspaces = true, canExpandRows = true, - initialFilters = [], hiddenColumns = [], rowActions = () => [], }, @@ -104,7 +130,15 @@ const WorkspaceTable = React.forwardRef( ) => { const [workspaceKinds] = useWorkspaceKinds(); const [expandedWorkspacesNames, setExpandedWorkspacesNames] = useState([]); - const [filters, setFilters] = useState(initialFilters); + const [filters, setFilters] = useState>({ + name: '', + kind: '', + image: '', + state: '', + namespace: '', + idleGpu: '', + }); + const [activeSortColumnKey, setActiveSortColumnKey] = useState(null); const [activeSortDirection, setActiveSortDirection] = useState<'asc' | 'desc' | null>(null); @@ -112,10 +146,66 @@ const WorkspaceTable = React.forwardRef( const [perPage, setPerPage] = useState(10); const navigate = useTypedNavigate(); - const filterRef = useRef(null); const kindLogoDict = buildKindLogoDictionary(workspaceKinds); const workspaceRedirectStatus = buildWorkspaceRedirectStatus(workspaceKinds); + // Use the derived FilterLabel type for the active menu + const [activeAttributeMenu, setActiveAttributeMenu] = useState('Name'); + const [isAttributeMenuOpen, setIsAttributeMenuOpen] = useState(false); + + const handleFilterChange = useCallback((key: FilterKey, value: string) => { + setFilters((prev) => ({ ...prev, [key]: value })); + }, []); + + const clearAllFilters = useCallback(() => { + setFilters({ + name: '', + kind: '', + image: '', + state: '', + namespace: '', + idleGpu: '', + }); + }, []); + + const onAttributeToggleClick = useCallback(() => { + setIsAttributeMenuOpen((prev) => !prev); + }, []); + + const attributeDropdown = useMemo( + () => ( + + ), + [isAttributeMenuOpen, activeAttributeMenu, onAttributeToggleClick], + ); + const visibleColumnKeys: WorkspaceTableColumnKeys[] = useMemo( () => hiddenColumns.length @@ -129,39 +219,32 @@ const WorkspaceTable = React.forwardRef( [visibleColumnKeys], ); - const visibleFilterableColumnKeys: WorkspaceTableFilterableColumnKeys[] = useMemo( - () => filterableWsTableColumnKeyArray.filter((col) => visibleColumnKeys.includes(col)), - [visibleColumnKeys], - ); - - const visibleFilterableColumnMap = useMemo( - () => - Object.fromEntries( - visibleFilterableColumnKeys.map((key) => [key, wsTableColumns[key].label]), - ) as Record, - [visibleFilterableColumnKeys], - ); - useImperativeHandle(ref, () => ({ - addFilter: (newFilter: WorkspaceTableFilteredColumn) => { - if (!visibleFilterableColumnKeys.includes(newFilter.columnKey)) { - return; - } - - setFilters((prev) => { - const existingIndex = prev.findIndex((f) => f.columnKey === newFilter.columnKey); - if (existingIndex !== -1) { - return prev.map((f, i) => (i === existingIndex ? newFilter : f)); - } - return [...prev, newFilter]; - }); - }, + clearAllFilters, + setFilter: handleFilterChange, })); const createWorkspace = useCallback(() => { navigate('workspaceCreate'); }, [navigate]); + const emptyState = useMemo( + () => , + [clearAllFilters], + ); + + const filterableProperties: Record string> = useMemo( + () => ({ + name: (ws) => ws.name, + kind: (ws) => ws.workspaceKind.name, + image: (ws) => ws.podTemplate.options.imageConfig.current.displayName, + state: (ws) => ws.state, + namespace: (ws) => ws.namespace, + idleGpu: (ws) => formatWorkspaceIdleState(ws), + }), + [], + ); + const setWorkspaceExpanded = (workspace: Workspace, isExpanding = true) => setExpandedWorkspacesNames((prevExpanded) => { const newExpandedWorkspacesNames = prevExpanded.filter( @@ -179,37 +262,29 @@ const WorkspaceTable = React.forwardRef( if (workspaces.length === 0) { return []; } - - return filters.reduce((result, filter) => { - let searchValueInput: RegExp; - try { - searchValueInput = new RegExp(filter.value, 'i'); - } catch { - searchValueInput = new RegExp(filter.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'); + const testRegex = (value: string, searchValue: string) => { + if (!searchValue) { + return true; } + try { + return new RegExp(searchValue, 'i').test(value); + } catch { + return new RegExp(searchValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i').test(value); + } + }; - return result.filter((ws) => { - switch (filter.columnKey as WorkspaceTableFilterableColumnKeys) { - case 'name': - return ws.name.match(searchValueInput); - case 'kind': - return ws.workspaceKind.name.match(searchValueInput); - case 'namespace': - return ws.namespace.match(searchValueInput); - case 'image': - return ws.podTemplate.options.imageConfig.current.displayName.match(searchValueInput); - case 'state': - return ws.state.match(searchValueInput); - case 'gpu': - return formatResourceFromWorkspace(ws, 'gpu').match(searchValueInput); - case 'idleGpu': - return formatWorkspaceIdleState(ws).match(searchValueInput); - default: - return true; - } - }); - }, workspaces); - }, [workspaces, filters]); + const activeFilters = Object.entries(filters).filter(([, value]) => value); + if (activeFilters.length === 0) { + return workspaces; + } + + return workspaces.filter((ws) => + activeFilters.every(([key, searchValue]) => { + const propertyGetter = filterableProperties[key as FilterKey]; + return testRegex(propertyGetter(ws), searchValue); + }), + ); + }, [workspaces, filters, filterableProperties]); // Column sorting @@ -317,8 +392,6 @@ const WorkspaceTable = React.forwardRef( } }; - // Redirect Status Icons - const getRedirectStatusIcon = (level: string | undefined, message: string) => { switch (level) { case 'Info': @@ -383,22 +456,68 @@ const WorkspaceTable = React.forwardRef( }; return ( - + <> - - Create workspace - - ) - } - /> + + + } breakpoint="xl"> + + {attributeDropdown} + {filterConfigs.map(({ key, label, placeholder }) => ( + handleFilterChange(key, '')} + deleteLabelGroup={() => handleFilterChange(key, '')} + categoryName={label} + showToolbarItem={activeAttributeMenu === label} + > + + handleFilterChange(key, value)} + placeholder={placeholder} + fieldLabel={placeholder} + aria-label={placeholder} + data-testid="filter-workspaces-search-input" + /> + + + ))} + {Object.entries(filters).map(([key, value]) => { + // Check if the key is not in the dropdown config and has a value + const isWsSummaryFilter = !filterConfigs.some((config) => config.key === key); + if (!isWsSummaryFilter || !value) { + return null; + } + + return ( + handleFilterChange(key as FilterKey, '')} + categoryName={allFiltersConfig[key as FilterKey].label} + // eslint-disable-next-line react/no-children-prop + children={undefined} + /> + ); + })} + {canCreateWorkspaces && ( + + + + )} + + + + ( ))} {sortedWorkspaces.length === 0 && ( - - - - - + + + )}
- - filterRef.current?.clearAll()} /> - -
+ {emptyState} +
( onSetPage={onSetPage} onPerPageSelect={onPerPageSelect} /> -
+ ); }, ); diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx index d0868e40..ea3bff29 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { Drawer, DrawerContent, @@ -17,13 +17,11 @@ import { ToolbarToggleGroup, } from '@patternfly/react-core/dist/esm/components/Toolbar'; import { - Menu, - MenuContent, - MenuList, - MenuItem, -} from '@patternfly/react-core/dist/esm/components/Menu'; + Select, + SelectList, + SelectOption, +} from '@patternfly/react-core/dist/esm/components/Select'; import { MenuToggle } from '@patternfly/react-core/dist/esm/components/MenuToggle'; -import { Popper } from '@patternfly/react-core/helpers'; import { Bullseye } from '@patternfly/react-core/dist/esm/layouts/Bullseye'; import { Button } from '@patternfly/react-core/dist/esm/components/Button'; import { @@ -200,110 +198,40 @@ export const WorkspaceKinds: React.FunctionComponent = () => { // Set up status single select const [isStatusMenuOpen, setIsStatusMenuOpen] = useState(false); - const statusToggleRef = useRef(null); - const statusMenuRef = useRef(null); - const statusContainerRef = useRef(null); - const handleStatusMenuKeys = useCallback( - (event: KeyboardEvent) => { - if (isStatusMenuOpen && statusMenuRef.current?.contains(event.target as Node)) { - if (event.key === 'Escape' || event.key === 'Tab') { - setIsStatusMenuOpen(!isStatusMenuOpen); - statusToggleRef.current?.focus(); - } - } - }, - [isStatusMenuOpen], - ); + const onStatusSelect = ( + _event: React.MouseEvent | undefined, + value: string | number | undefined, + ) => { + if (typeof value === 'undefined') { + return; + } + setStatusSelection(value.toString()); + setIsStatusMenuOpen(false); + }; - const handleStatusClickOutside = useCallback( - (event: MouseEvent) => { - if (isStatusMenuOpen && !statusMenuRef.current?.contains(event.target as Node)) { - setIsStatusMenuOpen(false); - } - }, - [isStatusMenuOpen], - ); - - useEffect(() => { - window.addEventListener('keydown', handleStatusMenuKeys); - window.addEventListener('click', handleStatusClickOutside); - return () => { - window.removeEventListener('keydown', handleStatusMenuKeys); - window.removeEventListener('click', handleStatusClickOutside); - }; - }, [isStatusMenuOpen, statusMenuRef, handleStatusClickOutside, handleStatusMenuKeys]); - - const onStatusToggleClick = useCallback((ev: React.MouseEvent) => { - ev.stopPropagation(); - setTimeout(() => { - const firstElement = statusMenuRef.current?.querySelector('li > button:not(:disabled)'); - if (firstElement) { - (firstElement as HTMLElement).focus(); - } - }, 0); - setIsStatusMenuOpen((prev) => !prev); - }, []); - - const onStatusSelect = useCallback( - (event: React.MouseEvent | undefined, itemId: string | number | undefined) => { - if (typeof itemId === 'undefined') { - return; - } - - setStatusSelection(itemId.toString()); - setIsStatusMenuOpen((prev) => !prev); - }, - [], - ); - - const statusToggle = useMemo( - () => ( - - Filter by status - - ), - [isStatusMenuOpen, onStatusToggleClick], - ); - - const statusMenu = useMemo( - () => ( - - - - Deprecated - Active - - - - ), - [statusSelection, onStatusSelect], - ); - - const statusSelect = useMemo( - () => ( -
- -
- ), - [statusToggle, statusMenu, isStatusMenuOpen], + const statusSelect = ( + ); // Set up attribute selector @@ -311,108 +239,33 @@ export const WorkspaceKinds: React.FunctionComponent = () => { 'Name', ); const [isAttributeMenuOpen, setIsAttributeMenuOpen] = useState(false); - const attributeToggleRef = useRef(null); - const attributeMenuRef = useRef(null); - const attributeContainerRef = useRef(null); - const handleAttributeMenuKeys = useCallback( - (event: KeyboardEvent) => { - if (!isAttributeMenuOpen) { - return; - } - if ( - attributeMenuRef.current?.contains(event.target as Node) || - attributeToggleRef.current?.contains(event.target as Node) - ) { - if (event.key === 'Escape' || event.key === 'Tab') { - setIsAttributeMenuOpen(!isAttributeMenuOpen); - attributeToggleRef.current?.focus(); - } - } - }, - [isAttributeMenuOpen], - ); - - const handleAttributeClickOutside = useCallback( - (event: MouseEvent) => { - if (isAttributeMenuOpen && !attributeMenuRef.current?.contains(event.target as Node)) { + const attributeDropdown = ( + ); const emptyState = useMemo( diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummary.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummary.tsx index c1f2b94a..18367fca 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummary.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummary.tsx @@ -5,10 +5,7 @@ import { Stack, StackItem } from '@patternfly/react-core/dist/esm/layouts/Stack' import { Breadcrumb } from '@patternfly/react-core/dist/esm/components/Breadcrumb'; import { BreadcrumbItem } from '@patternfly/react-core/dist/esm/components/Breadcrumb/BreadcrumbItem'; import { useTypedLocation, useTypedParams } from '~/app/routerHelper'; -import WorkspaceTable, { - WorkspaceTableFilteredColumn, - WorkspaceTableRef, -} from '~/app/components/WorkspaceTable'; +import WorkspaceTable, { WorkspaceTableRef } from '~/app/components/WorkspaceTable'; import { useWorkspacesByKind } from '~/app/hooks/useWorkspaces'; import WorkspaceKindSummaryExpandableCard from '~/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard'; import { DEFAULT_POLLING_RATE_MS } from '~/app/const'; @@ -37,11 +34,17 @@ const WorkspaceKindSummary: React.FC = () => { const tableRowActions = useWorkspaceRowActions([{ id: 'viewDetails' }]); const onAddFilter = useCallback( - (filter: WorkspaceTableFilteredColumn) => { + (columnKey: string, value: string) => { if (!workspaceTableRef.current) { return; } - workspaceTableRef.current.addFilter(filter); + // Map to valid filter keys from WorkspaceTable + const validKeys = ['name', 'kind', 'image', 'state', 'namespace', 'idleGpu'] as const; + type ValidKey = (typeof validKeys)[number]; + + if (validKeys.includes(columnKey as ValidKey)) { + workspaceTableRef.current.setFilter(columnKey as ValidKey, value); + } }, [workspaceTableRef], ); diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard.tsx index 66e4af9d..6373c059 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard.tsx @@ -20,7 +20,6 @@ import { groupWorkspacesByNamespaceAndGpu, YesNoValue, } from '~/shared/utilities/WorkspaceUtils'; -import { WorkspaceTableFilteredColumn } from '~/app/components/WorkspaceTable'; const TOP_GPU_CONSUMERS_LIMIT = 2; @@ -28,7 +27,7 @@ interface WorkspaceKindSummaryExpandableCardProps { workspaces: Workspace[]; isExpanded: boolean; onExpandToggle: () => void; - onAddFilter: (filter: WorkspaceTableFilteredColumn) => void; + onAddFilter: (columnKey: string, value: string) => void; } const WorkspaceKindSummaryExpandableCard: React.FC = ({ @@ -73,7 +72,7 @@ const WorkspaceKindSummaryExpandableCard: React.FC { - onAddFilter({ columnKey: 'idleGpu', value: YesNoValue.Yes }); + onAddFilter('idleGpu', YesNoValue.Yes); }} > {filterIdleWorkspacesWithGpu(workspaces).length} @@ -141,7 +140,7 @@ const SectionDivider: React.FC = () => ( interface NamespaceConsumerProps { namespace: string; gpuCount: number; - onAddFilter: (filter: WorkspaceTableFilteredColumn) => void; + onAddFilter: (columnKey: string, value: string) => void; } const NamespaceGpuConsumer: React.FC = ({ @@ -154,7 +153,7 @@ const NamespaceGpuConsumer: React.FC = ({ variant="link" isInline onClick={() => { - onAddFilter({ columnKey: 'namespace', value: namespace }); + onAddFilter('namespace', namespace); }} > {namespace} diff --git a/workspaces/frontend/src/shared/style/MUI-theme.scss b/workspaces/frontend/src/shared/style/MUI-theme.scss index ddaf6135..9d98e53c 100644 --- a/workspaces/frontend/src/shared/style/MUI-theme.scss +++ b/workspaces/frontend/src/shared/style/MUI-theme.scss @@ -510,8 +510,8 @@ .mui-theme .pf-v6-c-menu { --pf-v6-c-menu--BoxShadow: var(--mui-shadows-8); --pf-v6-c-menu--BorderRadius: var(--mui-shape-borderRadius); - --pf-v6-c-menu--PaddingBlockStart: var(--mui-spacing); - --pf-v6-c-menu--PaddingBlockEnd: var(--mui-spacing); + --pf-v6-c-menu--PaddingBlockStart: none; + --pf-v6-c-menu--PaddingBlockEnd: none; --pf-v6-c-menu--PaddingInlineStart: var(--mui-spacing); --pf-v6-c-menu--PaddingInlineEnd: var(--mui-spacing); --pf-v6-c-menu__item--PaddingBlockStart: var(--mui-menu__item--PaddingBlockStart); @@ -546,6 +546,8 @@ font-weight: var(--mui-button-font-weight); letter-spacing: 0.02857em; + height: 37px; + } .mui-theme .pf-v6-c-menu-toggle__button { @@ -919,7 +921,7 @@ margin-block-end: var(--mui-spacing-16px); } -.workspacekind-file-upload { +.mui-theme .workspacekind-file-upload { height: 100%; .pf-v6-c-file-upload__file-details { @@ -928,7 +930,31 @@ } /* Workaround for Toggle group header in Workspace Kind Form */ -.workspace-kind-form-header .pf-v6-c-toggle-group__button.pf-m-selected { +.mui-theme .workspace-kind-form-header .pf-v6-c-toggle-group__button.pf-m-selected { background-color: #e0f0ff; color: var(--pf-t--color--black); } + + +.mui-theme .pf-v6-c-menu__item { + &.pf-m-selected { + --pf-v6-c-menu__item--BackgroundColor: rgba( + var(--mui-palette-primary-mainChannel, 25 118 210) / + var(--mui-palette-action-selectedOpacity, 0.08) + ); + --pf-v6-c-menu__item--FontWeight: var(--mui-button-font-weight); + + .pf-v6-c-menu__item-select-icon { + visibility: hidden; + } + } +} + +.mui-theme button.pf-v6-c-menu-toggle { + // Use box-shadow to create a border effect without affecting the layout + &.pf-m-expanded, + &:focus { + box-shadow: 0 0 0 2px var(--mui-palette-primary-main); + outline: none; // Remove default browser outline + } +} From 7a6bb30e76858898c2c773edb332e8221907d040 Mon Sep 17 00:00:00 2001 From: Paulo Rego <832830+paulovmr@users.noreply.github.com> Date: Tue, 29 Jul 2025 16:19:47 -0300 Subject: [PATCH 47/68] feat(ws): fix workspaces table pagination (#506) Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> --- .../src/app/components/WorkspaceTable.tsx | 242 +++++++++--------- 1 file changed, 122 insertions(+), 120 deletions(-) diff --git a/workspaces/frontend/src/app/components/WorkspaceTable.tsx b/workspaces/frontend/src/app/components/WorkspaceTable.tsx index a29bf1bb..44c86006 100644 --- a/workspaces/frontend/src/app/components/WorkspaceTable.tsx +++ b/workspaces/frontend/src/app/components/WorkspaceTable.tsx @@ -548,128 +548,130 @@ const WorkspaceTable = React.forwardRef( {sortedWorkspaces.length > 0 && - sortedWorkspaces.map((workspace, rowIndex) => ( - - ( + - {canExpandRows && ( - - setWorkspaceExpanded(workspace, !isWorkspaceExpanded(workspace)), - }} + + {canExpandRows && ( + + setWorkspaceExpanded(workspace, !isWorkspaceExpanded(workspace)), + }} + /> + )} + {visibleColumnKeys.map((columnKey) => { + if (columnKey === 'connect') { + return ( + + + + ); + } + + if (columnKey === 'actions') { + return ( + + ({ + ...action, + 'data-testid': `action-${action.id || ''}`, + }))} + /> + + ); + } + + return ( + + {columnKey === 'name' && workspace.name} + {columnKey === 'image' && ( + + {workspace.podTemplate.options.imageConfig.current.displayName}{' '} + {workspaceRedirectStatus[workspace.workspaceKind.name] + ? getRedirectStatusIcon( + workspaceRedirectStatus[workspace.workspaceKind.name]?.message + ?.level, + workspaceRedirectStatus[workspace.workspaceKind.name]?.message + ?.text || 'No API response available', + ) + : getRedirectStatusIcon(undefined, 'No API response available')} + + )} + {columnKey === 'kind' && ( + + } + > + {(validSrc) => ( + + {workspace.workspaceKind.name} + + )} + + )} + {columnKey === 'namespace' && workspace.namespace} + {columnKey === 'state' && ( + + )} + {columnKey === 'gpu' && formatResourceFromWorkspace(workspace, 'gpu')} + {columnKey === 'idleGpu' && formatWorkspaceIdleState(workspace)} + {columnKey === 'lastActivity' && ( + + {formatDistanceToNow(new Date(workspace.activity.lastActivity), { + addSuffix: true, + })} + + )} + + ); + })} + + {isWorkspaceExpanded(workspace) && ( + )} - {visibleColumnKeys.map((columnKey) => { - if (columnKey === 'connect') { - return ( - - - - ); - } - - if (columnKey === 'actions') { - return ( - - ({ - ...action, - 'data-testid': `action-${action.id || ''}`, - }))} - /> - - ); - } - - return ( - - {columnKey === 'name' && workspace.name} - {columnKey === 'image' && ( - - {workspace.podTemplate.options.imageConfig.current.displayName}{' '} - {workspaceRedirectStatus[workspace.workspaceKind.name] - ? getRedirectStatusIcon( - workspaceRedirectStatus[workspace.workspaceKind.name]?.message - ?.level, - workspaceRedirectStatus[workspace.workspaceKind.name]?.message - ?.text || 'No API response available', - ) - : getRedirectStatusIcon(undefined, 'No API response available')} - - )} - {columnKey === 'kind' && ( - - } - > - {(validSrc) => ( - - {workspace.workspaceKind.name} - - )} - - )} - {columnKey === 'namespace' && workspace.namespace} - {columnKey === 'state' && ( - - )} - {columnKey === 'gpu' && formatResourceFromWorkspace(workspace, 'gpu')} - {columnKey === 'idleGpu' && formatWorkspaceIdleState(workspace)} - {columnKey === 'lastActivity' && ( - - {formatDistanceToNow(new Date(workspace.activity.lastActivity), { - addSuffix: true, - })} - - )} - - ); - })} - - {isWorkspaceExpanded(workspace) && ( - - )} - - ))} + + ))} {sortedWorkspaces.length === 0 && ( @@ -679,7 +681,7 @@ const WorkspaceTable = React.forwardRef( )} Date: Tue, 29 Jul 2025 16:20:47 -0300 Subject: [PATCH 48/68] feat(ws): use workspace counts from API response (#508) Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> --- .../app/context/NamespaceContextProvider.tsx | 2 +- .../useWorkspaceCountPerKind.spec.tsx | 341 ++++++++++++++++++ .../src/app/hooks/useWorkspaceCountPerKind.ts | 111 ++++-- .../details/WorkspaceKindDetailsTable.tsx | 2 +- .../src/shared/api/backendApiTypes.ts | 7 + .../frontend/src/shared/mock/mockBuilder.ts | 21 ++ .../shared/mock/mockNotebookServiceData.ts | 9 + 7 files changed, 461 insertions(+), 32 deletions(-) create mode 100644 workspaces/frontend/src/app/hooks/__tests__/useWorkspaceCountPerKind.spec.tsx diff --git a/workspaces/frontend/src/app/context/NamespaceContextProvider.tsx b/workspaces/frontend/src/app/context/NamespaceContextProvider.tsx index 09781c4d..c62a1981 100644 --- a/workspaces/frontend/src/app/context/NamespaceContextProvider.tsx +++ b/workspaces/frontend/src/app/context/NamespaceContextProvider.tsx @@ -38,7 +38,7 @@ export const NamespaceContextProvider: React.FC = const namespaceNames = namespacesData.map((ns) => ns.name); setNamespaces(namespaceNames); setSelectedNamespace(lastUsedNamespace.length ? lastUsedNamespace : namespaceNames[0]); - if (!lastUsedNamespace.length) { + if (!lastUsedNamespace.length || !namespaceNames.includes(lastUsedNamespace)) { setLastUsedNamespace(storageKey, namespaceNames[0]); } } else { diff --git a/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceCountPerKind.spec.tsx b/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceCountPerKind.spec.tsx new file mode 100644 index 00000000..ff740970 --- /dev/null +++ b/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceCountPerKind.spec.tsx @@ -0,0 +1,341 @@ +import { waitFor } from '@testing-library/react'; +import { renderHook } from '~/__tests__/unit/testUtils/hooks'; +import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; +import { useWorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind'; +import { + Workspace, + WorkspaceImageConfigValue, + WorkspaceKind, + WorkspaceKindInfo, + WorkspacePodConfigValue, +} from '~/shared/api/backendApiTypes'; +import { NotebookAPIs } from '~/shared/api/notebookApi'; +import { buildMockWorkspace, buildMockWorkspaceKind } from '~/shared/mock/mockBuilder'; + +jest.mock('~/app/hooks/useNotebookAPI', () => ({ + useNotebookAPI: jest.fn(), +})); + +const mockUseNotebookAPI = useNotebookAPI as jest.MockedFunction; + +const baseWorkspaceKindInfoTest: WorkspaceKindInfo = { + name: 'jupyter', + missing: false, + icon: { url: '' }, + logo: { url: '' }, +}; + +const baseWorkspaceTest = buildMockWorkspace({ + name: 'workspace', + namespace: 'namespace', + workspaceKind: baseWorkspaceKindInfoTest, +}); + +const baseImageConfigTest: WorkspaceImageConfigValue = { + id: 'image', + displayName: 'Image', + description: 'Test image', + labels: [], + hidden: false, + clusterMetrics: undefined, +}; + +const basePodConfigTest: WorkspacePodConfigValue = { + id: 'podConfig', + displayName: 'Pod Config', + description: 'Test pod config', + labels: [], + hidden: false, + clusterMetrics: undefined, +}; + +describe('useWorkspaceCountPerKind', () => { + const mockListAllWorkspaces = jest.fn(); + const mockListWorkspaceKinds = jest.fn(); + + const mockApi: Partial = { + listAllWorkspaces: mockListAllWorkspaces, + listWorkspaceKinds: mockListWorkspaceKinds, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockUseNotebookAPI.mockReturnValue({ + api: mockApi as NotebookAPIs, + apiAvailable: true, + refreshAllAPI: jest.fn(), + }); + }); + + it('should return empty object initially', () => { + mockListAllWorkspaces.mockResolvedValue([]); + mockListWorkspaceKinds.mockResolvedValue([]); + + const { result } = renderHook(() => useWorkspaceCountPerKind()); + + waitFor(() => { + expect(result.current).toEqual({}); + }); + }); + + it('should fetch and calculate workspace counts on mount', async () => { + const mockWorkspaces: Workspace[] = [ + { + ...baseWorkspaceTest, + name: 'workspace1', + namespace: 'namespace1', + workspaceKind: { ...baseWorkspaceKindInfoTest, name: 'jupyter1' }, + }, + { + ...baseWorkspaceTest, + name: 'workspace2', + namespace: 'namespace1', + workspaceKind: { ...baseWorkspaceKindInfoTest, name: 'jupyter1' }, + }, + { + ...baseWorkspaceTest, + name: 'workspace3', + namespace: 'namespace2', + workspaceKind: { ...baseWorkspaceKindInfoTest, name: 'jupyter2' }, + }, + ]; + + const mockWorkspaceKinds: WorkspaceKind[] = [ + buildMockWorkspaceKind({ + name: 'jupyter1', + clusterMetrics: { workspacesCount: 10 }, + podTemplate: { + podMetadata: { labels: {}, annotations: {} }, + volumeMounts: { home: '/home' }, + options: { + imageConfig: { + default: 'image1', + values: [ + { + ...baseImageConfigTest, + id: 'image1', + clusterMetrics: { workspacesCount: 1 }, + }, + { + ...baseImageConfigTest, + id: 'image2', + clusterMetrics: { workspacesCount: 2 }, + }, + ], + }, + podConfig: { + default: 'podConfig1', + values: [ + { + ...basePodConfigTest, + id: 'podConfig1', + clusterMetrics: { workspacesCount: 3 }, + }, + { + ...basePodConfigTest, + id: 'podConfig2', + clusterMetrics: { workspacesCount: 4 }, + }, + ], + }, + }, + }, + }), + buildMockWorkspaceKind({ + name: 'jupyter2', + clusterMetrics: { workspacesCount: 20 }, + podTemplate: { + podMetadata: { labels: {}, annotations: {} }, + volumeMounts: { home: '/home' }, + options: { + imageConfig: { + default: 'image1', + values: [ + { + ...baseImageConfigTest, + id: 'image1', + clusterMetrics: { workspacesCount: 11 }, + }, + ], + }, + podConfig: { + default: 'podConfig1', + values: [ + { + ...basePodConfigTest, + id: 'podConfig1', + clusterMetrics: { workspacesCount: 12 }, + }, + ], + }, + }, + }, + }), + ]; + + mockListAllWorkspaces.mockResolvedValue(mockWorkspaces); + mockListWorkspaceKinds.mockResolvedValue(mockWorkspaceKinds); + + const { result } = renderHook(() => useWorkspaceCountPerKind()); + + await waitFor(() => { + expect(result.current).toEqual({ + jupyter1: { + count: 10, + countByImage: { + image1: 1, + image2: 2, + }, + countByPodConfig: { + podConfig1: 3, + podConfig2: 4, + }, + countByNamespace: { + namespace1: 2, + }, + }, + jupyter2: { + count: 20, + countByImage: { + image1: 11, + }, + countByPodConfig: { + podConfig1: 12, + }, + countByNamespace: { + namespace2: 1, + }, + }, + }); + }); + }); + + it('should handle missing cluster metrics gracefully', async () => { + const mockEmptyWorkspaces: Workspace[] = []; + const mockWorkspaceKinds: WorkspaceKind[] = [ + buildMockWorkspaceKind({ + name: 'no-metrics', + clusterMetrics: undefined, + podTemplate: { + podMetadata: { labels: {}, annotations: {} }, + volumeMounts: { home: '/home' }, + options: { + imageConfig: { + default: baseImageConfigTest.id, + values: [{ ...baseImageConfigTest }], + }, + podConfig: { + default: basePodConfigTest.id, + values: [{ ...basePodConfigTest }], + }, + }, + }, + }), + buildMockWorkspaceKind({ + name: 'no-metrics-2', + clusterMetrics: undefined, + podTemplate: { + podMetadata: { labels: {}, annotations: {} }, + volumeMounts: { home: '/home' }, + options: { + imageConfig: { + default: 'empty', + values: [], + }, + podConfig: { + default: 'empty', + values: [], + }, + }, + }, + }), + ]; + + mockListAllWorkspaces.mockResolvedValue(mockEmptyWorkspaces); + mockListWorkspaceKinds.mockResolvedValue(mockWorkspaceKinds); + + const { result } = renderHook(() => useWorkspaceCountPerKind()); + + await waitFor(() => { + expect(result.current).toEqual({ + 'no-metrics': { + count: 0, + countByImage: { + image: 0, + }, + countByPodConfig: { + podConfig: 0, + }, + countByNamespace: {}, + }, + 'no-metrics-2': { + count: 0, + countByImage: {}, + countByPodConfig: {}, + countByNamespace: {}, + }, + }); + }); + }); + + it('should return empty object in case of API errors rather than propagating them', async () => { + mockListAllWorkspaces.mockRejectedValue(new Error('API Error')); + mockListWorkspaceKinds.mockRejectedValue(new Error('API Error')); + + const { result } = renderHook(() => useWorkspaceCountPerKind()); + + await waitFor(() => { + expect(result.current).toEqual({}); + }); + }); + + it('should handle empty workspace kinds array', async () => { + mockListWorkspaceKinds.mockResolvedValue([]); + + const { result } = renderHook(() => useWorkspaceCountPerKind()); + + await waitFor(() => { + expect(result.current).toEqual({}); + }); + }); + + it('should handle workspaces with no matching kinds', async () => { + const mockWorkspaces: Workspace[] = [baseWorkspaceTest]; + const workspaceKind = buildMockWorkspaceKind({ + name: 'nomatch', + clusterMetrics: { workspacesCount: 0 }, + podTemplate: { + podMetadata: { labels: {}, annotations: {} }, + volumeMounts: { home: '/home' }, + options: { + imageConfig: { + default: baseImageConfigTest.id, + values: [{ ...baseImageConfigTest }], + }, + podConfig: { + default: basePodConfigTest.id, + values: [{ ...basePodConfigTest }], + }, + }, + }, + }); + + const mockWorkspaceKinds: WorkspaceKind[] = [workspaceKind]; + + mockListAllWorkspaces.mockResolvedValue(mockWorkspaces); + mockListWorkspaceKinds.mockResolvedValue(mockWorkspaceKinds); + + const { result } = renderHook(() => useWorkspaceCountPerKind()); + + await waitFor(() => { + expect(result.current).toEqual({ + [workspaceKind.name]: { + count: 0, + countByImage: { [baseImageConfigTest.id]: 0 }, + countByPodConfig: { [basePodConfigTest.id]: 0 }, + countByNamespace: {}, + }, + }); + }); + }); +}); diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts b/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts index ccc1fbca..3a5f766a 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts @@ -2,45 +2,96 @@ import { useEffect, useState } from 'react'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { Workspace, WorkspaceKind } from '~/shared/api/backendApiTypes'; import { WorkspaceCountPerOption } from '~/app/types'; +import { NotebookAPIs } from '~/shared/api/notebookApi'; export type WorkspaceCountPerKind = Record; -// TODO: This hook is temporary; we should get counts from the API directly export const useWorkspaceCountPerKind = (): WorkspaceCountPerKind => { const { api } = useNotebookAPI(); - const [workspaceCountPerKind, setWorkspaceCountPerKind] = useState({}); useEffect(() => { - api.listAllWorkspaces({}).then((workspaces) => { - const countPerKind = workspaces.reduce((acc: WorkspaceCountPerKind, workspace: Workspace) => { - acc[workspace.workspaceKind.name] = acc[workspace.workspaceKind.name] ?? { - count: 0, - countByImage: {}, - countByPodConfig: {}, - countByNamespace: {}, - }; - acc[workspace.workspaceKind.name].count = - (acc[workspace.workspaceKind.name].count || 0) + 1; - acc[workspace.workspaceKind.name].countByImage[ - workspace.podTemplate.options.imageConfig.current.id - ] = - (acc[workspace.workspaceKind.name].countByImage[ - workspace.podTemplate.options.imageConfig.current.id - ] || 0) + 1; - acc[workspace.workspaceKind.name].countByPodConfig[ - workspace.podTemplate.options.podConfig.current.id - ] = - (acc[workspace.workspaceKind.name].countByPodConfig[ - workspace.podTemplate.options.podConfig.current.id - ] || 0) + 1; - acc[workspace.workspaceKind.name].countByNamespace[workspace.namespace] = - (acc[workspace.workspaceKind.name].countByNamespace[workspace.namespace] || 0) + 1; - return acc; - }, {}); - setWorkspaceCountPerKind(countPerKind); - }); + const fetchAndSetCounts = async () => { + try { + const countPerKind = await loadWorkspaceCounts(api); + setWorkspaceCountPerKind(countPerKind); + } catch (err) { + // TODO: alert user about error + console.error('Failed to fetch workspace counts:', err); + } + }; + + fetchAndSetCounts(); }, [api]); return workspaceCountPerKind; }; + +async function loadWorkspaceCounts(api: NotebookAPIs): Promise { + const [workspaces, workspaceKinds] = await Promise.all([ + api.listAllWorkspaces({}), + api.listWorkspaceKinds({}), + ]); + + return extractCountPerKind({ workspaceKinds, workspaces }); +} + +function extractCountByNamespace(args: { + kind: WorkspaceKind; + workspaces: Workspace[]; +}): WorkspaceCountPerOption['countByNamespace'] { + const { kind, workspaces } = args; + return workspaces.reduce( + (acc, { namespace, workspaceKind }) => { + if (kind.name === workspaceKind.name) { + acc[namespace] = (acc[namespace] ?? 0) + 1; + } + return acc; + }, + {}, + ); +} + +function extractCountByImage( + workspaceKind: WorkspaceKind, +): WorkspaceCountPerOption['countByImage'] { + return workspaceKind.podTemplate.options.imageConfig.values.reduce< + WorkspaceCountPerOption['countByImage'] + >((acc, { id, clusterMetrics }) => { + acc[id] = clusterMetrics?.workspacesCount ?? 0; + return acc; + }, {}); +} + +function extractCountByPodConfig( + workspaceKind: WorkspaceKind, +): WorkspaceCountPerOption['countByPodConfig'] { + return workspaceKind.podTemplate.options.podConfig.values.reduce< + WorkspaceCountPerOption['countByPodConfig'] + >((acc, { id, clusterMetrics }) => { + acc[id] = clusterMetrics?.workspacesCount ?? 0; + return acc; + }, {}); +} + +function extractTotalCount(workspaceKind: WorkspaceKind): number { + return workspaceKind.clusterMetrics?.workspacesCount ?? 0; +} + +function extractCountPerKind(args: { + workspaceKinds: WorkspaceKind[]; + workspaces: Workspace[]; +}): WorkspaceCountPerKind { + const { workspaceKinds, workspaces } = args; + + return workspaceKinds.reduce((acc, kind) => { + acc[kind.name] = { + count: extractTotalCount(kind), + countByImage: extractCountByImage(kind), + countByPodConfig: extractCountByPodConfig(kind), + countByNamespace: extractCountByNamespace({ kind, workspaces }), + }; + + return acc; + }, {}); +} diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsTable.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsTable.tsx index 5ee7bc16..58ed429b 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsTable.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsTable.tsx @@ -63,7 +63,7 @@ export const WorkspaceKindDetailsTable: React.FC - {rowPages[page - 1].map((row) => ( + {rowPages[page - 1]?.map((row) => ( {row.displayName} diff --git a/workspaces/frontend/src/shared/api/backendApiTypes.ts b/workspaces/frontend/src/shared/api/backendApiTypes.ts index 3f833058..643c300a 100644 --- a/workspaces/frontend/src/shared/api/backendApiTypes.ts +++ b/workspaces/frontend/src/shared/api/backendApiTypes.ts @@ -27,6 +27,7 @@ export interface WorkspacePodConfigValue { labels: WorkspaceOptionLabel[]; hidden: boolean; redirect?: WorkspaceOptionRedirect; + clusterMetrics?: WorkspaceKindClusterMetrics; } export interface WorkspaceKindPodConfig { @@ -71,6 +72,7 @@ export interface WorkspaceImageConfigValue { labels: WorkspaceOptionLabel[]; hidden: boolean; redirect?: WorkspaceOptionRedirect; + clusterMetrics?: WorkspaceKindClusterMetrics; } export interface WorkspaceKindImageConfig { @@ -107,9 +109,14 @@ export interface WorkspaceKind { hidden: boolean; icon: WorkspaceImageRef; logo: WorkspaceImageRef; + clusterMetrics?: WorkspaceKindClusterMetrics; podTemplate: WorkspaceKindPodTemplate; } +export interface WorkspaceKindClusterMetrics { + workspacesCount: number; +} + export enum WorkspaceState { WorkspaceStateRunning = 'Running', WorkspaceStateTerminating = 'Terminating', diff --git a/workspaces/frontend/src/shared/mock/mockBuilder.ts b/workspaces/frontend/src/shared/mock/mockBuilder.ts index e15d045c..8b6eeccc 100644 --- a/workspaces/frontend/src/shared/mock/mockBuilder.ts +++ b/workspaces/frontend/src/shared/mock/mockBuilder.ts @@ -147,6 +147,9 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial): logo: { url: 'https://upload.wikimedia.org/wikipedia/commons/3/38/Jupyter_logo.svg', }, + clusterMetrics: { + workspacesCount: 10, + }, podTemplate: { podMetadata: { labels: { @@ -172,6 +175,9 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial): { key: 'jupyterlabVersion', value: '1.8.0' }, ], hidden: true, + clusterMetrics: { + workspacesCount: 0, + }, redirect: { to: 'jupyterlab_scipy_190', message: { @@ -196,6 +202,9 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial): level: WorkspaceRedirectMessageLevel.RedirectMessageLevelWarning, }, }, + clusterMetrics: { + workspacesCount: 1, + }, }, { id: 'jupyterlab_scipy_200', @@ -213,6 +222,9 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial): level: WorkspaceRedirectMessageLevel.RedirectMessageLevelWarning, }, }, + clusterMetrics: { + workspacesCount: 2, + }, }, { id: 'jupyterlab_scipy_210', @@ -230,6 +242,9 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial): level: WorkspaceRedirectMessageLevel.RedirectMessageLevelWarning, }, }, + clusterMetrics: { + workspacesCount: 3, + }, }, ], }, @@ -252,6 +267,9 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial): level: WorkspaceRedirectMessageLevel.RedirectMessageLevelDanger, }, }, + clusterMetrics: { + workspacesCount: 0, + }, }, { id: 'large_cpu', @@ -270,6 +288,9 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial): level: WorkspaceRedirectMessageLevel.RedirectMessageLevelDanger, }, }, + clusterMetrics: { + workspacesCount: 5, + }, }, ], }, diff --git a/workspaces/frontend/src/shared/mock/mockNotebookServiceData.ts b/workspaces/frontend/src/shared/mock/mockNotebookServiceData.ts index 8ff2e181..fdbf7de0 100644 --- a/workspaces/frontend/src/shared/mock/mockNotebookServiceData.ts +++ b/workspaces/frontend/src/shared/mock/mockNotebookServiceData.ts @@ -27,16 +27,25 @@ export const mockNamespaces = [mockNamespace1, mockNamespace2, mockNamespace3]; export const mockWorkspaceKind1: WorkspaceKind = buildMockWorkspaceKind({ name: 'jupyterlab1', displayName: 'JupyterLab Notebook 1', + clusterMetrics: { + workspacesCount: 18, + }, }); export const mockWorkspaceKind2: WorkspaceKind = buildMockWorkspaceKind({ name: 'jupyterlab2', displayName: 'JupyterLab Notebook 2', + clusterMetrics: { + workspacesCount: 2, + }, }); export const mockWorkspaceKind3: WorkspaceKind = buildMockWorkspaceKind({ name: 'jupyterlab3', displayName: 'JupyterLab Notebook 3', + clusterMetrics: { + workspacesCount: 0, + }, }); export const mockWorkspaceKinds = [mockWorkspaceKind1, mockWorkspaceKind2, mockWorkspaceKind3]; From 039e0c9faeaabfa95504390ae46f58ec46d5b109 Mon Sep 17 00:00:00 2001 From: Andy Stoneberg Date: Thu, 31 Jul 2025 13:18:49 -0400 Subject: [PATCH 49/68] feat(ws): add @ID swag annotation to handlers (#488) - added @ID annotations for all API routes to populate operationId Swagger attribute - split GetWorkspacesHandler into 2 separate handlers to account for @ID needing to be unique-per-route - GetAllWorkspacesHandler now services GET /workspaces - GetWorkspacesByNamespaceHandler now services GET /workspaces/{namespace} - non-exported getWorkspacesHandler function contains all business logic that existed in GetWorkspacesHandler - Adjusted test cases to align with the new handler names. Signed-off-by: Andy Stoneberg --- workspaces/backend/api/app.go | 4 +- workspaces/backend/api/healthcheck_handler.go | 1 + workspaces/backend/api/namespaces_handler.go | 1 + .../backend/api/workspace_actions_handler.go | 1 + .../backend/api/workspacekinds_handler.go | 3 ++ .../api/workspacekinds_handler_test.go | 2 +- workspaces/backend/api/workspaces_handler.go | 41 +++++++++++++++---- .../backend/api/workspaces_handler_test.go | 20 ++++----- workspaces/backend/openapi/docs.go | 31 ++++++++------ workspaces/backend/openapi/swagger.json | 31 ++++++++------ 10 files changed, 87 insertions(+), 48 deletions(-) diff --git a/workspaces/backend/api/app.go b/workspaces/backend/api/app.go index e533eac8..0500e70f 100644 --- a/workspaces/backend/api/app.go +++ b/workspaces/backend/api/app.go @@ -113,8 +113,8 @@ func (a *App) Routes() http.Handler { router.GET(AllNamespacesPath, a.GetNamespacesHandler) // workspaces - router.GET(AllWorkspacesPath, a.GetWorkspacesHandler) - router.GET(WorkspacesByNamespacePath, a.GetWorkspacesHandler) + router.GET(AllWorkspacesPath, a.GetAllWorkspacesHandler) + router.GET(WorkspacesByNamespacePath, a.GetWorkspacesByNamespaceHandler) router.GET(WorkspacesByNamePath, a.GetWorkspaceHandler) router.POST(WorkspacesByNamespacePath, a.CreateWorkspaceHandler) router.DELETE(WorkspacesByNamePath, a.DeleteWorkspaceHandler) diff --git a/workspaces/backend/api/healthcheck_handler.go b/workspaces/backend/api/healthcheck_handler.go index 69a1b78a..cdd7ff88 100644 --- a/workspaces/backend/api/healthcheck_handler.go +++ b/workspaces/backend/api/healthcheck_handler.go @@ -29,6 +29,7 @@ import ( // @Summary Returns the health status of the application // @Description Provides a healthcheck response indicating the status of key services. // @Tags healthcheck +// @ID getHealthcheck // @Produce application/json // @Success 200 {object} health_check.HealthCheck "Successful healthcheck response" // @Failure 500 {object} ErrorEnvelope "Internal server error" diff --git a/workspaces/backend/api/namespaces_handler.go b/workspaces/backend/api/namespaces_handler.go index 9405c2e0..69773f2f 100644 --- a/workspaces/backend/api/namespaces_handler.go +++ b/workspaces/backend/api/namespaces_handler.go @@ -33,6 +33,7 @@ type NamespaceListEnvelope Envelope[[]models.Namespace] // @Summary Returns a list of all namespaces // @Description Provides a list of all namespaces that the user has access to // @Tags namespaces +// @ID listNamespaces // @Produce application/json // @Success 200 {object} NamespaceListEnvelope "Successful namespaces response" // @Failure 401 {object} ErrorEnvelope "Unauthorized" diff --git a/workspaces/backend/api/workspace_actions_handler.go b/workspaces/backend/api/workspace_actions_handler.go index 75e1001d..0b1f54fa 100644 --- a/workspaces/backend/api/workspace_actions_handler.go +++ b/workspaces/backend/api/workspace_actions_handler.go @@ -39,6 +39,7 @@ type WorkspaceActionPauseEnvelope Envelope[*models.WorkspaceActionPause] // @Summary Pause or unpause a workspace // @Description Pauses or unpauses a workspace, stopping or resuming all associated pods. // @Tags workspaces +// @ID updateWorkspacePauseState // @Accept json // @Produce json // @Param namespace path string true "Namespace of the workspace" extensions(x-example=default) diff --git a/workspaces/backend/api/workspacekinds_handler.go b/workspaces/backend/api/workspacekinds_handler.go index 85bd5a62..b995a400 100644 --- a/workspaces/backend/api/workspacekinds_handler.go +++ b/workspaces/backend/api/workspacekinds_handler.go @@ -47,6 +47,7 @@ type WorkspaceKindEnvelope Envelope[models.WorkspaceKind] // @Summary Get workspace kind // @Description Returns details of a specific workspace kind identified by its name. Workspace kinds define the available types of workspaces that can be created. // @Tags workspacekinds +// @ID getWorkspaceKind // @Accept json // @Produce json // @Param name path string true "Name of the workspace kind" extensions(x-example=jupyterlab) @@ -101,6 +102,7 @@ func (a *App) GetWorkspaceKindHandler(w http.ResponseWriter, r *http.Request, ps // @Summary List workspace kinds // @Description Returns a list of all available workspace kinds. Workspace kinds define the different types of workspaces that can be created in the system. // @Tags workspacekinds +// @ID listWorkspaceKinds // @Accept json // @Produce json // @Success 200 {object} WorkspaceKindListEnvelope "Successful operation. Returns a list of all available workspace kinds." @@ -136,6 +138,7 @@ func (a *App) GetWorkspaceKindsHandler(w http.ResponseWriter, r *http.Request, _ // @Summary Create workspace kind // @Description Creates a new workspace kind. // @Tags workspacekinds +// @ID createWorkspaceKind // @Accept application/yaml // @Produce json // @Param body body string true "Kubernetes YAML manifest of a WorkspaceKind" diff --git a/workspaces/backend/api/workspacekinds_handler_test.go b/workspaces/backend/api/workspacekinds_handler_test.go index 19853676..46465927 100644 --- a/workspaces/backend/api/workspacekinds_handler_test.go +++ b/workspaces/backend/api/workspacekinds_handler_test.go @@ -209,7 +209,7 @@ var _ = Describe("WorkspaceKinds Handler", func() { By("setting the auth headers") req.Header.Set(userIdHeader, adminUser) - By("executing GetWorkspacesHandler") + By("executing GetWorkspaceKindsHandler") ps := httprouter.Params{} rr := httptest.NewRecorder() a.GetWorkspaceKindsHandler(rr, req, ps) diff --git a/workspaces/backend/api/workspaces_handler.go b/workspaces/backend/api/workspaces_handler.go index 97354b12..a03f7c4a 100644 --- a/workspaces/backend/api/workspaces_handler.go +++ b/workspaces/backend/api/workspaces_handler.go @@ -44,6 +44,7 @@ type WorkspaceEnvelope Envelope[models.Workspace] // @Summary Get workspace // @Description Returns details of a specific workspace identified by namespace and workspace name. // @Tags workspaces +// @ID getWorkspace // @Accept json // @Produce json // @Param namespace path string true "Namespace of the workspace" extensions(x-example=kubeflow-user-example-com) @@ -99,24 +100,44 @@ func (a *App) GetWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps htt a.dataResponse(w, r, responseEnvelope) } -// GetWorkspacesHandler returns a list of workspaces. +// GetAllWorkspacesHandler returns a list of all workspaces across all namespaces. // -// @Summary List workspaces -// @Description Returns a list of workspaces. The endpoint supports two modes: -// @Description 1. List all workspaces across all namespaces (when no namespace is provided) -// @Description 2. List workspaces in a specific namespace (when namespace is provided) +// @Summary List all workspaces +// @Description Returns a list of all workspaces across all namespaces. // @Tags workspaces +// @ID listAllWorkspaces // @Accept json // @Produce json -// @Param namespace path string true "Namespace to filter workspaces. If not provided, returns all workspaces across all namespaces." extensions(x-example=kubeflow-user-example-com) -// @Success 200 {object} WorkspaceListEnvelope "Successful operation. Returns a list of workspaces." +// @Success 200 {object} WorkspaceListEnvelope "Successful operation. Returns a list of all workspaces." +// @Failure 401 {object} ErrorEnvelope "Unauthorized. Authentication is required." +// @Failure 403 {object} ErrorEnvelope "Forbidden. User does not have permission to list workspaces." +// @Failure 500 {object} ErrorEnvelope "Internal server error. An unexpected error occurred on the server." +// @Router /workspaces [get] +func (a *App) GetAllWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + a.getWorkspacesHandler(w, r, ps) +} + +// GetWorkspacesByNamespaceHandler returns a list of workspaces in a specific namespace. +// +// @Summary List workspaces by namespace +// @Description Returns a list of workspaces in a specific namespace. +// @Tags workspaces +// @ID listWorkspacesByNamespace +// @Accept json +// @Produce json +// @Param namespace path string true "Namespace to filter workspaces" extensions(x-example=kubeflow-user-example-com) +// @Success 200 {object} WorkspaceListEnvelope "Successful operation. Returns a list of workspaces in the specified namespace." // @Failure 400 {object} ErrorEnvelope "Bad Request. Invalid namespace format." // @Failure 401 {object} ErrorEnvelope "Unauthorized. Authentication is required." // @Failure 403 {object} ErrorEnvelope "Forbidden. User does not have permission to list workspaces." // @Failure 500 {object} ErrorEnvelope "Internal server error. An unexpected error occurred on the server." -// @Router /workspaces [get] // @Router /workspaces/{namespace} [get] -func (a *App) GetWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { +func (a *App) GetWorkspacesByNamespaceHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + a.getWorkspacesHandler(w, r, ps) +} + +// getWorkspacesHandler is the internal implementation for listing workspaces. +func (a *App) getWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { namespace := ps.ByName(NamespacePathParam) // validate path parameters @@ -167,6 +188,7 @@ func (a *App) GetWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps ht // @Summary Create workspace // @Description Creates a new workspace in the specified namespace. // @Tags workspaces +// @ID createWorkspace // @Accept json // @Produce json // @Param namespace path string true "Namespace for the workspace" extensions(x-example=kubeflow-user-example-com) @@ -267,6 +289,7 @@ func (a *App) CreateWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps // @Summary Delete workspace // @Description Deletes a specific workspace identified by namespace and workspace name. // @Tags workspaces +// @ID deleteWorkspace // @Accept json // @Produce json // @Param namespace path string true "Namespace of the workspace" extensions(x-example=kubeflow-user-example-com) diff --git a/workspaces/backend/api/workspaces_handler_test.go b/workspaces/backend/api/workspaces_handler_test.go index 10f1b445..23a35f67 100644 --- a/workspaces/backend/api/workspaces_handler_test.go +++ b/workspaces/backend/api/workspaces_handler_test.go @@ -164,10 +164,10 @@ var _ = Describe("Workspaces Handler", func() { By("setting the auth headers") req.Header.Set(userIdHeader, adminUser) - By("executing GetWorkspacesHandler") + By("executing GetAllWorkspacesHandler") ps := httprouter.Params{} rr := httptest.NewRecorder() - a.GetWorkspacesHandler(rr, req, ps) + a.GetAllWorkspacesHandler(rr, req, ps) rs := rr.Result() defer rs.Body.Close() @@ -219,12 +219,12 @@ var _ = Describe("Workspaces Handler", func() { By("setting the auth headers") req.Header.Set(userIdHeader, adminUser) - By("executing GetWorkspacesHandler") + By("executing GetWorkspacesByNamespaceHandler") ps := httprouter.Params{ httprouter.Param{Key: NamespacePathParam, Value: namespaceName1}, } rr := httptest.NewRecorder() - a.GetWorkspacesHandler(rr, req, ps) + a.GetWorkspacesByNamespaceHandler(rr, req, ps) rs := rr.Result() defer rs.Body.Close() @@ -429,12 +429,12 @@ var _ = Describe("Workspaces Handler", func() { By("setting the auth headers") req.Header.Set(userIdHeader, adminUser) - By("executing GetWorkspacesHandler") + By("executing GetWorkspacesByNamespaceHandler") ps := httprouter.Params{ httprouter.Param{Key: NamespacePathParam, Value: namespaceName1}, } rr := httptest.NewRecorder() - a.GetWorkspacesHandler(rr, req, ps) + a.GetWorkspacesByNamespaceHandler(rr, req, ps) rs := rr.Result() defer rs.Body.Close() @@ -543,10 +543,10 @@ var _ = Describe("Workspaces Handler", func() { By("setting the auth headers") req.Header.Set(userIdHeader, adminUser) - By("executing GetWorkspacesHandler") + By("executing GetAllWorkspacesHandler") ps := httprouter.Params{} rr := httptest.NewRecorder() - a.GetWorkspacesHandler(rr, req, ps) + a.GetAllWorkspacesHandler(rr, req, ps) rs := rr.Result() defer rs.Body.Close() @@ -577,12 +577,12 @@ var _ = Describe("Workspaces Handler", func() { By("setting the auth headers") req.Header.Set(userIdHeader, adminUser) - By("executing GetWorkspacesHandler") + By("executing GetWorkspacesByNamespaceHandler") ps := httprouter.Params{ httprouter.Param{Key: NamespacePathParam, Value: missingNamespace}, } rr := httptest.NewRecorder() - a.GetWorkspacesHandler(rr, req, ps) + a.GetWorkspacesByNamespaceHandler(rr, req, ps) rs := rr.Result() defer rs.Body.Close() diff --git a/workspaces/backend/openapi/docs.go b/workspaces/backend/openapi/docs.go index d4324874..616bb409 100644 --- a/workspaces/backend/openapi/docs.go +++ b/workspaces/backend/openapi/docs.go @@ -29,6 +29,7 @@ const docTemplate = `{ "healthcheck" ], "summary": "Returns the health status of the application", + "operationId": "getHealthcheck", "responses": { "200": { "description": "Successful healthcheck response", @@ -55,6 +56,7 @@ const docTemplate = `{ "namespaces" ], "summary": "Returns a list of all namespaces", + "operationId": "listNamespaces", "responses": { "200": { "description": "Successful namespaces response", @@ -96,6 +98,7 @@ const docTemplate = `{ "workspacekinds" ], "summary": "List workspace kinds", + "operationId": "listWorkspaceKinds", "responses": { "200": { "description": "Successful operation. Returns a list of all available workspace kinds.", @@ -135,6 +138,7 @@ const docTemplate = `{ "workspacekinds" ], "summary": "Create workspace kind", + "operationId": "createWorkspaceKind", "parameters": [ { "description": "Kubernetes YAML manifest of a WorkspaceKind", @@ -217,6 +221,7 @@ const docTemplate = `{ "workspacekinds" ], "summary": "Get workspace kind", + "operationId": "getWorkspaceKind", "parameters": [ { "type": "string", @@ -269,7 +274,7 @@ const docTemplate = `{ }, "/workspaces": { "get": { - "description": "Returns a list of workspaces. The endpoint supports two modes:\n1. List all workspaces across all namespaces (when no namespace is provided)\n2. List workspaces in a specific namespace (when namespace is provided)", + "description": "Returns a list of all workspaces across all namespaces.", "consumes": [ "application/json" ], @@ -279,20 +284,15 @@ const docTemplate = `{ "tags": [ "workspaces" ], - "summary": "List workspaces", + "summary": "List all workspaces", + "operationId": "listAllWorkspaces", "responses": { "200": { - "description": "Successful operation. Returns a list of workspaces.", + "description": "Successful operation. Returns a list of all workspaces.", "schema": { "$ref": "#/definitions/api.WorkspaceListEnvelope" } }, - "400": { - "description": "Bad Request. Invalid namespace format.", - "schema": { - "$ref": "#/definitions/api.ErrorEnvelope" - } - }, "401": { "description": "Unauthorized. Authentication is required.", "schema": { @@ -316,7 +316,7 @@ const docTemplate = `{ }, "/workspaces/{namespace}": { "get": { - "description": "Returns a list of workspaces. The endpoint supports two modes:\n1. List all workspaces across all namespaces (when no namespace is provided)\n2. List workspaces in a specific namespace (when namespace is provided)", + "description": "Returns a list of workspaces in a specific namespace.", "consumes": [ "application/json" ], @@ -326,12 +326,13 @@ const docTemplate = `{ "tags": [ "workspaces" ], - "summary": "List workspaces", + "summary": "List workspaces by namespace", + "operationId": "listWorkspacesByNamespace", "parameters": [ { "type": "string", "x-example": "kubeflow-user-example-com", - "description": "Namespace to filter workspaces. If not provided, returns all workspaces across all namespaces.", + "description": "Namespace to filter workspaces", "name": "namespace", "in": "path", "required": true @@ -339,7 +340,7 @@ const docTemplate = `{ ], "responses": { "200": { - "description": "Successful operation. Returns a list of workspaces.", + "description": "Successful operation. Returns a list of workspaces in the specified namespace.", "schema": { "$ref": "#/definitions/api.WorkspaceListEnvelope" } @@ -382,6 +383,7 @@ const docTemplate = `{ "workspaces" ], "summary": "Create workspace", + "operationId": "createWorkspace", "parameters": [ { "type": "string", @@ -466,6 +468,7 @@ const docTemplate = `{ "workspaces" ], "summary": "Pause or unpause a workspace", + "operationId": "updateWorkspacePauseState", "parameters": [ { "type": "string", @@ -564,6 +567,7 @@ const docTemplate = `{ "workspaces" ], "summary": "Get workspace", + "operationId": "getWorkspace", "parameters": [ { "type": "string", @@ -633,6 +637,7 @@ const docTemplate = `{ "workspaces" ], "summary": "Delete workspace", + "operationId": "deleteWorkspace", "parameters": [ { "type": "string", diff --git a/workspaces/backend/openapi/swagger.json b/workspaces/backend/openapi/swagger.json index ff81e8bf..6260cf1b 100644 --- a/workspaces/backend/openapi/swagger.json +++ b/workspaces/backend/openapi/swagger.json @@ -27,6 +27,7 @@ "healthcheck" ], "summary": "Returns the health status of the application", + "operationId": "getHealthcheck", "responses": { "200": { "description": "Successful healthcheck response", @@ -53,6 +54,7 @@ "namespaces" ], "summary": "Returns a list of all namespaces", + "operationId": "listNamespaces", "responses": { "200": { "description": "Successful namespaces response", @@ -94,6 +96,7 @@ "workspacekinds" ], "summary": "List workspace kinds", + "operationId": "listWorkspaceKinds", "responses": { "200": { "description": "Successful operation. Returns a list of all available workspace kinds.", @@ -133,6 +136,7 @@ "workspacekinds" ], "summary": "Create workspace kind", + "operationId": "createWorkspaceKind", "parameters": [ { "description": "Kubernetes YAML manifest of a WorkspaceKind", @@ -215,6 +219,7 @@ "workspacekinds" ], "summary": "Get workspace kind", + "operationId": "getWorkspaceKind", "parameters": [ { "type": "string", @@ -267,7 +272,7 @@ }, "/workspaces": { "get": { - "description": "Returns a list of workspaces. The endpoint supports two modes:\n1. List all workspaces across all namespaces (when no namespace is provided)\n2. List workspaces in a specific namespace (when namespace is provided)", + "description": "Returns a list of all workspaces across all namespaces.", "consumes": [ "application/json" ], @@ -277,20 +282,15 @@ "tags": [ "workspaces" ], - "summary": "List workspaces", + "summary": "List all workspaces", + "operationId": "listAllWorkspaces", "responses": { "200": { - "description": "Successful operation. Returns a list of workspaces.", + "description": "Successful operation. Returns a list of all workspaces.", "schema": { "$ref": "#/definitions/api.WorkspaceListEnvelope" } }, - "400": { - "description": "Bad Request. Invalid namespace format.", - "schema": { - "$ref": "#/definitions/api.ErrorEnvelope" - } - }, "401": { "description": "Unauthorized. Authentication is required.", "schema": { @@ -314,7 +314,7 @@ }, "/workspaces/{namespace}": { "get": { - "description": "Returns a list of workspaces. The endpoint supports two modes:\n1. List all workspaces across all namespaces (when no namespace is provided)\n2. List workspaces in a specific namespace (when namespace is provided)", + "description": "Returns a list of workspaces in a specific namespace.", "consumes": [ "application/json" ], @@ -324,12 +324,13 @@ "tags": [ "workspaces" ], - "summary": "List workspaces", + "summary": "List workspaces by namespace", + "operationId": "listWorkspacesByNamespace", "parameters": [ { "type": "string", "x-example": "kubeflow-user-example-com", - "description": "Namespace to filter workspaces. If not provided, returns all workspaces across all namespaces.", + "description": "Namespace to filter workspaces", "name": "namespace", "in": "path", "required": true @@ -337,7 +338,7 @@ ], "responses": { "200": { - "description": "Successful operation. Returns a list of workspaces.", + "description": "Successful operation. Returns a list of workspaces in the specified namespace.", "schema": { "$ref": "#/definitions/api.WorkspaceListEnvelope" } @@ -380,6 +381,7 @@ "workspaces" ], "summary": "Create workspace", + "operationId": "createWorkspace", "parameters": [ { "type": "string", @@ -464,6 +466,7 @@ "workspaces" ], "summary": "Pause or unpause a workspace", + "operationId": "updateWorkspacePauseState", "parameters": [ { "type": "string", @@ -562,6 +565,7 @@ "workspaces" ], "summary": "Get workspace", + "operationId": "getWorkspace", "parameters": [ { "type": "string", @@ -631,6 +635,7 @@ "workspaces" ], "summary": "Delete workspace", + "operationId": "deleteWorkspace", "parameters": [ { "type": "string", From c50bdbbac8375e4bb1afa2634a0f01cbbab6512e Mon Sep 17 00:00:00 2001 From: Andy Stoneberg Date: Thu, 31 Jul 2025 13:48:49 -0400 Subject: [PATCH 50/68] chore(ws): update swag to 1.16.6 for required fields (#489) - Updated swaggo/swag from v1.16.4 to v1.16.6 in go.mod and Makefile. - Added required fields to various OpenAPI definitions in docs.go and swagger.json for better validation via `--requiredByDefault` flag - `swag` `v1.16.6` contains a commit we authored to treat `json:omitempty` as `required: false`. That, in conjunction with `--requiredByDefault` flag, allows us to generate models with proper _required-ness_ Signed-off-by: Andy Stoneberg --- workspaces/backend/Makefile | 6 +- workspaces/backend/go.mod | 3 +- workspaces/backend/go.sum | 4 +- workspaces/backend/openapi/docs.go | 229 ++++++++++++++++++++++++ workspaces/backend/openapi/swagger.json | 229 ++++++++++++++++++++++++ 5 files changed, 465 insertions(+), 6 deletions(-) diff --git a/workspaces/backend/Makefile b/workspaces/backend/Makefile index 55925977..d139d313 100644 --- a/workspaces/backend/Makefile +++ b/workspaces/backend/Makefile @@ -74,7 +74,7 @@ SWAG_DIRS := cmd,$(ALL_GO_DIRS_NO_CMD) .PHONY: swag swag: SWAGGER $(SWAGGER) fmt -g main.go -d $(SWAG_DIRS) - $(SWAGGER) init --parseDependency -q -g main.go -d $(SWAG_DIRS) --output openapi --outputTypes go,json + $(SWAGGER) init --parseDependency -q -g main.go -d $(SWAG_DIRS) --output openapi --outputTypes go,json --requiredByDefault ##@ Build @@ -82,7 +82,7 @@ swag: SWAGGER build: fmt vet swag ## Build backend binary. go build -o bin/backend cmd/main.go -.PHONY: run +.PHONY: run run: fmt vet swag ## Run a backend from your host. go run ./cmd/main.go --port=$(PORT) @@ -131,7 +131,7 @@ SWAGGER = $(LOCALBIN)/swag ## Tool Versions ENVTEST_VERSION ?= release-0.19 GOLANGCI_LINT_VERSION ?= v1.61.0 -SWAGGER_VERSION ?= v1.16.4 +SWAGGER_VERSION ?= v1.16.6 .PHONY: SWAGGER SWAGGER: $(SWAGGER) diff --git a/workspaces/backend/go.mod b/workspaces/backend/go.mod index ab546a80..ef15dbbc 100644 --- a/workspaces/backend/go.mod +++ b/workspaces/backend/go.mod @@ -10,7 +10,7 @@ require ( github.com/onsi/ginkgo/v2 v2.19.0 github.com/onsi/gomega v1.33.1 github.com/swaggo/http-swagger/v2 v2.0.2 - github.com/swaggo/swag v1.16.4 + github.com/swaggo/swag v1.16.6 k8s.io/api v0.31.0 k8s.io/apimachinery v0.31.0 k8s.io/apiserver v0.31.0 @@ -80,6 +80,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect + golang.org/x/mod v0.23.0 // indirect golang.org/x/net v0.35.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.11.0 // indirect diff --git a/workspaces/backend/go.sum b/workspaces/backend/go.sum index 4e67123c..8d0397b1 100644 --- a/workspaces/backend/go.sum +++ b/workspaces/backend/go.sum @@ -128,8 +128,8 @@ github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU github.com/swaggo/files/v2 v2.0.2/go.mod h1:TVqetIzZsO9OhHX1Am9sRf9LdrFZqoK49N37KON/jr0= github.com/swaggo/http-swagger/v2 v2.0.2 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSyIKC9OBg= github.com/swaggo/http-swagger/v2 v2.0.2/go.mod h1:r7/GBkAWIfK6E/OLnE8fXnviHiDeAHmgIyooa4xm3AQ= -github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= -github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= +github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= +github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/workspaces/backend/openapi/docs.go b/workspaces/backend/openapi/docs.go index 616bb409..d0b29075 100644 --- a/workspaces/backend/openapi/docs.go +++ b/workspaces/backend/openapi/docs.go @@ -697,6 +697,9 @@ const docTemplate = `{ "definitions": { "actions.WorkspaceActionPause": { "type": "object", + "required": [ + "paused" + ], "properties": { "paused": { "type": "boolean" @@ -716,6 +719,9 @@ const docTemplate = `{ }, "api.ErrorEnvelope": { "type": "object", + "required": [ + "error" + ], "properties": { "error": { "$ref": "#/definitions/api.HTTPError" @@ -724,6 +730,10 @@ const docTemplate = `{ }, "api.HTTPError": { "type": "object", + "required": [ + "code", + "message" + ], "properties": { "cause": { "$ref": "#/definitions/api.ErrorCause" @@ -738,6 +748,9 @@ const docTemplate = `{ }, "api.NamespaceListEnvelope": { "type": "object", + "required": [ + "data" + ], "properties": { "data": { "type": "array", @@ -749,6 +762,11 @@ const docTemplate = `{ }, "api.ValidationError": { "type": "object", + "required": [ + "field", + "message", + "type" + ], "properties": { "field": { "type": "string" @@ -763,6 +781,9 @@ const docTemplate = `{ }, "api.WorkspaceActionPauseEnvelope": { "type": "object", + "required": [ + "data" + ], "properties": { "data": { "$ref": "#/definitions/actions.WorkspaceActionPause" @@ -771,6 +792,9 @@ const docTemplate = `{ }, "api.WorkspaceCreateEnvelope": { "type": "object", + "required": [ + "data" + ], "properties": { "data": { "$ref": "#/definitions/workspaces.WorkspaceCreate" @@ -779,6 +803,9 @@ const docTemplate = `{ }, "api.WorkspaceEnvelope": { "type": "object", + "required": [ + "data" + ], "properties": { "data": { "$ref": "#/definitions/workspaces.Workspace" @@ -787,6 +814,9 @@ const docTemplate = `{ }, "api.WorkspaceKindEnvelope": { "type": "object", + "required": [ + "data" + ], "properties": { "data": { "$ref": "#/definitions/workspacekinds.WorkspaceKind" @@ -795,6 +825,9 @@ const docTemplate = `{ }, "api.WorkspaceKindListEnvelope": { "type": "object", + "required": [ + "data" + ], "properties": { "data": { "type": "array", @@ -806,6 +839,9 @@ const docTemplate = `{ }, "api.WorkspaceListEnvelope": { "type": "object", + "required": [ + "data" + ], "properties": { "data": { "type": "array", @@ -844,6 +880,10 @@ const docTemplate = `{ }, "health_check.HealthCheck": { "type": "object", + "required": [ + "status", + "systemInfo" + ], "properties": { "status": { "$ref": "#/definitions/health_check.ServiceStatus" @@ -866,6 +906,9 @@ const docTemplate = `{ }, "health_check.SystemInfo": { "type": "object", + "required": [ + "version" + ], "properties": { "version": { "type": "string" @@ -874,6 +917,9 @@ const docTemplate = `{ }, "namespaces.Namespace": { "type": "object", + "required": [ + "name" + ], "properties": { "name": { "type": "string" @@ -882,6 +928,10 @@ const docTemplate = `{ }, "workspacekinds.ImageConfig": { "type": "object", + "required": [ + "default", + "values" + ], "properties": { "default": { "type": "string" @@ -896,6 +946,13 @@ const docTemplate = `{ }, "workspacekinds.ImageConfigValue": { "type": "object", + "required": [ + "description", + "displayName", + "hidden", + "id", + "labels" + ], "properties": { "clusterMetrics": { "$ref": "#/definitions/workspacekinds.clusterMetrics" @@ -925,6 +982,9 @@ const docTemplate = `{ }, "workspacekinds.ImageRef": { "type": "object", + "required": [ + "url" + ], "properties": { "url": { "type": "string" @@ -933,6 +993,10 @@ const docTemplate = `{ }, "workspacekinds.OptionLabel": { "type": "object", + "required": [ + "key", + "value" + ], "properties": { "key": { "type": "string" @@ -944,6 +1008,9 @@ const docTemplate = `{ }, "workspacekinds.OptionRedirect": { "type": "object", + "required": [ + "to" + ], "properties": { "message": { "$ref": "#/definitions/workspacekinds.RedirectMessage" @@ -955,6 +1022,10 @@ const docTemplate = `{ }, "workspacekinds.PodConfig": { "type": "object", + "required": [ + "default", + "values" + ], "properties": { "default": { "type": "string" @@ -969,6 +1040,13 @@ const docTemplate = `{ }, "workspacekinds.PodConfigValue": { "type": "object", + "required": [ + "description", + "displayName", + "hidden", + "id", + "labels" + ], "properties": { "clusterMetrics": { "$ref": "#/definitions/workspacekinds.clusterMetrics" @@ -998,6 +1076,10 @@ const docTemplate = `{ }, "workspacekinds.PodMetadata": { "type": "object", + "required": [ + "annotations", + "labels" + ], "properties": { "annotations": { "type": "object", @@ -1015,6 +1097,11 @@ const docTemplate = `{ }, "workspacekinds.PodTemplate": { "type": "object", + "required": [ + "options", + "podMetadata", + "volumeMounts" + ], "properties": { "options": { "$ref": "#/definitions/workspacekinds.PodTemplateOptions" @@ -1029,6 +1116,10 @@ const docTemplate = `{ }, "workspacekinds.PodTemplateOptions": { "type": "object", + "required": [ + "imageConfig", + "podConfig" + ], "properties": { "imageConfig": { "$ref": "#/definitions/workspacekinds.ImageConfig" @@ -1040,6 +1131,9 @@ const docTemplate = `{ }, "workspacekinds.PodVolumeMounts": { "type": "object", + "required": [ + "home" + ], "properties": { "home": { "type": "string" @@ -1048,6 +1142,10 @@ const docTemplate = `{ }, "workspacekinds.RedirectMessage": { "type": "object", + "required": [ + "level", + "text" + ], "properties": { "level": { "$ref": "#/definitions/workspacekinds.RedirectMessageLevel" @@ -1072,6 +1170,17 @@ const docTemplate = `{ }, "workspacekinds.WorkspaceKind": { "type": "object", + "required": [ + "deprecated", + "deprecationMessage", + "description", + "displayName", + "hidden", + "icon", + "logo", + "name", + "podTemplate" + ], "properties": { "clusterMetrics": { "$ref": "#/definitions/workspacekinds.clusterMetrics" @@ -1107,6 +1216,9 @@ const docTemplate = `{ }, "workspacekinds.clusterMetrics": { "type": "object", + "required": [ + "workspacesCount" + ], "properties": { "workspacesCount": { "type": "integer" @@ -1115,6 +1227,10 @@ const docTemplate = `{ }, "workspaces.Activity": { "type": "object", + "required": [ + "lastActivity", + "lastUpdate" + ], "properties": { "lastActivity": { "description": "Unix Epoch time", @@ -1131,6 +1247,10 @@ const docTemplate = `{ }, "workspaces.HttpService": { "type": "object", + "required": [ + "displayName", + "httpPath" + ], "properties": { "displayName": { "type": "string" @@ -1142,6 +1262,9 @@ const docTemplate = `{ }, "workspaces.ImageConfig": { "type": "object", + "required": [ + "current" + ], "properties": { "current": { "$ref": "#/definitions/workspaces.OptionInfo" @@ -1159,6 +1282,9 @@ const docTemplate = `{ }, "workspaces.ImageRef": { "type": "object", + "required": [ + "url" + ], "properties": { "url": { "type": "string" @@ -1167,6 +1293,12 @@ const docTemplate = `{ }, "workspaces.LastProbeInfo": { "type": "object", + "required": [ + "endTimeMs", + "message", + "result", + "startTimeMs" + ], "properties": { "endTimeMs": { "description": "Unix Epoch time in milliseconds", @@ -1186,6 +1318,12 @@ const docTemplate = `{ }, "workspaces.OptionInfo": { "type": "object", + "required": [ + "description", + "displayName", + "id", + "labels" + ], "properties": { "description": { "type": "string" @@ -1206,6 +1344,10 @@ const docTemplate = `{ }, "workspaces.OptionLabel": { "type": "object", + "required": [ + "key", + "value" + ], "properties": { "key": { "type": "string" @@ -1217,6 +1359,9 @@ const docTemplate = `{ }, "workspaces.PodConfig": { "type": "object", + "required": [ + "current" + ], "properties": { "current": { "$ref": "#/definitions/workspaces.OptionInfo" @@ -1234,6 +1379,10 @@ const docTemplate = `{ }, "workspaces.PodMetadata": { "type": "object", + "required": [ + "annotations", + "labels" + ], "properties": { "annotations": { "type": "object", @@ -1251,6 +1400,10 @@ const docTemplate = `{ }, "workspaces.PodMetadataMutate": { "type": "object", + "required": [ + "annotations", + "labels" + ], "properties": { "annotations": { "type": "object", @@ -1268,6 +1421,10 @@ const docTemplate = `{ }, "workspaces.PodSecretInfo": { "type": "object", + "required": [ + "mountPath", + "secretName" + ], "properties": { "defaultMode": { "type": "integer" @@ -1282,6 +1439,10 @@ const docTemplate = `{ }, "workspaces.PodSecretMount": { "type": "object", + "required": [ + "mountPath", + "secretName" + ], "properties": { "defaultMode": { "type": "integer" @@ -1296,6 +1457,11 @@ const docTemplate = `{ }, "workspaces.PodTemplate": { "type": "object", + "required": [ + "options", + "podMetadata", + "volumes" + ], "properties": { "options": { "$ref": "#/definitions/workspaces.PodTemplateOptions" @@ -1310,6 +1476,11 @@ const docTemplate = `{ }, "workspaces.PodTemplateMutate": { "type": "object", + "required": [ + "options", + "podMetadata", + "volumes" + ], "properties": { "options": { "$ref": "#/definitions/workspaces.PodTemplateOptionsMutate" @@ -1324,6 +1495,10 @@ const docTemplate = `{ }, "workspaces.PodTemplateOptions": { "type": "object", + "required": [ + "imageConfig", + "podConfig" + ], "properties": { "imageConfig": { "$ref": "#/definitions/workspaces.ImageConfig" @@ -1335,6 +1510,10 @@ const docTemplate = `{ }, "workspaces.PodTemplateOptionsMutate": { "type": "object", + "required": [ + "imageConfig", + "podConfig" + ], "properties": { "imageConfig": { "type": "string" @@ -1346,6 +1525,11 @@ const docTemplate = `{ }, "workspaces.PodVolumeInfo": { "type": "object", + "required": [ + "mountPath", + "pvcName", + "readOnly" + ], "properties": { "mountPath": { "type": "string" @@ -1360,6 +1544,10 @@ const docTemplate = `{ }, "workspaces.PodVolumeMount": { "type": "object", + "required": [ + "mountPath", + "pvcName" + ], "properties": { "mountPath": { "type": "string" @@ -1374,6 +1562,9 @@ const docTemplate = `{ }, "workspaces.PodVolumes": { "type": "object", + "required": [ + "data" + ], "properties": { "data": { "type": "array", @@ -1394,6 +1585,9 @@ const docTemplate = `{ }, "workspaces.PodVolumesMutate": { "type": "object", + "required": [ + "data" + ], "properties": { "data": { "type": "array", @@ -1427,6 +1621,10 @@ const docTemplate = `{ }, "workspaces.RedirectMessage": { "type": "object", + "required": [ + "level", + "text" + ], "properties": { "level": { "$ref": "#/definitions/workspaces.RedirectMessageLevel" @@ -1451,6 +1649,10 @@ const docTemplate = `{ }, "workspaces.RedirectStep": { "type": "object", + "required": [ + "sourceId", + "targetId" + ], "properties": { "message": { "$ref": "#/definitions/workspaces.RedirectMessage" @@ -1473,6 +1675,20 @@ const docTemplate = `{ }, "workspaces.Workspace": { "type": "object", + "required": [ + "activity", + "deferUpdates", + "name", + "namespace", + "paused", + "pausedTime", + "pendingRestart", + "podTemplate", + "services", + "state", + "stateMessage", + "workspaceKind" + ], "properties": { "activity": { "$ref": "#/definitions/workspaces.Activity" @@ -1517,6 +1733,13 @@ const docTemplate = `{ }, "workspaces.WorkspaceCreate": { "type": "object", + "required": [ + "deferUpdates", + "kind", + "name", + "paused", + "podTemplate" + ], "properties": { "deferUpdates": { "type": "boolean" @@ -1537,6 +1760,12 @@ const docTemplate = `{ }, "workspaces.WorkspaceKindInfo": { "type": "object", + "required": [ + "icon", + "logo", + "missing", + "name" + ], "properties": { "icon": { "$ref": "#/definitions/workspaces.ImageRef" diff --git a/workspaces/backend/openapi/swagger.json b/workspaces/backend/openapi/swagger.json index 6260cf1b..33669b8a 100644 --- a/workspaces/backend/openapi/swagger.json +++ b/workspaces/backend/openapi/swagger.json @@ -695,6 +695,9 @@ "definitions": { "actions.WorkspaceActionPause": { "type": "object", + "required": [ + "paused" + ], "properties": { "paused": { "type": "boolean" @@ -714,6 +717,9 @@ }, "api.ErrorEnvelope": { "type": "object", + "required": [ + "error" + ], "properties": { "error": { "$ref": "#/definitions/api.HTTPError" @@ -722,6 +728,10 @@ }, "api.HTTPError": { "type": "object", + "required": [ + "code", + "message" + ], "properties": { "cause": { "$ref": "#/definitions/api.ErrorCause" @@ -736,6 +746,9 @@ }, "api.NamespaceListEnvelope": { "type": "object", + "required": [ + "data" + ], "properties": { "data": { "type": "array", @@ -747,6 +760,11 @@ }, "api.ValidationError": { "type": "object", + "required": [ + "field", + "message", + "type" + ], "properties": { "field": { "type": "string" @@ -761,6 +779,9 @@ }, "api.WorkspaceActionPauseEnvelope": { "type": "object", + "required": [ + "data" + ], "properties": { "data": { "$ref": "#/definitions/actions.WorkspaceActionPause" @@ -769,6 +790,9 @@ }, "api.WorkspaceCreateEnvelope": { "type": "object", + "required": [ + "data" + ], "properties": { "data": { "$ref": "#/definitions/workspaces.WorkspaceCreate" @@ -777,6 +801,9 @@ }, "api.WorkspaceEnvelope": { "type": "object", + "required": [ + "data" + ], "properties": { "data": { "$ref": "#/definitions/workspaces.Workspace" @@ -785,6 +812,9 @@ }, "api.WorkspaceKindEnvelope": { "type": "object", + "required": [ + "data" + ], "properties": { "data": { "$ref": "#/definitions/workspacekinds.WorkspaceKind" @@ -793,6 +823,9 @@ }, "api.WorkspaceKindListEnvelope": { "type": "object", + "required": [ + "data" + ], "properties": { "data": { "type": "array", @@ -804,6 +837,9 @@ }, "api.WorkspaceListEnvelope": { "type": "object", + "required": [ + "data" + ], "properties": { "data": { "type": "array", @@ -842,6 +878,10 @@ }, "health_check.HealthCheck": { "type": "object", + "required": [ + "status", + "systemInfo" + ], "properties": { "status": { "$ref": "#/definitions/health_check.ServiceStatus" @@ -864,6 +904,9 @@ }, "health_check.SystemInfo": { "type": "object", + "required": [ + "version" + ], "properties": { "version": { "type": "string" @@ -872,6 +915,9 @@ }, "namespaces.Namespace": { "type": "object", + "required": [ + "name" + ], "properties": { "name": { "type": "string" @@ -880,6 +926,10 @@ }, "workspacekinds.ImageConfig": { "type": "object", + "required": [ + "default", + "values" + ], "properties": { "default": { "type": "string" @@ -894,6 +944,13 @@ }, "workspacekinds.ImageConfigValue": { "type": "object", + "required": [ + "description", + "displayName", + "hidden", + "id", + "labels" + ], "properties": { "clusterMetrics": { "$ref": "#/definitions/workspacekinds.clusterMetrics" @@ -923,6 +980,9 @@ }, "workspacekinds.ImageRef": { "type": "object", + "required": [ + "url" + ], "properties": { "url": { "type": "string" @@ -931,6 +991,10 @@ }, "workspacekinds.OptionLabel": { "type": "object", + "required": [ + "key", + "value" + ], "properties": { "key": { "type": "string" @@ -942,6 +1006,9 @@ }, "workspacekinds.OptionRedirect": { "type": "object", + "required": [ + "to" + ], "properties": { "message": { "$ref": "#/definitions/workspacekinds.RedirectMessage" @@ -953,6 +1020,10 @@ }, "workspacekinds.PodConfig": { "type": "object", + "required": [ + "default", + "values" + ], "properties": { "default": { "type": "string" @@ -967,6 +1038,13 @@ }, "workspacekinds.PodConfigValue": { "type": "object", + "required": [ + "description", + "displayName", + "hidden", + "id", + "labels" + ], "properties": { "clusterMetrics": { "$ref": "#/definitions/workspacekinds.clusterMetrics" @@ -996,6 +1074,10 @@ }, "workspacekinds.PodMetadata": { "type": "object", + "required": [ + "annotations", + "labels" + ], "properties": { "annotations": { "type": "object", @@ -1013,6 +1095,11 @@ }, "workspacekinds.PodTemplate": { "type": "object", + "required": [ + "options", + "podMetadata", + "volumeMounts" + ], "properties": { "options": { "$ref": "#/definitions/workspacekinds.PodTemplateOptions" @@ -1027,6 +1114,10 @@ }, "workspacekinds.PodTemplateOptions": { "type": "object", + "required": [ + "imageConfig", + "podConfig" + ], "properties": { "imageConfig": { "$ref": "#/definitions/workspacekinds.ImageConfig" @@ -1038,6 +1129,9 @@ }, "workspacekinds.PodVolumeMounts": { "type": "object", + "required": [ + "home" + ], "properties": { "home": { "type": "string" @@ -1046,6 +1140,10 @@ }, "workspacekinds.RedirectMessage": { "type": "object", + "required": [ + "level", + "text" + ], "properties": { "level": { "$ref": "#/definitions/workspacekinds.RedirectMessageLevel" @@ -1070,6 +1168,17 @@ }, "workspacekinds.WorkspaceKind": { "type": "object", + "required": [ + "deprecated", + "deprecationMessage", + "description", + "displayName", + "hidden", + "icon", + "logo", + "name", + "podTemplate" + ], "properties": { "clusterMetrics": { "$ref": "#/definitions/workspacekinds.clusterMetrics" @@ -1105,6 +1214,9 @@ }, "workspacekinds.clusterMetrics": { "type": "object", + "required": [ + "workspacesCount" + ], "properties": { "workspacesCount": { "type": "integer" @@ -1113,6 +1225,10 @@ }, "workspaces.Activity": { "type": "object", + "required": [ + "lastActivity", + "lastUpdate" + ], "properties": { "lastActivity": { "description": "Unix Epoch time", @@ -1129,6 +1245,10 @@ }, "workspaces.HttpService": { "type": "object", + "required": [ + "displayName", + "httpPath" + ], "properties": { "displayName": { "type": "string" @@ -1140,6 +1260,9 @@ }, "workspaces.ImageConfig": { "type": "object", + "required": [ + "current" + ], "properties": { "current": { "$ref": "#/definitions/workspaces.OptionInfo" @@ -1157,6 +1280,9 @@ }, "workspaces.ImageRef": { "type": "object", + "required": [ + "url" + ], "properties": { "url": { "type": "string" @@ -1165,6 +1291,12 @@ }, "workspaces.LastProbeInfo": { "type": "object", + "required": [ + "endTimeMs", + "message", + "result", + "startTimeMs" + ], "properties": { "endTimeMs": { "description": "Unix Epoch time in milliseconds", @@ -1184,6 +1316,12 @@ }, "workspaces.OptionInfo": { "type": "object", + "required": [ + "description", + "displayName", + "id", + "labels" + ], "properties": { "description": { "type": "string" @@ -1204,6 +1342,10 @@ }, "workspaces.OptionLabel": { "type": "object", + "required": [ + "key", + "value" + ], "properties": { "key": { "type": "string" @@ -1215,6 +1357,9 @@ }, "workspaces.PodConfig": { "type": "object", + "required": [ + "current" + ], "properties": { "current": { "$ref": "#/definitions/workspaces.OptionInfo" @@ -1232,6 +1377,10 @@ }, "workspaces.PodMetadata": { "type": "object", + "required": [ + "annotations", + "labels" + ], "properties": { "annotations": { "type": "object", @@ -1249,6 +1398,10 @@ }, "workspaces.PodMetadataMutate": { "type": "object", + "required": [ + "annotations", + "labels" + ], "properties": { "annotations": { "type": "object", @@ -1266,6 +1419,10 @@ }, "workspaces.PodSecretInfo": { "type": "object", + "required": [ + "mountPath", + "secretName" + ], "properties": { "defaultMode": { "type": "integer" @@ -1280,6 +1437,10 @@ }, "workspaces.PodSecretMount": { "type": "object", + "required": [ + "mountPath", + "secretName" + ], "properties": { "defaultMode": { "type": "integer" @@ -1294,6 +1455,11 @@ }, "workspaces.PodTemplate": { "type": "object", + "required": [ + "options", + "podMetadata", + "volumes" + ], "properties": { "options": { "$ref": "#/definitions/workspaces.PodTemplateOptions" @@ -1308,6 +1474,11 @@ }, "workspaces.PodTemplateMutate": { "type": "object", + "required": [ + "options", + "podMetadata", + "volumes" + ], "properties": { "options": { "$ref": "#/definitions/workspaces.PodTemplateOptionsMutate" @@ -1322,6 +1493,10 @@ }, "workspaces.PodTemplateOptions": { "type": "object", + "required": [ + "imageConfig", + "podConfig" + ], "properties": { "imageConfig": { "$ref": "#/definitions/workspaces.ImageConfig" @@ -1333,6 +1508,10 @@ }, "workspaces.PodTemplateOptionsMutate": { "type": "object", + "required": [ + "imageConfig", + "podConfig" + ], "properties": { "imageConfig": { "type": "string" @@ -1344,6 +1523,11 @@ }, "workspaces.PodVolumeInfo": { "type": "object", + "required": [ + "mountPath", + "pvcName", + "readOnly" + ], "properties": { "mountPath": { "type": "string" @@ -1358,6 +1542,10 @@ }, "workspaces.PodVolumeMount": { "type": "object", + "required": [ + "mountPath", + "pvcName" + ], "properties": { "mountPath": { "type": "string" @@ -1372,6 +1560,9 @@ }, "workspaces.PodVolumes": { "type": "object", + "required": [ + "data" + ], "properties": { "data": { "type": "array", @@ -1392,6 +1583,9 @@ }, "workspaces.PodVolumesMutate": { "type": "object", + "required": [ + "data" + ], "properties": { "data": { "type": "array", @@ -1425,6 +1619,10 @@ }, "workspaces.RedirectMessage": { "type": "object", + "required": [ + "level", + "text" + ], "properties": { "level": { "$ref": "#/definitions/workspaces.RedirectMessageLevel" @@ -1449,6 +1647,10 @@ }, "workspaces.RedirectStep": { "type": "object", + "required": [ + "sourceId", + "targetId" + ], "properties": { "message": { "$ref": "#/definitions/workspaces.RedirectMessage" @@ -1471,6 +1673,20 @@ }, "workspaces.Workspace": { "type": "object", + "required": [ + "activity", + "deferUpdates", + "name", + "namespace", + "paused", + "pausedTime", + "pendingRestart", + "podTemplate", + "services", + "state", + "stateMessage", + "workspaceKind" + ], "properties": { "activity": { "$ref": "#/definitions/workspaces.Activity" @@ -1515,6 +1731,13 @@ }, "workspaces.WorkspaceCreate": { "type": "object", + "required": [ + "deferUpdates", + "kind", + "name", + "paused", + "podTemplate" + ], "properties": { "deferUpdates": { "type": "boolean" @@ -1535,6 +1758,12 @@ }, "workspaces.WorkspaceKindInfo": { "type": "object", + "required": [ + "icon", + "logo", + "missing", + "name" + ], "properties": { "icon": { "$ref": "#/definitions/workspaces.ImageRef" From 2d5b8304d71aa940317c3fb753121bf33d48e599 Mon Sep 17 00:00:00 2001 From: Paulo Rego <832830+paulovmr@users.noreply.github.com> Date: Tue, 5 Aug 2025 09:27:53 -0300 Subject: [PATCH 51/68] chore(ws): comment workspace details logs and pod template tabs while they are not supported (#512) * chore(ws): comment workspace details logs tab while it is not supported Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> * chore(ws): comment workspace details pod template tab while it is not supported Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> --------- Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> --- .../src/app/pages/Workspaces/Details/WorkspaceDetails.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetails.tsx index 226291c2..b5598a2f 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetails.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetails.tsx @@ -68,18 +68,22 @@ export const WorkspaceDetails: React.FunctionComponent = aria-label="Activity" data-testid="activityTab" /> + {/* TODO: Uncomment when Logs visualization is fully supported Logs} tabContentId="logsTabContent" aria-label="Logs" /> + */} + {/* TODO: Uncomment when Pod template visualization is fully supported Pod template} tabContentId="podTemplateTabContent" aria-label="Pod template" /> + */} From dcf6b93a468863b2bcbb1d30949eac03322b78d7 Mon Sep 17 00:00:00 2001 From: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> Date: Tue, 5 Aug 2025 09:28:53 -0300 Subject: [PATCH 52/68] feat(ws): automate generation of types and HTTP client layer from Swagger definitions (#496) Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> --- workspaces/frontend/README.md | 22 + workspaces/frontend/package-lock.json | 610 +++++++++++++++++- workspaces/frontend/package.json | 5 +- workspaces/frontend/scripts/generate-api.sh | 27 + workspaces/frontend/scripts/swagger.version | 1 + .../frontend/src/__mocks__/mockNamespaces.ts | 4 +- workspaces/frontend/src/__mocks__/utils.ts | 6 +- .../cypress/tests/mocked/workspace.mock.ts | 22 +- .../tests/mocked/workspaceKinds.mock.ts | 20 +- .../tests/mocked/workspaces/Workspaces.cy.ts | 8 +- .../src/app/actions/WorkspaceKindsActions.tsx | 13 +- .../app/components/ValidationErrorAlert.tsx | 9 +- .../src/app/components/WorkspaceTable.tsx | 28 +- .../app/context/WorkspaceActionsContext.tsx | 29 +- .../src/app/context/useNotebookAPIState.tsx | 95 +-- .../useWorkspaceCountPerKind.spec.tsx | 70 +- .../frontend/src/app/hooks/useNamespaces.ts | 24 +- .../src/app/hooks/useWorkspaceCountPerKind.ts | 31 +- .../src/app/hooks/useWorkspaceFormData.ts | 79 +-- .../src/app/hooks/useWorkspaceKindByName.ts | 28 +- .../src/app/hooks/useWorkspaceKinds.ts | 25 +- .../src/app/hooks/useWorkspaceRowActions.ts | 10 +- .../frontend/src/app/hooks/useWorkspaces.ts | 71 +- .../WorkspaceKinds/Form/EditableLabels.tsx | 14 +- .../WorkspaceKinds/Form/WorkspaceKindForm.tsx | 63 +- .../Form/WorkspaceKindFormPaginatedTable.tsx | 5 +- .../app/pages/WorkspaceKinds/Form/helpers.ts | 9 +- .../image/WorkspaceKindFormImageRedirect.tsx | 27 +- .../WorkspaceKindFormPodConfigModal.tsx | 4 +- .../WorkspaceKindFormPodTemplate.tsx | 6 +- .../pages/WorkspaceKinds/WorkspaceKinds.tsx | 13 +- .../details/WorkspaceKindDetails.tsx | 4 +- .../details/WorkspaceKindDetailsImages.tsx | 4 +- .../WorkspaceKindDetailsNamespaces.tsx | 4 +- .../details/WorkspaceKindDetailsOverview.tsx | 4 +- .../WorkspaceKindDetailsPodConfigs.tsx | 4 +- .../WorkspaceKindSummaryExpandableCard.tsx | 4 +- .../app/pages/Workspaces/DataVolumesList.tsx | 4 +- .../Workspaces/Details/WorkspaceDetails.tsx | 4 +- .../Details/WorkspaceDetailsActivity.tsx | 4 +- .../Details/WorkspaceDetailsOverview.tsx | 4 +- .../pages/Workspaces/ExpandedWorkspaceRow.tsx | 4 +- .../pages/Workspaces/Form/WorkspaceForm.tsx | 30 +- .../Form/image/WorkspaceFormImageDetails.tsx | 4 +- .../Form/image/WorkspaceFormImageList.tsx | 10 +- .../image/WorkspaceFormImageSelection.tsx | 8 +- .../Form/kind/WorkspaceFormKindDetails.tsx | 4 +- .../Form/kind/WorkspaceFormKindList.tsx | 8 +- .../Form/kind/WorkspaceFormKindSelection.tsx | 6 +- .../Form/labelFilter/FilterByLabels.tsx | 4 +- .../WorkspaceFormPodConfigDetails.tsx | 4 +- .../podConfig/WorkspaceFormPodConfigList.tsx | 10 +- .../WorkspaceFormPodConfigSelection.tsx | 8 +- .../WorkspaceFormPropertiesSecrets.tsx | 8 +- .../WorkspaceFormPropertiesSelection.tsx | 4 +- .../WorkspaceFormPropertiesVolumes.tsx | 8 +- .../Workspaces/WorkspaceConfigDetails.tsx | 4 +- .../Workspaces/WorkspaceConnectAction.tsx | 6 +- .../Workspaces/WorkspacePackageDetails.tsx | 4 +- .../app/pages/Workspaces/WorkspaceStorage.tsx | 4 +- .../src/app/pages/Workspaces/Workspaces.tsx | 8 +- .../WorkspaceRedirectInformationView.tsx | 6 +- .../WorkspaceRestartActionModal.tsx | 4 +- .../WorkspaceStartActionModal.tsx | 20 +- .../WorkspaceStopActionModal.tsx | 20 +- workspaces/frontend/src/app/types.ts | 50 +- .../frontend/src/generated/Healthcheck.ts | 34 + .../frontend/src/generated/Namespaces.ts | 36 ++ .../frontend/src/generated/Workspacekinds.ts | 89 +++ .../frontend/src/generated/Workspaces.ts | 167 +++++ .../frontend/src/generated/data-contracts.ts | 373 +++++++++++ .../frontend/src/generated/http-client.ts | 163 +++++ .../shared/api/__tests__/errorUtils.spec.ts | 36 -- .../api/__tests__/notebookService.spec.ts | 35 - .../frontend/src/shared/api/apiUtils.ts | 251 +------ .../src/shared/api/backendApiTypes.ts | 322 --------- .../frontend/src/shared/api/callTypes.ts | 47 -- .../frontend/src/shared/api/errorUtils.ts | 16 - .../frontend/src/shared/api/experimental.ts | 23 + .../frontend/src/shared/api/notebookApi.ts | 110 +--- .../src/shared/api/notebookService.ts | 78 --- workspaces/frontend/src/shared/api/types.ts | 19 +- .../frontend/src/shared/mock/mockBuilder.ts | 72 ++- .../src/shared/mock/mockNotebookApis.ts | 83 +++ .../src/shared/mock/mockNotebookService.ts | 107 --- .../shared/mock/mockNotebookServiceData.ts | 38 +- .../frontend/src/shared/mock/mockUtils.ts | 31 + .../src/shared/utilities/WorkspaceUtils.ts | 45 +- .../frontend/src/shared/utilities/const.ts | 10 +- 89 files changed, 2277 insertions(+), 1572 deletions(-) create mode 100755 workspaces/frontend/scripts/generate-api.sh create mode 100644 workspaces/frontend/scripts/swagger.version create mode 100644 workspaces/frontend/src/generated/Healthcheck.ts create mode 100644 workspaces/frontend/src/generated/Namespaces.ts create mode 100644 workspaces/frontend/src/generated/Workspacekinds.ts create mode 100644 workspaces/frontend/src/generated/Workspaces.ts create mode 100644 workspaces/frontend/src/generated/data-contracts.ts create mode 100644 workspaces/frontend/src/generated/http-client.ts delete mode 100644 workspaces/frontend/src/shared/api/__tests__/errorUtils.spec.ts delete mode 100644 workspaces/frontend/src/shared/api/__tests__/notebookService.spec.ts delete mode 100644 workspaces/frontend/src/shared/api/backendApiTypes.ts delete mode 100644 workspaces/frontend/src/shared/api/callTypes.ts delete mode 100644 workspaces/frontend/src/shared/api/errorUtils.ts create mode 100644 workspaces/frontend/src/shared/api/experimental.ts delete mode 100644 workspaces/frontend/src/shared/api/notebookService.ts create mode 100644 workspaces/frontend/src/shared/mock/mockNotebookApis.ts delete mode 100644 workspaces/frontend/src/shared/mock/mockNotebookService.ts diff --git a/workspaces/frontend/README.md b/workspaces/frontend/README.md index b43fbf1d..c2477b62 100644 --- a/workspaces/frontend/README.md +++ b/workspaces/frontend/README.md @@ -77,3 +77,25 @@ Automatically fix linting issues: ```bash npm run test:fix ``` + +### API Types & Client Generation + +The TypeScript types and the HTTP client layer for interacting with the backend APIs are automatically generated from the backend's `swagger.json` file. This ensures the frontend remains aligned with the backend API contract at all times. + +#### Generated Code Location + +All generated files live in the `src/generated` directory. + +⚠️ Do not manually edit any files in this folder. + +#### Updating the Generated Code + +To update the generated code, first update the `swagger.version` file in the `scripts` directory to the desired commit hash of the backend's `swagger.json` file. + +Then run the following command to update the generated code: + +```bash +npm run generate:api +``` + +Finally, make any necessary adaptations based on the changes in the generated code. \ No newline at end of file diff --git a/workspaces/frontend/package-lock.json b/workspaces/frontend/package-lock.json index 3e2c72b9..5687c07d 100644 --- a/workspaces/frontend/package-lock.json +++ b/workspaces/frontend/package-lock.json @@ -18,6 +18,7 @@ "@patternfly/react-table": "^6.2.0", "@patternfly/react-tokens": "^6.2.0", "@types/js-yaml": "^4.0.9", + "axios": "^1.10.0", "date-fns": "^4.1.0", "eslint-plugin-local-rules": "^3.0.2", "js-yaml": "^4.1.0", @@ -109,7 +110,8 @@ "eslint-plugin-no-relative-import-paths": "^1.5.2", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-react": "^7.37.2", - "eslint-plugin-react-hooks": "^5.0.0" + "eslint-plugin-react-hooks": "^5.0.0", + "swagger-typescript-api": "^13.2.7" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1950,6 +1952,34 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@biomejs/js-api": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@biomejs/js-api/-/js-api-1.0.0.tgz", + "integrity": "sha512-69OfQ7+09AtiCIg+k+aU3rEsGit5o/SJWCS3BeBH/2nJYdJGi0cIx+ybka8i1EK69aNcZxYO1y1iAAEmYMq1HA==", + "optional": true, + "peerDependencies": { + "@biomejs/wasm-bundler": "^2.0.0", + "@biomejs/wasm-nodejs": "^2.0.0", + "@biomejs/wasm-web": "^2.0.0" + }, + "peerDependenciesMeta": { + "@biomejs/wasm-bundler": { + "optional": true + }, + "@biomejs/wasm-nodejs": { + "optional": true + }, + "@biomejs/wasm-web": { + "optional": true + } + } + }, + "node_modules/@biomejs/wasm-nodejs": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@biomejs/wasm-nodejs/-/wasm-nodejs-2.0.5.tgz", + "integrity": "sha512-pihpBMylewgDdGFZHRkgmc3OajuGIJPXhvfYuKCNK/CWyJMrYEFmPKs8Iq1kY0sYMmGlTbD4K2udV03KYa+r0Q==", + "optional": true + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -3088,6 +3118,12 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@exodus/schemasafe": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", + "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==", + "optional": true + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -5734,6 +5770,12 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/swagger-schema-official": { + "version": "2.0.25", + "resolved": "https://registry.npmjs.org/@types/swagger-schema-official/-/swagger-schema-official-2.0.25.tgz", + "integrity": "sha512-T92Xav+Gf/Ik1uPW581nA+JftmjWPgskw/WBf4TJzxRG/SJ+DfNnNE+WuZ4mrXuzflQMqMkm1LSYjzYW7MB1Cg==", + "optional": true + }, "node_modules/@types/testing-library__jest-dom": { "version": "5.14.8", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.8.tgz", @@ -6656,8 +6698,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -6719,6 +6760,21 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/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==" + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -7415,6 +7471,62 @@ "node": ">= 0.8" } }, + "node_modules/c12": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.0.4.tgz", + "integrity": "sha512-t5FaZTYbbCtvxuZq9xxIruYydrAGsJ+8UdP0pZzMiK2xl/gNiSOy0OxhLzHUEEb0m1QXYqfzfvyIFEmz/g9lqg==", + "optional": true, + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.5.0", + "exsolve": "^1.0.5", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.1.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/c12/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==", + "optional": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/cachedir": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", @@ -7501,6 +7613,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "optional": true + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -7818,6 +7936,15 @@ "node": ">=8" } }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "optional": true, + "dependencies": { + "consola": "^3.2.3" + } + }, "node_modules/cjs-module-lexer": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", @@ -7945,7 +8072,7 @@ "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, + "devOptional": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -8023,7 +8150,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -8263,6 +8389,12 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "optional": true + }, "node_modules/connect-history-api-fallback": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", @@ -8272,6 +8404,15 @@ "node": ">=0.8" } }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "optional": true, + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/console-clear": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/console-clear/-/console-clear-1.1.1.tgz", @@ -9780,11 +9921,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "optional": true + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -9807,6 +9953,12 @@ "node": ">=6" } }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "optional": true + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -10014,11 +10166,10 @@ } }, "node_modules/dotenv": { - "version": "16.4.6", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.6.tgz", - "integrity": "sha512-JhcR/+KIjkkjiU8yEpaB/USlzVi3i5whwOjpIRNGi9svKEXZSe+Qp6IWAjFjv+2GViAoDRCUv/QLNziQxsLqDg==", - "dev": true, - "license": "BSD-2-Clause", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "devOptional": true, "engines": { "node": ">=12" }, @@ -10418,11 +10569,17 @@ "dev": true, "license": "MIT" }, + "node_modules/es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", + "optional": true + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -11279,6 +11436,18 @@ "node": ">=0.10.0" } }, + "node_modules/eta": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-2.2.0.tgz", + "integrity": "sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==", + "optional": true, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "url": "https://github.com/eta-dev/eta?sponsor=1" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -11482,6 +11651,12 @@ "node": ">= 0.8" } }, + "node_modules/exsolve": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.5.tgz", + "integrity": "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==", + "optional": true + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -11599,6 +11774,12 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "devOptional": true }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "optional": true + }, "node_modules/fastest-levenshtein": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", @@ -11988,7 +12169,6 @@ "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "dev": true, "funding": [ { "type": "individual", @@ -12234,7 +12414,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -12393,7 +12572,7 @@ "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, + "devOptional": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -12500,6 +12679,23 @@ "assert-plus": "^1.0.0" } }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "optional": true, + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, "node_modules/glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -13082,6 +13278,12 @@ "node": ">=0.10" } }, + "node_modules/http2-client": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", + "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", + "optional": true + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -15841,6 +16043,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "optional": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -16797,7 +17008,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -16806,7 +17016,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -17675,6 +17884,44 @@ "dev": true, "optional": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch-h2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", + "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", + "optional": true, + "dependencies": { + "http2-client": "^1.2.5" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.6.tgz", + "integrity": "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==", + "optional": true + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -17704,6 +17951,15 @@ "node": ">=8" } }, + "node_modules/node-readfiles": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", + "integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==", + "optional": true, + "dependencies": { + "es6-promise": "^3.2.1" + } + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -18090,6 +18346,95 @@ "node": ">=6" } }, + "node_modules/nypm": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.0.tgz", + "integrity": "sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==", + "optional": true, + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "pathe": "^2.0.3", + "pkg-types": "^2.0.0", + "tinyexec": "^0.3.2" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, + "node_modules/oas-kit-common": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", + "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", + "optional": true, + "dependencies": { + "fast-safe-stringify": "^2.0.7" + } + }, + "node_modules/oas-linter": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz", + "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", + "optional": true, + "dependencies": { + "@exodus/schemasafe": "^1.0.0-rc.2", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-resolver": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz", + "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", + "optional": true, + "dependencies": { + "node-fetch-h2": "^2.3.0", + "oas-kit-common": "^1.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + }, + "bin": { + "resolve": "resolve.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-schema-walker": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz", + "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==", + "optional": true, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-validator": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz", + "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", + "optional": true, + "dependencies": { + "call-me-maybe": "^1.0.1", + "oas-kit-common": "^1.0.8", + "oas-linter": "^3.2.2", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "reftools": "^1.1.9", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -18222,6 +18567,12 @@ "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", "dev": true }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "optional": true + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -18582,6 +18933,12 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "optional": true + }, "node_modules/peek-readable": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", @@ -18602,6 +18959,12 @@ "dev": true, "license": "MIT" }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "optional": true + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -18670,6 +19033,17 @@ "node": ">=8" } }, + "node_modules/pkg-types": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.1.0.tgz", + "integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==", + "optional": true, + "dependencies": { + "confbox": "^0.2.1", + "exsolve": "^1.0.1", + "pathe": "^2.0.3" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -19561,6 +19935,16 @@ "node": ">=0.10.0" } }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "optional": true, + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -19808,6 +20192,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/reftools": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", + "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", + "optional": true, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -19985,7 +20378,7 @@ "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, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -20864,6 +21257,60 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "optional": true, + "dependencies": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "node_modules/should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "optional": true, + "dependencies": { + "should-type": "^1.4.0" + } + }, + "node_modules/should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", + "optional": true, + "dependencies": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "node_modules/should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", + "optional": true + }, + "node_modules/should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "optional": true, + "dependencies": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "node_modules/should-util": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", + "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", + "optional": true + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -21647,6 +22094,85 @@ "node": ">= 10" } }, + "node_modules/swagger-schema-official": { + "version": "2.0.0-bab6bed", + "resolved": "https://registry.npmjs.org/swagger-schema-official/-/swagger-schema-official-2.0.0-bab6bed.tgz", + "integrity": "sha512-rCC0NWGKr/IJhtRuPq/t37qvZHI/mH4I4sxflVM+qgVe5Z2uOCivzWaVbuioJaB61kvm5UvB7b49E+oBY0M8jA==", + "optional": true + }, + "node_modules/swagger-typescript-api": { + "version": "13.2.7", + "resolved": "https://registry.npmjs.org/swagger-typescript-api/-/swagger-typescript-api-13.2.7.tgz", + "integrity": "sha512-rfqqoRFpZJPl477M/snMJPM90EvI8WqhuUHSF5ecC2r/w376T29+QXNJFVPsJmbFu5rBc/8m3vhArtMctjONdw==", + "optional": true, + "dependencies": { + "@biomejs/js-api": "1.0.0", + "@biomejs/wasm-nodejs": "2.0.5", + "@types/swagger-schema-official": "^2.0.25", + "c12": "^3.0.4", + "citty": "^0.1.6", + "consola": "^3.4.2", + "eta": "^2.2.0", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "nanoid": "^5.1.5", + "swagger-schema-official": "2.0.0-bab6bed", + "swagger2openapi": "^7.0.8", + "typescript": "~5.8.3" + }, + "bin": { + "sta": "dist/cli.js", + "swagger-typescript-api": "dist/cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/swagger-typescript-api/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "optional": true, + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/swagger2openapi": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", + "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", + "optional": true, + "dependencies": { + "call-me-maybe": "^1.0.1", + "node-fetch": "^2.6.1", + "node-fetch-h2": "^2.3.0", + "node-readfiles": "^0.2.0", + "oas-kit-common": "^1.0.8", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "oas-validator": "^5.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + }, + "bin": { + "boast": "boast.js", + "oas-validate": "oas-validate.js", + "swagger2openapi": "swagger2openapi.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -21874,6 +22400,12 @@ "node": ">=4" } }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "optional": true + }, "node_modules/tldts": { "version": "6.1.58", "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.58.tgz", @@ -21969,6 +22501,12 @@ "node": ">= 4.0.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "optional": true + }, "node_modules/tree-dump": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.3.tgz", @@ -22443,9 +22981,9 @@ } }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "devOptional": true, "bin": { "tsc": "bin/tsc", @@ -22812,6 +23350,12 @@ "minimalistic-assert": "^1.0.0" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "optional": true + }, "node_modules/webpack": { "version": "5.95.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", @@ -23381,6 +23925,16 @@ "node": ">=12" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "optional": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -23571,7 +24125,7 @@ "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, + "devOptional": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -23643,7 +24197,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -23658,7 +24212,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -23670,7 +24224,7 @@ "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 + "devOptional": true }, "node_modules/wrappy": { "version": "1.0.2", @@ -23784,7 +24338,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, + "devOptional": true, "license": "ISC", "engines": { "node": ">=10" @@ -23809,7 +24363,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, + "devOptional": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -23827,7 +24381,7 @@ "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, + "devOptional": true, "engines": { "node": ">=12" } diff --git a/workspaces/frontend/package.json b/workspaces/frontend/package.json index 4654bceb..a8e29468 100644 --- a/workspaces/frontend/package.json +++ b/workspaces/frontend/package.json @@ -19,6 +19,7 @@ "build:bundle-analyze": "webpack-bundle-analyzer ./bundle.stats.json", "build:clean": "rimraf ./dist", "build:prod": "webpack --config ./config/webpack.prod.js", + "generate:api": "./scripts/generate-api.sh && npm run prettier", "start:dev": "cross-env STYLE_THEME=$npm_config_theme webpack serve --hot --color --config ./config/webpack.dev.js", "start:dev:mock": "cross-env MOCK_API_ENABLED=true STYLE_THEME=$npm_config_theme npm run start:dev", "test": "run-s prettier:check test:lint test:unit test:cypress-ci", @@ -113,6 +114,7 @@ "@patternfly/react-table": "^6.2.0", "@patternfly/react-tokens": "^6.2.0", "@types/js-yaml": "^4.0.9", + "axios": "^1.10.0", "date-fns": "^4.1.0", "eslint-plugin-local-rules": "^3.0.2", "js-yaml": "^4.1.0", @@ -138,6 +140,7 @@ "eslint-plugin-no-relative-import-paths": "^1.5.2", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-react": "^7.37.2", - "eslint-plugin-react-hooks": "^5.0.0" + "eslint-plugin-react-hooks": "^5.0.0", + "swagger-typescript-api": "^13.2.7" } } diff --git a/workspaces/frontend/scripts/generate-api.sh b/workspaces/frontend/scripts/generate-api.sh new file mode 100755 index 00000000..c894fcd8 --- /dev/null +++ b/workspaces/frontend/scripts/generate-api.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -euo pipefail + +GENERATED_DIR="./src/generated" +HASH_FILE="./scripts/swagger.version" +SWAGGER_COMMIT_HASH=$(cat "$HASH_FILE") +SWAGGER_JSON_PATH="../backend/openapi/swagger.json" +TMP_SWAGGER=".tmp-swagger.json" + +if ! git cat-file -e "${SWAGGER_COMMIT_HASH}:${SWAGGER_JSON_PATH}"; then + echo "❌ Swagger file not found at commit $SWAGGER_COMMIT_HASH" + exit 1 +fi + +git show "${SWAGGER_COMMIT_HASH}:${SWAGGER_JSON_PATH}" >"$TMP_SWAGGER" + +swagger-typescript-api generate \ + -p "$TMP_SWAGGER" \ + -o "$GENERATED_DIR" \ + --extract-request-body \ + --responses \ + --clean-output \ + --axios \ + --unwrap-response-data \ + --modular + +rm "$TMP_SWAGGER" diff --git a/workspaces/frontend/scripts/swagger.version b/workspaces/frontend/scripts/swagger.version new file mode 100644 index 00000000..21ab894b --- /dev/null +++ b/workspaces/frontend/scripts/swagger.version @@ -0,0 +1 @@ +4f0a29dec0d3c9f0d0f02caab4dc84101bfef8b0 diff --git a/workspaces/frontend/src/__mocks__/mockNamespaces.ts b/workspaces/frontend/src/__mocks__/mockNamespaces.ts index 6265e681..2063b3f0 100644 --- a/workspaces/frontend/src/__mocks__/mockNamespaces.ts +++ b/workspaces/frontend/src/__mocks__/mockNamespaces.ts @@ -1,7 +1,7 @@ +import { NamespacesNamespace } from '~/generated/data-contracts'; import { buildMockNamespace } from '~/shared/mock/mockBuilder'; -import { Namespace } from '~/shared/api/backendApiTypes'; -export const mockNamespaces: Namespace[] = [ +export const mockNamespaces: NamespacesNamespace[] = [ buildMockNamespace({ name: 'default' }), buildMockNamespace({ name: 'kubeflow' }), buildMockNamespace({ name: 'custom-namespace' }), diff --git a/workspaces/frontend/src/__mocks__/utils.ts b/workspaces/frontend/src/__mocks__/utils.ts index d4af2e35..18538b43 100644 --- a/workspaces/frontend/src/__mocks__/utils.ts +++ b/workspaces/frontend/src/__mocks__/utils.ts @@ -1,5 +1,7 @@ -import { ResponseBody } from '~/shared/api/types'; +interface Envelope { + data: T; +} -export const mockBFFResponse = (data: T): ResponseBody => ({ +export const mockBFFResponse = (data: T): Envelope => ({ data, }); diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspace.mock.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspace.mock.ts index 0444639b..a3e2196c 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspace.mock.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspace.mock.ts @@ -1,17 +1,17 @@ -import { WorkspaceState } from '~/shared/api/backendApiTypes'; -import type { Workspace, WorkspaceKindInfo } from '~/shared/api/backendApiTypes'; +import type { WorkspacesWorkspace, WorkspacesWorkspaceKindInfo } from '~/generated/data-contracts'; +import { WorkspacesWorkspaceState } from '~/generated/data-contracts'; const generateMockWorkspace = ( name: string, namespace: string, - state: WorkspaceState, + state: WorkspacesWorkspaceState, paused: boolean, imageConfigId: string, imageConfigDisplayName: string, podConfigId: string, podConfigDisplayName: string, pvcName: string, -): Workspace => { +): WorkspacesWorkspace => { const pausedTime = new Date(2025, 0, 1).getTime(); const lastActivityTime = new Date(2025, 0, 2).getTime(); const lastUpdateTime = new Date(2025, 0, 3).getTime(); @@ -19,16 +19,16 @@ const generateMockWorkspace = ( return { name, namespace, - workspaceKind: { name: 'jupyterlab' } as WorkspaceKindInfo, + workspaceKind: { name: 'jupyterlab' } as WorkspacesWorkspaceKindInfo, deferUpdates: paused, paused, pausedTime, pendingRestart: Math.random() < 0.5, //to generate randomly True/False value state, stateMessage: - state === WorkspaceState.WorkspaceStateRunning + state === WorkspacesWorkspaceState.WorkspaceStateRunning ? 'Workspace is running smoothly.' - : state === WorkspaceState.WorkspaceStatePaused + : state === WorkspacesWorkspaceState.WorkspaceStatePaused ? 'Workspace is paused.' : 'Workspace is operational.', podTemplate: { @@ -104,11 +104,11 @@ const generateMockWorkspaces = (numWorkspaces: number, byNamespace = false) => { for (let i = 1; i <= numWorkspaces; i++) { const state = i % 3 === 0 - ? WorkspaceState.WorkspaceStateError + ? WorkspacesWorkspaceState.WorkspaceStateError : i % 2 === 0 - ? WorkspaceState.WorkspaceStatePaused - : WorkspaceState.WorkspaceStateRunning; - const paused = state === WorkspaceState.WorkspaceStatePaused; + ? WorkspacesWorkspaceState.WorkspaceStatePaused + : WorkspacesWorkspaceState.WorkspaceStateRunning; + const paused = state === WorkspacesWorkspaceState.WorkspaceStatePaused; const name = `workspace-${i}`; const namespace = namespaces[i % namespaces.length]; const pvcName = `data-pvc-${i}`; diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaceKinds.mock.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaceKinds.mock.ts index 4d0a4861..d1dfa27e 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaceKinds.mock.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaceKinds.mock.ts @@ -1,7 +1,12 @@ -import type { WorkspaceKind } from '~/shared/api/backendApiTypes'; +import { + WorkspacekindsRedirectMessageLevel, + type WorkspacekindsWorkspaceKind, +} from '~/generated/data-contracts'; // Factory function to create a valid WorkspaceKind -function createMockWorkspaceKind(overrides: Partial = {}): WorkspaceKind { +function createMockWorkspaceKind( + overrides: Partial = {}, +): WorkspacekindsWorkspaceKind { return { name: 'jupyter-lab', displayName: 'JupyterLab Notebook', @@ -27,14 +32,15 @@ function createMockWorkspaceKind(overrides: Partial = {}): Worksp values: [ { id: 'jupyterlab_scipy_180', + description: 'JupyterLab with SciPy 1.8.0', displayName: 'jupyter-scipy:v1.8.0', - labels: { pythonVersion: '3.11' }, + labels: [{ key: 'pythonVersion', value: '3.11' }], hidden: true, redirect: { to: 'jupyterlab_scipy_190', message: { text: 'This update will change...', - level: 'Info', + level: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelInfo, }, }, }, @@ -45,9 +51,13 @@ function createMockWorkspaceKind(overrides: Partial = {}): Worksp values: [ { id: 'tiny_cpu', + hidden: false, displayName: 'Tiny CPU', description: 'Pod with 0.1 CPU, 128 Mb RAM', - labels: { cpu: '100m', memory: '128Mi' }, + labels: [ + { key: 'cpu', value: '100m' }, + { key: 'memory', value: '128Mi' }, + ], }, ], }, diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/Workspaces.cy.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/Workspaces.cy.ts index 35a29d3e..a593f4f6 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/Workspaces.cy.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/Workspaces.cy.ts @@ -1,14 +1,14 @@ -import type { Workspace } from '~/shared/api/backendApiTypes'; +import { mockNamespaces } from '~/__mocks__/mockNamespaces'; +import { mockBFFResponse } from '~/__mocks__/utils'; import { home } from '~/__tests__/cypress/cypress/pages/home'; import { mockWorkspaces, mockWorkspacesByNS, } from '~/__tests__/cypress/cypress/tests/mocked/workspace.mock'; -import { mockNamespaces } from '~/__mocks__/mockNamespaces'; -import { mockBFFResponse } from '~/__mocks__/utils'; +import type { WorkspacesWorkspace } from '~/generated/data-contracts'; // Helper function to validate the content of a single workspace row in the table -const validateWorkspaceRow = (workspace: Workspace, index: number) => { +const validateWorkspaceRow = (workspace: WorkspacesWorkspace, index: number) => { // Validate the workspace name cy.findByTestId(`workspace-row-${index}`) .find('[data-testid="workspace-name"]') diff --git a/workspaces/frontend/src/app/actions/WorkspaceKindsActions.tsx b/workspaces/frontend/src/app/actions/WorkspaceKindsActions.tsx index 40608d81..068f19d7 100644 --- a/workspaces/frontend/src/app/actions/WorkspaceKindsActions.tsx +++ b/workspaces/frontend/src/app/actions/WorkspaceKindsActions.tsx @@ -1,4 +1,7 @@ -import { WorkspaceKind, WorkspaceOptionRedirect } from '~/shared/api/backendApiTypes'; +import { + WorkspacekindsOptionRedirect, + WorkspacekindsWorkspaceKind, +} from '~/generated/data-contracts'; type KindLogoDict = Record; @@ -7,7 +10,9 @@ type KindLogoDict = Record; * @param {WorkspaceKind[]} workspaceKinds - The list of workspace kinds. * @returns {KindLogoDict} A dictionary with kind names as keys and logo URLs as values. */ -export function buildKindLogoDictionary(workspaceKinds: WorkspaceKind[] | []): KindLogoDict { +export function buildKindLogoDictionary( + workspaceKinds: WorkspacekindsWorkspaceKind[] | [], +): KindLogoDict { const kindLogoDict: KindLogoDict = {}; for (const workspaceKind of workspaceKinds) { @@ -20,7 +25,7 @@ export function buildKindLogoDictionary(workspaceKinds: WorkspaceKind[] | []): K return kindLogoDict; } -type WorkspaceRedirectStatus = Record; +type WorkspaceRedirectStatus = Record; /** * Builds a dictionary of workspace kinds to redirect statuses. @@ -28,7 +33,7 @@ type WorkspaceRedirectStatus = Record = ({ title, errors }) => { @@ -18,7 +17,9 @@ export const ValidationErrorAlert: React.FC = ({ titl {errors.map((error, index) => ( - {error.message} + + {error.message}: '{error.field}' + ))} diff --git a/workspaces/frontend/src/app/components/WorkspaceTable.tsx b/workspaces/frontend/src/app/components/WorkspaceTable.tsx index 44c86006..7c813f05 100644 --- a/workspaces/frontend/src/app/components/WorkspaceTable.tsx +++ b/workspaces/frontend/src/app/components/WorkspaceTable.tsx @@ -44,7 +44,6 @@ import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/ import { TimesCircleIcon } from '@patternfly/react-icons/dist/esm/icons/times-circle-icon'; import { QuestionCircleIcon } from '@patternfly/react-icons/dist/esm/icons/question-circle-icon'; import { formatDistanceToNow } from 'date-fns/formatDistanceToNow'; -import { Workspace, WorkspaceState } from '~/shared/api/backendApiTypes'; import { DataFieldKey, defineDataFields, SortableDataFieldKey } from '~/app/filterableDataHelper'; import { useTypedNavigate } from '~/app/routerHelper'; import { @@ -62,6 +61,7 @@ import { } from '~/shared/utilities/WorkspaceUtils'; import { ExpandedWorkspaceRow } from '~/app/pages/Workspaces/ExpandedWorkspaceRow'; import CustomEmptyState from '~/shared/components/CustomEmptyState'; +import { WorkspacesWorkspace, WorkspacesWorkspaceState } from '~/generated/data-contracts'; const { fields: wsTableColumns, @@ -84,11 +84,11 @@ export type WorkspaceTableColumnKeys = DataFieldKey; type WorkspaceTableSortableColumnKeys = SortableDataFieldKey; interface WorkspaceTableProps { - workspaces: Workspace[]; + workspaces: WorkspacesWorkspace[]; canCreateWorkspaces?: boolean; canExpandRows?: boolean; hiddenColumns?: WorkspaceTableColumnKeys[]; - rowActions?: (workspace: Workspace) => IActions; + rowActions?: (workspace: WorkspacesWorkspace) => IActions; } const allFiltersConfig = { @@ -233,7 +233,7 @@ const WorkspaceTable = React.forwardRef( [clearAllFilters], ); - const filterableProperties: Record string> = useMemo( + const filterableProperties: Record string> = useMemo( () => ({ name: (ws) => ws.name, kind: (ws) => ws.workspaceKind.name, @@ -245,7 +245,7 @@ const WorkspaceTable = React.forwardRef( [], ); - const setWorkspaceExpanded = (workspace: Workspace, isExpanding = true) => + const setWorkspaceExpanded = (workspace: WorkspacesWorkspace, isExpanding = true) => setExpandedWorkspacesNames((prevExpanded) => { const newExpandedWorkspacesNames = prevExpanded.filter( (wsName) => wsName !== workspace.name, @@ -255,7 +255,7 @@ const WorkspaceTable = React.forwardRef( : newExpandedWorkspacesNames; }); - const isWorkspaceExpanded = (workspace: Workspace) => + const isWorkspaceExpanded = (workspace: WorkspacesWorkspace) => expandedWorkspacesNames.includes(workspace.name); const filteredWorkspaces = useMemo(() => { @@ -289,7 +289,7 @@ const WorkspaceTable = React.forwardRef( // Column sorting const getSortableRowValues = ( - workspace: Workspace, + workspace: WorkspacesWorkspace, ): Record => ({ name: workspace.name, kind: workspace.workspaceKind.name, @@ -374,19 +374,19 @@ const WorkspaceTable = React.forwardRef( } }; - const extractStateColor = (state: WorkspaceState) => { + const extractStateColor = (state: WorkspacesWorkspaceState) => { switch (state) { - case WorkspaceState.WorkspaceStateRunning: + case WorkspacesWorkspaceState.WorkspaceStateRunning: return 'green'; - case WorkspaceState.WorkspaceStatePending: + case WorkspacesWorkspaceState.WorkspaceStatePending: return 'orange'; - case WorkspaceState.WorkspaceStateTerminating: + case WorkspacesWorkspaceState.WorkspaceStateTerminating: return 'yellow'; - case WorkspaceState.WorkspaceStateError: + case WorkspacesWorkspaceState.WorkspaceStateError: return 'red'; - case WorkspaceState.WorkspaceStatePaused: + case WorkspacesWorkspaceState.WorkspaceStatePaused: return 'purple'; - case WorkspaceState.WorkspaceStateUnknown: + case WorkspacesWorkspaceState.WorkspaceStateUnknown: default: return 'grey'; } diff --git a/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx b/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx index 57349d35..75a2a7ee 100644 --- a/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx +++ b/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx @@ -8,11 +8,11 @@ import { useNamespaceContext } from '~/app/context/NamespaceContextProvider'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { WorkspaceDetails } from '~/app/pages/Workspaces/Details/WorkspaceDetails'; import { useTypedNavigate } from '~/app/routerHelper'; -import { Workspace } from '~/shared/api/backendApiTypes'; import DeleteModal from '~/shared/components/DeleteModal'; import { WorkspaceStartActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal'; import { WorkspaceRestartActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRestartActionModal'; import { WorkspaceStopActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal'; +import { WorkspacesWorkspace } from '~/generated/data-contracts'; export enum ActionType { ViewDetails = 'ViewDetails', @@ -25,7 +25,7 @@ export enum ActionType { export interface WorkspaceAction { action: ActionType; - workspace: Workspace; + workspace: WorkspacesWorkspace; onActionDone?: () => void; } @@ -85,7 +85,8 @@ export const WorkspaceActionsContextProvider: React.FC (args: { workspace: Workspace; onActionDone?: () => void }) => + (actionType: ActionType) => + (args: { workspace: WorkspacesWorkspace; onActionDone?: () => void }) => setActiveWsAction({ action: actionType, ...args }); const requestViewDetailsAction = createActionRequester(ActionType.ViewDetails); @@ -113,7 +114,7 @@ export const WorkspaceActionsContextProvider: React.FC - api.pauseWorkspace({}, selectedNamespace, activeWsAction.workspace.name, { - data: { paused: false }, - }) + api.workspaces.updateWorkspacePauseState( + selectedNamespace, + activeWsAction.workspace.name, + { + data: { paused: false }, + }, + ) } onActionDone={activeWsAction.onActionDone} onUpdateAndStart={async () => { @@ -202,9 +207,13 @@ export const WorkspaceActionsContextProvider: React.FC - api.pauseWorkspace({}, selectedNamespace, activeWsAction.workspace.name, { - data: { paused: true }, - }) + api.workspaces.updateWorkspacePauseState( + selectedNamespace, + activeWsAction.workspace.name, + { + data: { paused: true }, + }, + ) } onActionDone={activeWsAction.onActionDone} onUpdateAndStop={async () => { diff --git a/workspaces/frontend/src/app/context/useNotebookAPIState.tsx b/workspaces/frontend/src/app/context/useNotebookAPIState.tsx index 6b2e512e..0fc313ac 100644 --- a/workspaces/frontend/src/app/context/useNotebookAPIState.tsx +++ b/workspaces/frontend/src/app/context/useNotebookAPIState.tsx @@ -1,103 +1,18 @@ import { useCallback } from 'react'; -import { NotebookAPIs } from '~/shared/api/notebookApi'; -import { - createWorkspace, - createWorkspaceKind, - deleteWorkspace, - deleteWorkspaceKind, - getHealthCheck, - getWorkspace, - getWorkspaceKind, - listAllWorkspaces, - listNamespaces, - listWorkspaceKinds, - listWorkspaces, - patchWorkspace, - patchWorkspaceKind, - pauseWorkspace, - updateWorkspace, - updateWorkspaceKind, -} from '~/shared/api/notebookService'; +import { NotebookApis, notebookApisImpl } from '~/shared/api/notebookApi'; import { APIState } from '~/shared/api/types'; import useAPIState from '~/shared/api/useAPIState'; -import { - mockCreateWorkspace, - mockCreateWorkspaceKind, - mockDeleteWorkspace, - mockDeleteWorkspaceKind, - mockGetHealthCheck, - mockGetWorkspace, - mockGetWorkspaceKind, - mockListAllWorkspaces, - mockListNamespaces, - mockListWorkspaceKinds, - mockListWorkspaces, - mockPatchWorkspace, - mockPatchWorkspaceKind, - mockPauseWorkspace, - mockUpdateWorkspace, - mockUpdateWorkspaceKind, -} from '~/shared/mock/mockNotebookService'; +import { mockNotebookApisImpl } from '~/shared/mock/mockNotebookApis'; -export type NotebookAPIState = APIState; +export type NotebookAPIState = APIState; const MOCK_API_ENABLED = process.env.WEBPACK_REPLACE__mockApiEnabled === 'true'; const useNotebookAPIState = ( hostPath: string | null, ): [apiState: NotebookAPIState, refreshAPIState: () => void] => { - const createApi = useCallback( - (path: string): NotebookAPIs => ({ - // Health - getHealthCheck: getHealthCheck(path), - // Namespace - listNamespaces: listNamespaces(path), - // Workspace - listAllWorkspaces: listAllWorkspaces(path), - listWorkspaces: listWorkspaces(path), - createWorkspace: createWorkspace(path), - getWorkspace: getWorkspace(path), - updateWorkspace: updateWorkspace(path), - patchWorkspace: patchWorkspace(path), - deleteWorkspace: deleteWorkspace(path), - pauseWorkspace: pauseWorkspace(path), - // WorkspaceKind - listWorkspaceKinds: listWorkspaceKinds(path), - createWorkspaceKind: createWorkspaceKind(path), - getWorkspaceKind: getWorkspaceKind(path), - patchWorkspaceKind: patchWorkspaceKind(path), - deleteWorkspaceKind: deleteWorkspaceKind(path), - updateWorkspaceKind: updateWorkspaceKind(path), - }), - [], - ); - - const createMockApi = useCallback( - (path: string): NotebookAPIs => ({ - // Health - getHealthCheck: mockGetHealthCheck(path), - // Namespace - listNamespaces: mockListNamespaces(path), - // Workspace - listAllWorkspaces: mockListAllWorkspaces(path), - listWorkspaces: mockListWorkspaces(path), - createWorkspace: mockCreateWorkspace(path), - getWorkspace: mockGetWorkspace(path), - updateWorkspace: mockUpdateWorkspace(path), - patchWorkspace: mockPatchWorkspace(path), - deleteWorkspace: mockDeleteWorkspace(path), - pauseWorkspace: mockPauseWorkspace(path), - // WorkspaceKind - listWorkspaceKinds: mockListWorkspaceKinds(path), - createWorkspaceKind: mockCreateWorkspaceKind(path), - getWorkspaceKind: mockGetWorkspaceKind(path), - patchWorkspaceKind: mockPatchWorkspaceKind(path), - deleteWorkspaceKind: mockDeleteWorkspaceKind(path), - updateWorkspaceKind: mockUpdateWorkspaceKind(path), - }), - [], - ); - + const createApi = useCallback((path: string) => notebookApisImpl(path), []); + const createMockApi = useCallback(() => mockNotebookApisImpl(), []); return useAPIState(hostPath, MOCK_API_ENABLED ? createMockApi : createApi); }; diff --git a/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceCountPerKind.spec.tsx b/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceCountPerKind.spec.tsx index ff740970..4e0ec931 100644 --- a/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceCountPerKind.spec.tsx +++ b/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceCountPerKind.spec.tsx @@ -3,13 +3,13 @@ import { renderHook } from '~/__tests__/unit/testUtils/hooks'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { useWorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind'; import { - Workspace, - WorkspaceImageConfigValue, - WorkspaceKind, - WorkspaceKindInfo, - WorkspacePodConfigValue, -} from '~/shared/api/backendApiTypes'; -import { NotebookAPIs } from '~/shared/api/notebookApi'; + WorkspacekindsImageConfigValue, + WorkspacekindsPodConfigValue, + WorkspacekindsWorkspaceKind, + WorkspacesWorkspace, + WorkspacesWorkspaceKindInfo, +} from '~/generated/data-contracts'; +import { NotebookApis } from '~/shared/api/notebookApi'; import { buildMockWorkspace, buildMockWorkspaceKind } from '~/shared/mock/mockBuilder'; jest.mock('~/app/hooks/useNotebookAPI', () => ({ @@ -18,7 +18,7 @@ jest.mock('~/app/hooks/useNotebookAPI', () => ({ const mockUseNotebookAPI = useNotebookAPI as jest.MockedFunction; -const baseWorkspaceKindInfoTest: WorkspaceKindInfo = { +const baseWorkspaceKindInfoTest: WorkspacesWorkspaceKindInfo = { name: 'jupyter', missing: false, icon: { url: '' }, @@ -31,7 +31,7 @@ const baseWorkspaceTest = buildMockWorkspace({ workspaceKind: baseWorkspaceKindInfoTest, }); -const baseImageConfigTest: WorkspaceImageConfigValue = { +const baseImageConfigTest: WorkspacekindsImageConfigValue = { id: 'image', displayName: 'Image', description: 'Test image', @@ -40,7 +40,7 @@ const baseImageConfigTest: WorkspaceImageConfigValue = { clusterMetrics: undefined, }; -const basePodConfigTest: WorkspacePodConfigValue = { +const basePodConfigTest: WorkspacekindsPodConfigValue = { id: 'podConfig', displayName: 'Pod Config', description: 'Test pod config', @@ -53,23 +53,34 @@ describe('useWorkspaceCountPerKind', () => { const mockListAllWorkspaces = jest.fn(); const mockListWorkspaceKinds = jest.fn(); - const mockApi: Partial = { - listAllWorkspaces: mockListAllWorkspaces, - listWorkspaceKinds: mockListWorkspaceKinds, + const mockApi: Partial = { + workspaces: { + listAllWorkspaces: mockListAllWorkspaces, + listWorkspacesByNamespace: jest.fn(), + createWorkspace: jest.fn(), + updateWorkspacePauseState: jest.fn(), + getWorkspace: jest.fn(), + deleteWorkspace: jest.fn(), + }, + workspaceKinds: { + listWorkspaceKinds: mockListWorkspaceKinds, + createWorkspaceKind: jest.fn(), + getWorkspaceKind: jest.fn(), + }, }; beforeEach(() => { jest.clearAllMocks(); mockUseNotebookAPI.mockReturnValue({ - api: mockApi as NotebookAPIs, + api: mockApi as NotebookApis, apiAvailable: true, refreshAllAPI: jest.fn(), }); }); it('should return empty object initially', () => { - mockListAllWorkspaces.mockResolvedValue([]); - mockListWorkspaceKinds.mockResolvedValue([]); + mockListAllWorkspaces.mockResolvedValue({ ok: true, data: [] }); + mockListWorkspaceKinds.mockResolvedValue({ ok: true, data: [] }); const { result } = renderHook(() => useWorkspaceCountPerKind()); @@ -79,7 +90,7 @@ describe('useWorkspaceCountPerKind', () => { }); it('should fetch and calculate workspace counts on mount', async () => { - const mockWorkspaces: Workspace[] = [ + const mockWorkspaces: WorkspacesWorkspace[] = [ { ...baseWorkspaceTest, name: 'workspace1', @@ -100,7 +111,7 @@ describe('useWorkspaceCountPerKind', () => { }, ]; - const mockWorkspaceKinds: WorkspaceKind[] = [ + const mockWorkspaceKinds: WorkspacekindsWorkspaceKind[] = [ buildMockWorkspaceKind({ name: 'jupyter1', clusterMetrics: { workspacesCount: 10 }, @@ -173,8 +184,8 @@ describe('useWorkspaceCountPerKind', () => { }), ]; - mockListAllWorkspaces.mockResolvedValue(mockWorkspaces); - mockListWorkspaceKinds.mockResolvedValue(mockWorkspaceKinds); + mockListAllWorkspaces.mockResolvedValue({ ok: true, data: mockWorkspaces }); + mockListWorkspaceKinds.mockResolvedValue({ ok: true, data: mockWorkspaceKinds }); const { result } = renderHook(() => useWorkspaceCountPerKind()); @@ -211,8 +222,8 @@ describe('useWorkspaceCountPerKind', () => { }); it('should handle missing cluster metrics gracefully', async () => { - const mockEmptyWorkspaces: Workspace[] = []; - const mockWorkspaceKinds: WorkspaceKind[] = [ + const mockEmptyWorkspaces: WorkspacesWorkspace[] = []; + const mockWorkspaceKinds: WorkspacekindsWorkspaceKind[] = [ buildMockWorkspaceKind({ name: 'no-metrics', clusterMetrics: undefined, @@ -251,8 +262,8 @@ describe('useWorkspaceCountPerKind', () => { }), ]; - mockListAllWorkspaces.mockResolvedValue(mockEmptyWorkspaces); - mockListWorkspaceKinds.mockResolvedValue(mockWorkspaceKinds); + mockListAllWorkspaces.mockResolvedValue({ ok: true, data: mockEmptyWorkspaces }); + mockListWorkspaceKinds.mockResolvedValue({ ok: true, data: mockWorkspaceKinds }); const { result } = renderHook(() => useWorkspaceCountPerKind()); @@ -290,7 +301,8 @@ describe('useWorkspaceCountPerKind', () => { }); it('should handle empty workspace kinds array', async () => { - mockListWorkspaceKinds.mockResolvedValue([]); + mockListAllWorkspaces.mockResolvedValue({ ok: true, data: [] }); + mockListWorkspaceKinds.mockResolvedValue({ ok: true, data: [] }); const { result } = renderHook(() => useWorkspaceCountPerKind()); @@ -300,7 +312,7 @@ describe('useWorkspaceCountPerKind', () => { }); it('should handle workspaces with no matching kinds', async () => { - const mockWorkspaces: Workspace[] = [baseWorkspaceTest]; + const mockWorkspaces: WorkspacesWorkspace[] = [baseWorkspaceTest]; const workspaceKind = buildMockWorkspaceKind({ name: 'nomatch', clusterMetrics: { workspacesCount: 0 }, @@ -320,10 +332,10 @@ describe('useWorkspaceCountPerKind', () => { }, }); - const mockWorkspaceKinds: WorkspaceKind[] = [workspaceKind]; + const mockWorkspaceKinds: WorkspacekindsWorkspaceKind[] = [workspaceKind]; - mockListAllWorkspaces.mockResolvedValue(mockWorkspaces); - mockListWorkspaceKinds.mockResolvedValue(mockWorkspaceKinds); + mockListAllWorkspaces.mockResolvedValue({ ok: true, data: mockWorkspaces }); + mockListWorkspaceKinds.mockResolvedValue({ ok: true, data: mockWorkspaceKinds }); const { result } = renderHook(() => useWorkspaceCountPerKind()); diff --git a/workspaces/frontend/src/app/hooks/useNamespaces.ts b/workspaces/frontend/src/app/hooks/useNamespaces.ts index 1f62afeb..0e160713 100644 --- a/workspaces/frontend/src/app/hooks/useNamespaces.ts +++ b/workspaces/frontend/src/app/hooks/useNamespaces.ts @@ -1,24 +1,24 @@ import { useCallback } from 'react'; +import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; +import { ApiNamespaceListEnvelope } from '~/generated/data-contracts'; import useFetchState, { FetchState, FetchStateCallbackPromise, } from '~/shared/utilities/useFetchState'; -import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; -import { Namespace } from '~/shared/api/backendApiTypes'; -const useNamespaces = (): FetchState => { +const useNamespaces = (): FetchState => { const { api, apiAvailable } = useNotebookAPI(); - const call = useCallback>( - (opts) => { - if (!apiAvailable) { - return Promise.reject(new Error('API not yet available')); - } + const call = useCallback< + FetchStateCallbackPromise + >(async () => { + if (!apiAvailable) { + throw new Error('API not yet available'); + } - return api.listNamespaces(opts); - }, - [api, apiAvailable], - ); + const envelope = await api.namespaces.listNamespaces(); + return envelope.data; + }, [api, apiAvailable]); return useFetchState(call, null); }; diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts b/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts index 3a5f766a..22c06d33 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaceCountPerKind.ts @@ -1,10 +1,13 @@ import { useEffect, useState } from 'react'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; -import { Workspace, WorkspaceKind } from '~/shared/api/backendApiTypes'; import { WorkspaceCountPerOption } from '~/app/types'; -import { NotebookAPIs } from '~/shared/api/notebookApi'; +import { WorkspacekindsWorkspaceKind, WorkspacesWorkspace } from '~/generated/data-contracts'; +import { NotebookApis } from '~/shared/api/notebookApi'; -export type WorkspaceCountPerKind = Record; +export type WorkspaceCountPerKind = Record< + WorkspacekindsWorkspaceKind['name'], + WorkspaceCountPerOption +>; export const useWorkspaceCountPerKind = (): WorkspaceCountPerKind => { const { api } = useNotebookAPI(); @@ -27,18 +30,18 @@ export const useWorkspaceCountPerKind = (): WorkspaceCountPerKind => { return workspaceCountPerKind; }; -async function loadWorkspaceCounts(api: NotebookAPIs): Promise { +async function loadWorkspaceCounts(api: NotebookApis): Promise { const [workspaces, workspaceKinds] = await Promise.all([ - api.listAllWorkspaces({}), - api.listWorkspaceKinds({}), + api.workspaces.listAllWorkspaces({}), + api.workspaceKinds.listWorkspaceKinds({}), ]); - return extractCountPerKind({ workspaceKinds, workspaces }); + return extractCountPerKind({ workspaceKinds: workspaceKinds.data, workspaces: workspaces.data }); } function extractCountByNamespace(args: { - kind: WorkspaceKind; - workspaces: Workspace[]; + kind: WorkspacekindsWorkspaceKind; + workspaces: WorkspacesWorkspace[]; }): WorkspaceCountPerOption['countByNamespace'] { const { kind, workspaces } = args; return workspaces.reduce( @@ -53,7 +56,7 @@ function extractCountByNamespace(args: { } function extractCountByImage( - workspaceKind: WorkspaceKind, + workspaceKind: WorkspacekindsWorkspaceKind, ): WorkspaceCountPerOption['countByImage'] { return workspaceKind.podTemplate.options.imageConfig.values.reduce< WorkspaceCountPerOption['countByImage'] @@ -64,7 +67,7 @@ function extractCountByImage( } function extractCountByPodConfig( - workspaceKind: WorkspaceKind, + workspaceKind: WorkspacekindsWorkspaceKind, ): WorkspaceCountPerOption['countByPodConfig'] { return workspaceKind.podTemplate.options.podConfig.values.reduce< WorkspaceCountPerOption['countByPodConfig'] @@ -74,13 +77,13 @@ function extractCountByPodConfig( }, {}); } -function extractTotalCount(workspaceKind: WorkspaceKind): number { +function extractTotalCount(workspaceKind: WorkspacekindsWorkspaceKind): number { return workspaceKind.clusterMetrics?.workspacesCount ?? 0; } function extractCountPerKind(args: { - workspaceKinds: WorkspaceKind[]; - workspaces: Workspace[]; + workspaceKinds: WorkspacekindsWorkspaceKind[]; + workspaces: WorkspacesWorkspace[]; }): WorkspaceCountPerKind { const { workspaceKinds, workspaces } = args; diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts b/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts index 4a741b7a..88b9acc6 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts @@ -26,48 +26,49 @@ const useWorkspaceFormData = (args: { const { namespace, workspaceName } = args; const { api, apiAvailable } = useNotebookAPI(); - const call = useCallback>( - async (opts) => { - if (!apiAvailable) { - throw new Error('API not yet available'); - } + const call = useCallback>(async () => { + if (!apiAvailable) { + throw new Error('API not yet available'); + } - if (!namespace || !workspaceName) { - return EMPTY_FORM_DATA; - } + if (!namespace || !workspaceName) { + return EMPTY_FORM_DATA; + } - const workspace = await api.getWorkspace(opts, namespace, workspaceName); - const workspaceKind = await api.getWorkspaceKind(opts, workspace.workspaceKind.name); - const imageConfig = workspace.podTemplate.options.imageConfig.current; - const podConfig = workspace.podTemplate.options.podConfig.current; + const workspaceEnvelope = await api.workspaces.getWorkspace(namespace, workspaceName); + const workspace = workspaceEnvelope.data; + const workspaceKindEnvelope = await api.workspaceKinds.getWorkspaceKind( + workspace.workspaceKind.name, + ); + const workspaceKind = workspaceKindEnvelope.data; + const imageConfig = workspace.podTemplate.options.imageConfig.current; + const podConfig = workspace.podTemplate.options.podConfig.current; - return { - kind: workspaceKind, - image: { - id: imageConfig.id, - displayName: imageConfig.displayName, - description: imageConfig.description, - hidden: false, - labels: [], - }, - podConfig: { - id: podConfig.id, - displayName: podConfig.displayName, - description: podConfig.description, - hidden: false, - labels: [], - }, - properties: { - workspaceName: workspace.name, - deferUpdates: workspace.deferUpdates, - volumes: workspace.podTemplate.volumes.data.map((volume) => ({ ...volume })), - secrets: workspace.podTemplate.volumes.secrets?.map((secret) => ({ ...secret })) ?? [], - homeDirectory: workspace.podTemplate.volumes.home?.mountPath ?? '', - }, - }; - }, - [api, apiAvailable, namespace, workspaceName], - ); + return { + kind: workspaceKind, + image: { + id: imageConfig.id, + displayName: imageConfig.displayName, + description: imageConfig.description, + hidden: false, + labels: [], + }, + podConfig: { + id: podConfig.id, + displayName: podConfig.displayName, + description: podConfig.description, + hidden: false, + labels: [], + }, + properties: { + workspaceName: workspace.name, + deferUpdates: workspace.deferUpdates, + volumes: workspace.podTemplate.volumes.data.map((volume) => ({ ...volume })), + secrets: workspace.podTemplate.volumes.secrets?.map((secret) => ({ ...secret })) ?? [], + homeDirectory: workspace.podTemplate.volumes.home?.mountPath ?? '', + }, + }; + }, [api, apiAvailable, namespace, workspaceName]); return useFetchState(call, EMPTY_FORM_DATA); }; diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceKindByName.ts b/workspaces/frontend/src/app/hooks/useWorkspaceKindByName.ts index 1c575b24..b5ebac44 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaceKindByName.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaceKindByName.ts @@ -4,21 +4,27 @@ import useFetchState, { FetchStateCallbackPromise, } from '~/shared/utilities/useFetchState'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; -import { WorkspaceKind } from '~/shared/api/backendApiTypes'; +import { ApiWorkspaceKindEnvelope } from '~/generated/data-contracts'; -const useWorkspaceKindByName = (kind: string): FetchState => { +const useWorkspaceKindByName = ( + kind: string | undefined, +): FetchState => { const { api, apiAvailable } = useNotebookAPI(); - const call = useCallback>( - (opts) => { - if (!apiAvailable) { - return Promise.reject(new Error('API not yet available')); - } + const call = useCallback< + FetchStateCallbackPromise + >(async () => { + if (!apiAvailable) { + return Promise.reject(new Error('API not yet available')); + } - return api.getWorkspaceKind(opts, kind); - }, - [api, apiAvailable, kind], - ); + if (!kind) { + return null; + } + + const envelope = await api.workspaceKinds.getWorkspaceKind(kind); + return envelope.data; + }, [api, apiAvailable, kind]); return useFetchState(call, null); }; diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceKinds.ts b/workspaces/frontend/src/app/hooks/useWorkspaceKinds.ts index d654bd92..99d64e9b 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaceKinds.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaceKinds.ts @@ -3,20 +3,23 @@ import useFetchState, { FetchState, FetchStateCallbackPromise, } from '~/shared/utilities/useFetchState'; -import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; +import { + ApiWorkspaceKindListEnvelope, + WorkspacekindsWorkspaceKind, +} from '~/generated/data-contracts'; -const useWorkspaceKinds = (): FetchState => { +const useWorkspaceKinds = (): FetchState => { const { api, apiAvailable } = useNotebookAPI(); - const call = useCallback>( - (opts) => { - if (!apiAvailable) { - return Promise.reject(new Error('API not yet available')); - } - return api.listWorkspaceKinds(opts); - }, - [api, apiAvailable], - ); + const call = useCallback< + FetchStateCallbackPromise + >(async () => { + if (!apiAvailable) { + return Promise.reject(new Error('API not yet available')); + } + const envelope = await api.workspaceKinds.listWorkspaceKinds(); + return envelope.data; + }, [api, apiAvailable]); return useFetchState(call, []); }; diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceRowActions.ts b/workspaces/frontend/src/app/hooks/useWorkspaceRowActions.ts index 4787107e..1b58f774 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaceRowActions.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaceRowActions.ts @@ -1,25 +1,25 @@ import { useCallback } from 'react'; import { IActions } from '@patternfly/react-table/dist/esm/components/Table'; -import { Workspace } from '~/shared/api/backendApiTypes'; import { useWorkspaceActionsContext, WorkspaceAction } from '~/app/context/WorkspaceActionsContext'; +import { WorkspacesWorkspace } from '~/generated/data-contracts'; export type WorkspaceRowActionId = 'viewDetails' | 'edit' | 'delete' | 'start' | 'stop' | 'restart'; interface WorkspaceRowAction { id: WorkspaceRowActionId; onActionDone?: WorkspaceAction['onActionDone']; - isVisible?: boolean | ((workspace: Workspace) => boolean); + isVisible?: boolean | ((workspace: WorkspacesWorkspace) => boolean); } type WorkspaceRowActionItem = WorkspaceRowAction | { id: 'separator' }; export const useWorkspaceRowActions = ( actionsToInclude: WorkspaceRowActionItem[], -): ((workspace: Workspace) => IActions) => { +): ((workspace: WorkspacesWorkspace) => IActions) => { const actionsContext = useWorkspaceActionsContext(); return useCallback( - (workspace: Workspace): IActions => { + (workspace: WorkspacesWorkspace): IActions => { const actions: IActions = []; for (const item of actionsToInclude) { @@ -47,7 +47,7 @@ export const useWorkspaceRowActions = ( function buildAction( id: WorkspaceRowActionId, onActionDone: WorkspaceAction['onActionDone'] | undefined, - workspace: Workspace, + workspace: WorkspacesWorkspace, actionsContext: ReturnType, ): IActions[number] { const map: Record IActions[number]> = { diff --git a/workspaces/frontend/src/app/hooks/useWorkspaces.ts b/workspaces/frontend/src/app/hooks/useWorkspaces.ts index 998f586c..5ce9d320 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaces.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaces.ts @@ -4,21 +4,23 @@ import useFetchState, { FetchStateCallbackPromise, } from '~/shared/utilities/useFetchState'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; -import { Workspace } from '~/shared/api/backendApiTypes'; +import { ApiWorkspaceListEnvelope } from '~/generated/data-contracts'; -export const useWorkspacesByNamespace = (namespace: string): FetchState => { +export const useWorkspacesByNamespace = ( + namespace: string, +): FetchState => { const { api, apiAvailable } = useNotebookAPI(); - const call = useCallback>( - (opts) => { - if (!apiAvailable) { - return Promise.reject(new Error('API not yet available')); - } + const call = useCallback< + FetchStateCallbackPromise + >(async () => { + if (!apiAvailable) { + return Promise.reject(new Error('API not yet available')); + } - return api.listWorkspaces(opts, namespace); - }, - [api, apiAvailable, namespace], - ); + const envelope = await api.workspaces.listWorkspacesByNamespace(namespace); + return envelope.data; + }, [api, apiAvailable, namespace]); return useFetchState(call, []); }; @@ -28,34 +30,33 @@ export const useWorkspacesByKind = (args: { namespace?: string; imageId?: string; podConfigId?: string; -}): FetchState => { +}): FetchState => { const { kind, namespace, imageId, podConfigId } = args; const { api, apiAvailable } = useNotebookAPI(); - const call = useCallback>( - async (opts) => { - if (!apiAvailable) { - throw new Error('API not yet available'); - } - if (!kind) { - throw new Error('Workspace kind is required'); - } + const call = useCallback< + FetchStateCallbackPromise + >(async () => { + if (!apiAvailable) { + throw new Error('API not yet available'); + } + if (!kind) { + throw new Error('Workspace kind is required'); + } - const workspaces = await api.listAllWorkspaces(opts); + const envelope = await api.workspaces.listAllWorkspaces(); - return workspaces.filter((workspace) => { - const matchesKind = workspace.workspaceKind.name === kind; - const matchesNamespace = namespace ? workspace.namespace === namespace : true; - const matchesImage = imageId - ? workspace.podTemplate.options.imageConfig.current.id === imageId - : true; - const matchesPodConfig = podConfigId - ? workspace.podTemplate.options.podConfig.current.id === podConfigId - : true; + return envelope.data.filter((workspace) => { + const matchesKind = workspace.workspaceKind.name === kind; + const matchesNamespace = namespace ? workspace.namespace === namespace : true; + const matchesImage = imageId + ? workspace.podTemplate.options.imageConfig.current.id === imageId + : true; + const matchesPodConfig = podConfigId + ? workspace.podTemplate.options.podConfig.current.id === podConfigId + : true; - return matchesKind && matchesNamespace && matchesImage && matchesPodConfig; - }); - }, - [apiAvailable, api, kind, namespace, imageId, podConfigId], - ); + return matchesKind && matchesNamespace && matchesImage && matchesPodConfig; + }); + }, [apiAvailable, api, kind, namespace, imageId, podConfigId]); return useFetchState(call, []); }; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/EditableLabels.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/EditableLabels.tsx index a9858f9c..5f52ea78 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/EditableLabels.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/EditableLabels.tsx @@ -11,12 +11,12 @@ import inlineEditStyles from '@patternfly/react-styles/css/components/InlineEdit import { css } from '@patternfly/react-styles'; import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; import { TrashAltIcon } from '@patternfly/react-icons/dist/esm/icons/trash-alt-icon'; -import { WorkspaceOptionLabel } from '~/shared/api/backendApiTypes'; +import { WorkspacekindsOptionLabel } from '~/generated/data-contracts'; interface EditableRowInterface { - data: WorkspaceOptionLabel; - columnNames: ColumnNames; - saveChanges: (editedData: WorkspaceOptionLabel) => void; + data: WorkspacekindsOptionLabel; + columnNames: ColumnNames; + saveChanges: (editedData: WorkspacekindsOptionLabel) => void; ariaLabel: string; deleteRow: () => void; } @@ -70,8 +70,8 @@ const EditableRow: React.FC = ({ type ColumnNames = { [K in keyof T]: string }; interface EditableLabelsProps { - rows: WorkspaceOptionLabel[]; - setRows: (value: WorkspaceOptionLabel[]) => void; + rows: WorkspacekindsOptionLabel[]; + setRows: (value: WorkspacekindsOptionLabel[]) => void; title?: string; description?: string; buttonLabel?: string; @@ -84,7 +84,7 @@ export const EditableLabels: React.FC = ({ description, buttonLabel = 'Label', }) => { - const columnNames: ColumnNames = { + const columnNames: ColumnNames = { key: 'Key', value: 'Value', }; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx index c17a78c2..449b49ce 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx @@ -9,13 +9,14 @@ import { ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/ex import { EmptyState, EmptyStateBody } from '@patternfly/react-core/dist/esm/components/EmptyState'; import { ValidationErrorAlert } from '~/app/components/ValidationErrorAlert'; import useWorkspaceKindByName from '~/app/hooks/useWorkspaceKindByName'; -import { WorkspaceKind, ValidationError } from '~/shared/api/backendApiTypes'; import { useTypedNavigate, useTypedParams } from '~/app/routerHelper'; import { useCurrentRouteKey } from '~/app/hooks/useCurrentRouteKey'; import useGenericObjectState from '~/app/hooks/useGenericObjectState'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { WorkspaceKindFormData } from '~/app/types'; -import { ErrorEnvelopeException } from '~/shared/api/apiUtils'; +import { safeApiCall } from '~/shared/api/apiUtils'; +import { CONTENT_TYPE_KEY, ContentType } from '~/shared/utilities/const'; +import { ApiValidationError, WorkspacekindsWorkspaceKind } from '~/generated/data-contracts'; import { WorkspaceKindFileUpload } from './fileUpload/WorkspaceKindFileUpload'; import { WorkspaceKindFormProperties } from './properties/WorkspaceKindFormProperties'; import { WorkspaceKindFormImage } from './image/WorkspaceKindFormImage'; @@ -31,7 +32,7 @@ export enum WorkspaceKindFormView { export type ValidationStatus = 'success' | 'error' | 'default'; export type FormMode = 'edit' | 'create'; -const convertToFormData = (initialData: WorkspaceKind): WorkspaceKindFormData => { +const convertToFormData = (initialData: WorkspacekindsWorkspaceKind): WorkspaceKindFormData => { const { podTemplate, ...properties } = initialData; const { options, ...spec } = podTemplate; const { podConfig, imageConfig } = options; @@ -51,11 +52,12 @@ export const WorkspaceKindForm: React.FC = () => { const [isSubmitting, setIsSubmitting] = useState(false); const [validated, setValidated] = useState('default'); const mode: FormMode = useCurrentRouteKey() === 'workspaceKindCreate' ? 'create' : 'edit'; - const [specErrors, setSpecErrors] = useState<(ValidationError | ErrorEnvelopeException)[]>([]); + const [specErrors, setSpecErrors] = useState([]); - const { kind } = useTypedParams<'workspaceKindEdit'>(); - const [initialFormData, initialFormDataLoaded, initialFormDataError] = - useWorkspaceKindByName(kind); + const routeParams = useTypedParams<'workspaceKindEdit' | 'workspaceKindCreate'>(); + const [initialFormData, initialFormDataLoaded, initialFormDataError] = useWorkspaceKindByName( + routeParams?.kind, + ); const [data, setData, resetData, replaceData] = useGenericObjectState( initialFormData ? convertToFormData(initialFormData) : EMPTY_WORKSPACE_KIND_FORM_DATA, @@ -73,32 +75,45 @@ export const WorkspaceKindForm: React.FC = () => { // TODO: Complete handleCreate with API call to create a new WS kind try { if (mode === 'create') { - const newWorkspaceKind = await api.createWorkspaceKind({ directYAML: true }, yamlValue); - // TODO: alert user about success - console.info('New workspace kind created:', JSON.stringify(newWorkspaceKind)); - navigate('workspaceKinds'); + const createResult = await safeApiCall(() => + api.workspaceKinds.createWorkspaceKind(yamlValue, { + headers: { + [CONTENT_TYPE_KEY]: ContentType.YAML, + }, + }), + ); + + if (createResult.ok) { + // TODO: alert user about success + console.info('New workspace kind created:', JSON.stringify(createResult.data)); + navigate('workspaceKinds'); + } else { + const validationErrors = createResult.errorEnvelope.error.cause?.validation_errors; + if (validationErrors && validationErrors.length > 0) { + setSpecErrors((prev) => [...prev, ...validationErrors]); + setValidated('error'); + return; + } + // TODO: alert user about generic error with no validation errors + setValidated('error'); + console.error( + `Error while creating workspace kind: ${JSON.stringify(createResult.errorEnvelope)}`, + ); + } } // TODO: Finish when WSKind API is finalized // const updatedWorkspace = await api.updateWorkspaceKind({}, kind, { data: {} }); // console.info('Workspace Kind updated:', JSON.stringify(updatedWorkspace)); // navigate('workspaceKinds'); } catch (err) { - if (err instanceof ErrorEnvelopeException) { - const validationErrors = err.envelope.error?.cause?.validation_errors; - if (validationErrors && validationErrors.length > 0) { - setSpecErrors((prev) => [...prev, ...validationErrors]); - setValidated('error'); - return; - } - setSpecErrors((prev) => [...prev, err]); - setValidated('error'); - } - // TODO: alert user about error - console.error(`Error ${mode === 'edit' ? 'editing' : 'creating'} workspace kind: ${err}`); + // TODO: alert user about unexpected error + console.error( + `Unexpected error while ${mode === 'edit' ? 'editing' : 'creating'} workspace kind: ${err}`, + ); } finally { setIsSubmitting(false); } - }, [navigate, mode, api, yamlValue]); + }, [api, mode, navigate, yamlValue]); const canSubmit = useMemo( () => !isSubmitting && validated === 'success', diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindFormPaginatedTable.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindFormPaginatedTable.tsx index c503c446..4d0ae134 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindFormPaginatedTable.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindFormPaginatedTable.tsx @@ -11,12 +11,11 @@ import { import { Radio } from '@patternfly/react-core/dist/esm/components/Radio'; import { Dropdown, DropdownItem } from '@patternfly/react-core/dist/esm/components/Dropdown'; import { EllipsisVIcon } from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; - import { WorkspaceKindImageConfigValue } from '~/app/types'; -import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes'; +import { WorkspacekindsPodConfigValue } from '~/generated/data-contracts'; interface PaginatedTableProps { - rows: WorkspaceKindImageConfigValue[] | WorkspacePodConfigValue[]; + rows: WorkspaceKindImageConfigValue[] | WorkspacekindsPodConfigValue[]; defaultId: string; setDefaultId: (id: string) => void; handleEdit: (index: number) => void; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/helpers.ts b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/helpers.ts index 786670e7..f768d768 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/helpers.ts +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/helpers.ts @@ -1,5 +1,8 @@ import { ImagePullPolicy, WorkspaceKindImagePort, WorkspaceKindPodConfigValue } from '~/app/types'; -import { WorkspaceOptionLabel, WorkspacePodConfigValue } from '~/shared/api/backendApiTypes'; +import { + WorkspacekindsOptionLabel, + WorkspacekindsPodConfigValue, +} from '~/generated/data-contracts'; import { PodResourceEntry } from './podConfig/WorkspaceKindFormResource'; // Simple ID generator to avoid PatternFly dependency in tests @@ -79,7 +82,7 @@ export const emptyImage = { description: '', hidden: false, imagePullPolicy: ImagePullPolicy.IfNotPresent, - labels: [] as WorkspaceOptionLabel[], + labels: [] as WorkspacekindsOptionLabel[], image: '', ports: [ { @@ -94,7 +97,7 @@ export const emptyImage = { }, }; -export const emptyPodConfig: WorkspacePodConfigValue = { +export const emptyPodConfig: WorkspacekindsPodConfigValue = { id: '', displayName: '', description: '', diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageRedirect.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageRedirect.tsx index f250624f..62cad633 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageRedirect.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/image/WorkspaceKindFormImageRedirect.tsx @@ -10,13 +10,13 @@ import { } from '@patternfly/react-core/dist/esm/components/FormSelect'; import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput'; import { - WorkspaceOptionRedirect, - WorkspaceRedirectMessageLevel, -} from '~/shared/api/backendApiTypes'; + WorkspacekindsOptionRedirect, + WorkspacekindsRedirectMessageLevel, +} from '~/generated/data-contracts'; interface WorkspaceKindFormImageRedirectProps { - redirect: WorkspaceOptionRedirect; - setRedirect: (obj: WorkspaceOptionRedirect) => void; + redirect: WorkspacekindsOptionRedirect; + setRedirect: (obj: WorkspacekindsOptionRedirect) => void; } export const WorkspaceKindFormImageRedirect: React.FC = ({ @@ -26,18 +26,18 @@ export const WorkspaceKindFormImageRedirect: React.FC getResources(currConfig), [currConfig]); const [resources, setResources] = useState(initialResources); - const [labels, setLabels] = useState(currConfig.labels); + const [labels, setLabels] = useState(currConfig.labels); const [id, setId] = useState(currConfig.id); const [displayName, setDisplayName] = useState(currConfig.displayName); const [description, setDescription] = useState(currConfig.description); diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podTemplate/WorkspaceKindFormPodTemplate.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podTemplate/WorkspaceKindFormPodTemplate.tsx index a6c69821..7246a881 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podTemplate/WorkspaceKindFormPodTemplate.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/podTemplate/WorkspaceKindFormPodTemplate.tsx @@ -10,9 +10,9 @@ import { HelperText, HelperTextItem } from '@patternfly/react-core/dist/esm/comp import { Switch } from '@patternfly/react-core/dist/esm/components/Switch'; import { WorkspaceKindPodTemplateData } from '~/app/types'; import { EditableLabels } from '~/app/pages/WorkspaceKinds/Form/EditableLabels'; -import { WorkspacePodVolumeMount } from '~/shared/api/backendApiTypes'; import { ResourceInputWrapper } from '~/app/pages/WorkspaceKinds/Form/podConfig/ResourceInputWrapper'; import { WorkspaceFormPropertiesVolumes } from '~/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes'; +import { WorkspacesPodVolumeMount } from '~/generated/data-contracts'; interface WorkspaceKindFormPodTemplateProps { podTemplate: WorkspaceKindPodTemplateData; @@ -24,7 +24,7 @@ export const WorkspaceKindFormPodTemplate: React.FC { const [isExpanded, setIsExpanded] = useState(false); - const [volumes, setVolumes] = useState([]); + const [volumes, setVolumes] = useState([]); const toggleCullingEnabled = useCallback( (checked: boolean) => { @@ -42,7 +42,7 @@ export const WorkspaceKindFormPodTemplate: React.FC { + (newVolumes: WorkspacesPodVolumeMount[]) => { setVolumes(newVolumes); updatePodTemplate({ ...podTemplate, diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx index ea3bff29..3f212a17 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx @@ -36,7 +36,6 @@ import { IActions, } from '@patternfly/react-table/dist/esm/components/Table'; import { FilterIcon } from '@patternfly/react-icons/dist/esm/icons/filter-icon'; -import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds'; import { useWorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind'; import { WorkspaceKindsColumns } from '~/app/types'; @@ -45,6 +44,7 @@ import CustomEmptyState from '~/shared/components/CustomEmptyState'; import WithValidImage from '~/shared/components/WithValidImage'; import ImageFallback from '~/shared/components/ImageFallback'; import { useTypedNavigate } from '~/app/routerHelper'; +import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts'; import { WorkspaceKindDetails } from './details/WorkspaceKindDetails'; export enum ActionType { @@ -74,7 +74,8 @@ export const WorkspaceKinds: React.FunctionComponent = () => { }, [navigate]); const [workspaceKinds, workspaceKindsLoaded, workspaceKindsError] = useWorkspaceKinds(); const workspaceCountPerKind = useWorkspaceCountPerKind(); - const [selectedWorkspaceKind, setSelectedWorkspaceKind] = useState(null); + const [selectedWorkspaceKind, setSelectedWorkspaceKind] = + useState(null); const [activeActionType, setActiveActionType] = useState(null); // Column sorting @@ -82,7 +83,7 @@ export const WorkspaceKinds: React.FunctionComponent = () => { const [activeSortDirection, setActiveSortDirection] = useState<'asc' | 'desc' | null>(null); const getSortableRowValues = useCallback( - (workspaceKind: WorkspaceKind): (string | boolean | number)[] => { + (workspaceKind: WorkspacekindsWorkspaceKind): (string | boolean | number)[] => { const { icon, name, @@ -151,7 +152,7 @@ export const WorkspaceKinds: React.FunctionComponent = () => { }, []); const onFilter = useCallback( - (workspaceKind: WorkspaceKind) => { + (workspaceKind: WorkspacekindsWorkspaceKind) => { let nameRegex: RegExp; let descriptionRegex: RegExp; @@ -275,13 +276,13 @@ export const WorkspaceKinds: React.FunctionComponent = () => { // Actions - const viewDetailsClick = useCallback((workspaceKind: WorkspaceKind) => { + const viewDetailsClick = useCallback((workspaceKind: WorkspacekindsWorkspaceKind) => { setSelectedWorkspaceKind(workspaceKind); setActiveActionType(ActionType.ViewDetails); }, []); const workspaceKindsDefaultActions = useCallback( - (workspaceKind: WorkspaceKind): IActions => [ + (workspaceKind: WorkspacekindsWorkspaceKind): IActions => [ { id: 'view-details', title: 'View Details', diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx index aaa7a150..23eb9155 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetails.tsx @@ -14,15 +14,15 @@ import { TabContent, } from '@patternfly/react-core/dist/esm/components/Tabs'; import { Title } from '@patternfly/react-core/dist/esm/components/Title'; -import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import { WorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind'; import { WorkspaceKindDetailsNamespaces } from '~/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsNamespaces'; +import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts'; import { WorkspaceKindDetailsOverview } from './WorkspaceKindDetailsOverview'; import { WorkspaceKindDetailsImages } from './WorkspaceKindDetailsImages'; import { WorkspaceKindDetailsPodConfigs } from './WorkspaceKindDetailsPodConfigs'; type WorkspaceKindDetailsProps = { - workspaceKind: WorkspaceKind; + workspaceKind: WorkspacekindsWorkspaceKind; workspaceCountPerKind: WorkspaceCountPerKind; onCloseClick: React.MouseEventHandler; }; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsImages.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsImages.tsx index 6822982a..09e4cab3 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsImages.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsImages.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import { WorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind'; +import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts'; import { WorkspaceKindDetailsTable } from './WorkspaceKindDetailsTable'; type WorkspaceDetailsImagesProps = { - workspaceKind: WorkspaceKind; + workspaceKind: WorkspacekindsWorkspaceKind; workspaceCountPerKind: WorkspaceCountPerKind; }; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsNamespaces.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsNamespaces.tsx index ca6c76fb..336bce1b 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsNamespaces.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsNamespaces.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import { WorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind'; +import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts'; import { WorkspaceKindDetailsTable } from './WorkspaceKindDetailsTable'; type WorkspaceDetailsNamespacesProps = { - workspaceKind: WorkspaceKind; + workspaceKind: WorkspacekindsWorkspaceKind; workspaceCountPerKind: WorkspaceCountPerKind; }; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsOverview.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsOverview.tsx index 7a0b58d8..c5bf8c1f 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsOverview.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsOverview.tsx @@ -6,12 +6,12 @@ import { DescriptionListDescription, } from '@patternfly/react-core/dist/esm/components/DescriptionList'; import { Divider } from '@patternfly/react-core/dist/esm/components/Divider'; -import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import ImageFallback from '~/shared/components/ImageFallback'; import WithValidImage from '~/shared/components/WithValidImage'; +import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts'; type WorkspaceDetailsOverviewProps = { - workspaceKind: WorkspaceKind; + workspaceKind: WorkspacekindsWorkspaceKind; }; export const WorkspaceKindDetailsOverview: React.FunctionComponent< diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsPodConfigs.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsPodConfigs.tsx index 7ca13f4b..0abea53f 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsPodConfigs.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsPodConfigs.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import { WorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind'; +import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts'; import { WorkspaceKindDetailsTable } from './WorkspaceKindDetailsTable'; type WorkspaceDetailsPodConfigsProps = { - workspaceKind: WorkspaceKind; + workspaceKind: WorkspacekindsWorkspaceKind; workspaceCountPerKind: WorkspaceCountPerKind; }; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard.tsx index 6373c059..7c3a82f5 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard.tsx @@ -12,7 +12,6 @@ import { Divider } from '@patternfly/react-core/dist/esm/components/Divider'; import { Flex, FlexItem } from '@patternfly/react-core/dist/esm/layouts/Flex'; import { Stack, StackItem } from '@patternfly/react-core/dist/esm/layouts/Stack'; import { t_global_spacer_md as MediumPadding } from '@patternfly/react-tokens'; -import { Workspace } from '~/shared/api/backendApiTypes'; import { countGpusFromWorkspaces, filterIdleWorkspacesWithGpu, @@ -20,11 +19,12 @@ import { groupWorkspacesByNamespaceAndGpu, YesNoValue, } from '~/shared/utilities/WorkspaceUtils'; +import { WorkspacesWorkspace } from '~/generated/data-contracts'; const TOP_GPU_CONSUMERS_LIMIT = 2; interface WorkspaceKindSummaryExpandableCardProps { - workspaces: Workspace[]; + workspaces: WorkspacesWorkspace[]; isExpanded: boolean; onExpandToggle: () => void; onAddFilter: (columnKey: string, value: string) => void; diff --git a/workspaces/frontend/src/app/pages/Workspaces/DataVolumesList.tsx b/workspaces/frontend/src/app/pages/Workspaces/DataVolumesList.tsx index d509e078..c9f27f69 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/DataVolumesList.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/DataVolumesList.tsx @@ -15,10 +15,10 @@ import { List, ListItem } from '@patternfly/react-core/dist/esm/components/List' import { Tooltip } from '@patternfly/react-core/dist/esm/components/Tooltip'; import { DatabaseIcon } from '@patternfly/react-icons/dist/esm/icons/database-icon'; import { LockedIcon } from '@patternfly/react-icons/dist/esm/icons/locked-icon'; -import { Workspace } from '~/shared/api/backendApiTypes'; +import { WorkspacesWorkspace } from '~/generated/data-contracts'; interface DataVolumesListProps { - workspace: Workspace; + workspace: WorkspacesWorkspace; } export const DataVolumesList: React.FC = ({ workspace }) => { diff --git a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetails.tsx index b5598a2f..9907f5d7 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetails.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetails.tsx @@ -14,14 +14,14 @@ import { TabContent, } from '@patternfly/react-core/dist/esm/components/Tabs'; import { Title } from '@patternfly/react-core/dist/esm/components/Title'; -import { Workspace } from '~/shared/api/backendApiTypes'; import { WorkspaceDetailsOverview } from '~/app/pages/Workspaces/Details/WorkspaceDetailsOverview'; import { WorkspaceDetailsActions } from '~/app/pages/Workspaces/Details/WorkspaceDetailsActions'; import { WorkspaceDetailsActivity } from '~/app/pages/Workspaces/Details/WorkspaceDetailsActivity'; import { WorkspaceDetailsPodTemplate } from '~/app/pages/Workspaces/Details/WorkspaceDetailsPodTemplate'; +import { WorkspacesWorkspace } from '~/generated/data-contracts'; type WorkspaceDetailsProps = { - workspace: Workspace; + workspace: WorkspacesWorkspace; onCloseClick: React.MouseEventHandler; // TODO: Uncomment when edit action is fully supported // onEditClick: React.MouseEventHandler; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsActivity.tsx b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsActivity.tsx index 17a4b411..f47c0729 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsActivity.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsActivity.tsx @@ -7,12 +7,12 @@ import { DescriptionListDescription, } from '@patternfly/react-core/dist/esm/components/DescriptionList'; import { Divider } from '@patternfly/react-core/dist/esm/components/Divider'; -import { Workspace } from '~/shared/api/backendApiTypes'; +import { WorkspacesWorkspace } from '~/generated/data-contracts'; const DATE_FORMAT = 'PPpp'; type WorkspaceDetailsActivityProps = { - workspace: Workspace; + workspace: WorkspacesWorkspace; }; export const WorkspaceDetailsActivity: React.FunctionComponent = ({ diff --git a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsOverview.tsx b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsOverview.tsx index 55f5ae92..af902b20 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsOverview.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Details/WorkspaceDetailsOverview.tsx @@ -6,10 +6,10 @@ import { DescriptionListDescription, } from '@patternfly/react-core/dist/esm/components/DescriptionList'; import { Divider } from '@patternfly/react-core/dist/esm/components/Divider'; -import { Workspace } from '~/shared/api/backendApiTypes'; +import { WorkspacesWorkspace } from '~/generated/data-contracts'; type WorkspaceDetailsOverviewProps = { - workspace: Workspace; + workspace: WorkspacesWorkspace; }; export const WorkspaceDetailsOverview: React.FunctionComponent = ({ diff --git a/workspaces/frontend/src/app/pages/Workspaces/ExpandedWorkspaceRow.tsx b/workspaces/frontend/src/app/pages/Workspaces/ExpandedWorkspaceRow.tsx index 38c1b02a..f8edeb80 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/ExpandedWorkspaceRow.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/ExpandedWorkspaceRow.tsx @@ -1,13 +1,13 @@ import React from 'react'; import { Tr, Td, ExpandableRowContent } from '@patternfly/react-table/dist/esm/components/Table'; -import { Workspace } from '~/shared/api/backendApiTypes'; import { WorkspaceTableColumnKeys } from '~/app/components/WorkspaceTable'; +import { WorkspacesWorkspace } from '~/generated/data-contracts'; import { WorkspaceStorage } from './WorkspaceStorage'; import { WorkspacePackageDetails } from './WorkspacePackageDetails'; import { WorkspaceConfigDetails } from './WorkspaceConfigDetails'; interface ExpandedWorkspaceRowProps { - workspace: Workspace; + workspace: WorkspacesWorkspace; visibleColumnKeys: WorkspaceTableColumnKeys[]; canExpandRows: boolean; } diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx index ae39ca00..f4cdb8b2 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx @@ -26,14 +26,14 @@ import { WorkspaceFormKindSelection } from '~/app/pages/Workspaces/Form/kind/Wor import { WorkspaceFormPodConfigSelection } from '~/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection'; import { WorkspaceFormPropertiesSelection } from '~/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSelection'; import { WorkspaceFormData } from '~/app/types'; -import { - WorkspaceCreate, - WorkspaceKind, - WorkspaceImageConfigValue, - WorkspacePodConfigValue, -} from '~/shared/api/backendApiTypes'; import useWorkspaceFormData from '~/app/hooks/useWorkspaceFormData'; import { useTypedNavigate } from '~/app/routerHelper'; +import { + WorkspacekindsImageConfigValue, + WorkspacekindsPodConfigValue, + WorkspacekindsWorkspaceKind, + WorkspacesWorkspaceCreate, +} from '~/generated/data-contracts'; import { useWorkspaceFormLocationData } from '~/app/hooks/useWorkspaceFormLocationData'; import { WorkspaceFormKindDetails } from '~/app/pages/Workspaces/Form/kind/WorkspaceFormKindDetails'; import { WorkspaceFormImageDetails } from '~/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails'; @@ -151,7 +151,7 @@ const WorkspaceForm: React.FC = () => { } // TODO: Prepare WorkspaceUpdate data accordingly when BE supports it - const submitData: WorkspaceCreate = { + const submitData: WorkspacesWorkspaceCreate = { name: data.properties.workspaceName, kind: data.kind.name, deferUpdates: data.properties.deferUpdates, @@ -177,15 +177,13 @@ const WorkspaceForm: React.FC = () => { try { if (mode === 'edit') { - const updateWorkspace = await api.updateWorkspace({}, submitData.name, namespace, { + // TODO: call api to update workspace when implemented in backend + } else { + const workspaceEnvelope = await api.workspaces.createWorkspace(namespace, { data: submitData, }); // TODO: alert user about success - console.info('Workspace updated:', JSON.stringify(updateWorkspace)); - } else { - const newWorkspace = await api.createWorkspace({}, namespace, { data: submitData }); - // TODO: alert user about success - console.info('New workspace created:', JSON.stringify(newWorkspace)); + console.info('New workspace created:', JSON.stringify(workspaceEnvelope.data)); } navigate('workspaces'); @@ -202,7 +200,7 @@ const WorkspaceForm: React.FC = () => { }, [navigate]); const handleKindSelect = useCallback( - (kind: WorkspaceKind | undefined) => { + (kind: WorkspacekindsWorkspaceKind | undefined) => { if (kind) { resetData(); setData('kind', kind); @@ -213,7 +211,7 @@ const WorkspaceForm: React.FC = () => { ); const handleImageSelect = useCallback( - (image: WorkspaceImageConfigValue | undefined) => { + (image: WorkspacekindsImageConfigValue | undefined) => { if (image) { setData('image', image); setDrawerExpanded(true); @@ -223,7 +221,7 @@ const WorkspaceForm: React.FC = () => { ); const handlePodConfigSelect = useCallback( - (podConfig: WorkspacePodConfigValue | undefined) => { + (podConfig: WorkspacekindsPodConfigValue | undefined) => { if (podConfig) { setData('podConfig', podConfig); setDrawerExpanded(true); diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails.tsx index d47a85d2..d40c4581 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails.tsx @@ -6,11 +6,11 @@ import { DescriptionListDescription, } from '@patternfly/react-core/dist/esm/components/DescriptionList'; import { Title } from '@patternfly/react-core/dist/esm/components/Title'; -import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes'; import { formatLabelKey } from '~/shared/utilities/WorkspaceUtils'; +import { WorkspacekindsPodConfigValue } from '~/generated/data-contracts'; type WorkspaceFormImageDetailsProps = { - workspaceImage?: WorkspacePodConfigValue; + workspaceImage?: WorkspacekindsPodConfigValue; }; export const WorkspaceFormImageDetails: React.FunctionComponent = ({ diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageList.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageList.tsx index 08e39205..e36d1d5f 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageList.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageList.tsx @@ -9,9 +9,9 @@ import { Gallery } from '@patternfly/react-core/dist/esm/layouts/Gallery'; import { PageSection } from '@patternfly/react-core/dist/esm/components/Page'; import { Toolbar, ToolbarContent } from '@patternfly/react-core/dist/esm/components/Toolbar'; import Filter, { FilteredColumn, FilterRef } from '~/shared/components/Filter'; -import { WorkspaceImageConfigValue } from '~/shared/api/backendApiTypes'; import CustomEmptyState from '~/shared/components/CustomEmptyState'; import { defineDataFields, FilterableDataFieldKey } from '~/app/filterableDataHelper'; +import { WorkspacekindsImageConfigValue } from '~/generated/data-contracts'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const { fields, filterableLabelMap } = defineDataFields({ @@ -21,10 +21,10 @@ const { fields, filterableLabelMap } = defineDataFields({ type FilterableDataFieldKeys = FilterableDataFieldKey; type WorkspaceFormImageListProps = { - images: WorkspaceImageConfigValue[]; + images: WorkspacekindsImageConfigValue[]; selectedLabels: Map>; - selectedImage: WorkspaceImageConfigValue | undefined; - onSelect: (workspaceImage: WorkspaceImageConfigValue | undefined) => void; + selectedImage: WorkspacekindsImageConfigValue | undefined; + onSelect: (workspaceImage: WorkspacekindsImageConfigValue | undefined) => void; }; export const WorkspaceFormImageList: React.FunctionComponent = ({ @@ -37,7 +37,7 @@ export const WorkspaceFormImageList: React.FunctionComponent(null); const getFilteredWorkspaceImagesByLabels = useCallback( - (unfilteredImages: WorkspaceImageConfigValue[]) => + (unfilteredImages: WorkspacekindsImageConfigValue[]) => unfilteredImages.filter((image) => image.labels.reduce((accumulator, label) => { if (selectedLabels.has(label.key)) { diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx index aaf807c9..6123161f 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx @@ -3,12 +3,12 @@ import { Content } from '@patternfly/react-core/dist/esm/components/Content'; import { Split, SplitItem } from '@patternfly/react-core/dist/esm/layouts/Split'; import { WorkspaceFormImageList } from '~/app/pages/Workspaces/Form/image/WorkspaceFormImageList'; import { FilterByLabels } from '~/app/pages/Workspaces/Form/labelFilter/FilterByLabels'; -import { WorkspaceImageConfigValue } from '~/shared/api/backendApiTypes'; +import { WorkspacekindsImageConfigValue } from '~/generated/data-contracts'; interface WorkspaceFormImageSelectionProps { - images: WorkspaceImageConfigValue[]; - selectedImage: WorkspaceImageConfigValue | undefined; - onSelect: (image: WorkspaceImageConfigValue | undefined) => void; + images: WorkspacekindsImageConfigValue[]; + selectedImage: WorkspacekindsImageConfigValue | undefined; + onSelect: (image: WorkspacekindsImageConfigValue | undefined) => void; } const WorkspaceFormImageSelection: React.FunctionComponent = ({ diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindDetails.tsx index 2363bf97..7a161efc 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindDetails.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindDetails.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { Title } from '@patternfly/react-core/dist/esm/components/Title'; -import { WorkspaceKind } from '~/shared/api/backendApiTypes'; +import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts'; type WorkspaceFormKindDetailsProps = { - workspaceKind?: WorkspaceKind; + workspaceKind?: WorkspacekindsWorkspaceKind; }; export const WorkspaceFormKindDetails: React.FunctionComponent = ({ diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx index 8530bcab..bac0154f 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindList.tsx @@ -8,12 +8,12 @@ import { import { Gallery } from '@patternfly/react-core/dist/esm/layouts/Gallery'; import { PageSection } from '@patternfly/react-core/dist/esm/components/Page'; import { Toolbar, ToolbarContent } from '@patternfly/react-core/dist/esm/components/Toolbar'; -import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import Filter, { FilteredColumn, FilterRef } from '~/shared/components/Filter'; import CustomEmptyState from '~/shared/components/CustomEmptyState'; import ImageFallback from '~/shared/components/ImageFallback'; import WithValidImage from '~/shared/components/WithValidImage'; import { defineDataFields, FilterableDataFieldKey } from '~/app/filterableDataHelper'; +import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const { fields, filterableLabelMap } = defineDataFields({ @@ -23,9 +23,9 @@ const { fields, filterableLabelMap } = defineDataFields({ type FilterableDataFieldKeys = FilterableDataFieldKey; type WorkspaceFormKindListProps = { - allWorkspaceKinds: WorkspaceKind[]; - selectedKind: WorkspaceKind | undefined; - onSelect: (workspaceKind: WorkspaceKind | undefined) => void; + allWorkspaceKinds: WorkspacekindsWorkspaceKind[]; + selectedKind: WorkspacekindsWorkspaceKind | undefined; + onSelect: (workspaceKind: WorkspacekindsWorkspaceKind | undefined) => void; }; export const WorkspaceFormKindList: React.FunctionComponent = ({ diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx index 9903abaf..30db818d 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { Content } from '@patternfly/react-core/dist/esm/components/Content'; -import { WorkspaceKind } from '~/shared/api/backendApiTypes'; import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds'; import { WorkspaceFormKindList } from '~/app/pages/Workspaces/Form/kind/WorkspaceFormKindList'; +import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts'; interface WorkspaceFormKindSelectionProps { - selectedKind: WorkspaceKind | undefined; - onSelect: (kind: WorkspaceKind | undefined) => void; + selectedKind: WorkspacekindsWorkspaceKind | undefined; + onSelect: (kind: WorkspacekindsWorkspaceKind | undefined) => void; } const WorkspaceFormKindSelection: React.FunctionComponent = ({ diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/labelFilter/FilterByLabels.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/labelFilter/FilterByLabels.tsx index 6303747a..8ab9586e 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/labelFilter/FilterByLabels.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/labelFilter/FilterByLabels.tsx @@ -5,11 +5,11 @@ import { FilterSidePanelCategoryItem, } from '@patternfly/react-catalog-view-extension'; import '@patternfly/react-catalog-view-extension/dist/css/react-catalog-view-extension.css'; -import { WorkspaceOptionLabel } from '~/shared/api/backendApiTypes'; import { formatLabelKey } from '~/shared/utilities/WorkspaceUtils'; +import { WorkspacesOptionLabel } from '~/generated/data-contracts'; type FilterByLabelsProps = { - labelledObjects: WorkspaceOptionLabel[]; + labelledObjects: WorkspacesOptionLabel[]; selectedLabels: Map>; onSelect: (labels: Map>) => void; }; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigDetails.tsx index d3a9ba8e..fe829a4e 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigDetails.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigDetails.tsx @@ -7,11 +7,11 @@ import { } from '@patternfly/react-core/dist/esm/components/DescriptionList'; import { Title } from '@patternfly/react-core/dist/esm/components/Title'; import { Divider } from '@patternfly/react-core/dist/esm/components/Divider'; -import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes'; import { formatLabelKey } from '~/shared/utilities/WorkspaceUtils'; +import { WorkspacekindsPodConfigValue } from '~/generated/data-contracts'; type WorkspaceFormPodConfigDetailsProps = { - workspacePodConfig?: WorkspacePodConfigValue; + workspacePodConfig?: WorkspacekindsPodConfigValue; }; export const WorkspaceFormPodConfigDetails: React.FunctionComponent< diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList.tsx index 06b9aa8b..7c200ec7 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList.tsx @@ -8,10 +8,10 @@ import { import { Gallery } from '@patternfly/react-core/dist/esm/layouts/Gallery'; import { PageSection } from '@patternfly/react-core/dist/esm/components/Page'; import { Toolbar, ToolbarContent } from '@patternfly/react-core/dist/esm/components/Toolbar'; -import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes'; import Filter, { FilteredColumn, FilterRef } from '~/shared/components/Filter'; import CustomEmptyState from '~/shared/components/CustomEmptyState'; import { defineDataFields, FilterableDataFieldKey } from '~/app/filterableDataHelper'; +import { WorkspacekindsPodConfigValue } from '~/generated/data-contracts'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const { fields, filterableLabelMap } = defineDataFields({ @@ -21,10 +21,10 @@ const { fields, filterableLabelMap } = defineDataFields({ type FilterableDataFieldKeys = FilterableDataFieldKey; type WorkspaceFormPodConfigListProps = { - podConfigs: WorkspacePodConfigValue[]; + podConfigs: WorkspacekindsPodConfigValue[]; selectedLabels: Map>; - selectedPodConfig: WorkspacePodConfigValue | undefined; - onSelect: (workspacePodConfig: WorkspacePodConfigValue | undefined) => void; + selectedPodConfig: WorkspacekindsPodConfigValue | undefined; + onSelect: (workspacePodConfig: WorkspacekindsPodConfigValue | undefined) => void; }; export const WorkspaceFormPodConfigList: React.FunctionComponent< @@ -34,7 +34,7 @@ export const WorkspaceFormPodConfigList: React.FunctionComponent< const filterRef = useRef(null); const getFilteredWorkspacePodConfigsByLabels = useCallback( - (unfilteredPodConfigs: WorkspacePodConfigValue[]) => + (unfilteredPodConfigs: WorkspacekindsPodConfigValue[]) => unfilteredPodConfigs.filter((podConfig) => podConfig.labels.reduce((accumulator, label) => { if (selectedLabels.has(label.key)) { diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx index dfe4b843..a82a2870 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx @@ -3,12 +3,12 @@ import { Content } from '@patternfly/react-core/dist/esm/components/Content'; import { Split, SplitItem } from '@patternfly/react-core/dist/esm/layouts/Split'; import { WorkspaceFormPodConfigList } from '~/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList'; import { FilterByLabels } from '~/app/pages/Workspaces/Form/labelFilter/FilterByLabels'; -import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes'; +import { WorkspacekindsPodConfigValue } from '~/generated/data-contracts'; interface WorkspaceFormPodConfigSelectionProps { - podConfigs: WorkspacePodConfigValue[]; - selectedPodConfig: WorkspacePodConfigValue | undefined; - onSelect: (podConfig: WorkspacePodConfigValue | undefined) => void; + podConfigs: WorkspacekindsPodConfigValue[]; + selectedPodConfig: WorkspacekindsPodConfigValue | undefined; + onSelect: (podConfig: WorkspacekindsPodConfigValue | undefined) => void; } const WorkspaceFormPodConfigSelection: React.FunctionComponent< diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSecrets.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSecrets.tsx index 96f1db95..66e07ab4 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSecrets.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSecrets.tsx @@ -24,11 +24,11 @@ import { MenuToggle } from '@patternfly/react-core/dist/esm/components/MenuToggl import { Form, FormGroup } from '@patternfly/react-core/dist/esm/components/Form'; import { HelperText, HelperTextItem } from '@patternfly/react-core/dist/esm/components/HelperText'; import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; -import { WorkspacePodSecretMount } from '~/shared/api/backendApiTypes'; +import { WorkspacesPodSecretMount } from '~/generated/data-contracts'; interface WorkspaceFormPropertiesSecretsProps { - secrets: WorkspacePodSecretMount[]; - setSecrets: (secrets: WorkspacePodSecretMount[]) => void; + secrets: WorkspacesPodSecretMount[]; + setSecrets: (secrets: WorkspacesPodSecretMount[]) => void; } const DEFAULT_MODE_OCTAL = (420).toString(8); @@ -39,7 +39,7 @@ export const WorkspaceFormPropertiesSecrets: React.FC { const [isModalOpen, setIsModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - const [formData, setFormData] = useState({ + const [formData, setFormData] = useState({ secretName: '', mountPath: '', defaultMode: parseInt(DEFAULT_MODE_OCTAL, 8), diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSelection.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSelection.tsx index d3c64b83..f1e6e3ae 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSelection.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSelection.tsx @@ -8,12 +8,12 @@ import { Split, SplitItem } from '@patternfly/react-core/dist/esm/layouts/Split' import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput'; import { WorkspaceFormImageDetails } from '~/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails'; import { WorkspaceFormPropertiesVolumes } from '~/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes'; +import { WorkspacekindsImageConfigValue } from '~/generated/data-contracts'; import { WorkspaceFormProperties } from '~/app/types'; -import { WorkspaceImageConfigValue } from '~/shared/api/backendApiTypes'; import { WorkspaceFormPropertiesSecrets } from './WorkspaceFormPropertiesSecrets'; interface WorkspaceFormPropertiesSelectionProps { - selectedImage: WorkspaceImageConfigValue | undefined; + selectedImage: WorkspacekindsImageConfigValue | undefined; selectedProperties: WorkspaceFormProperties; onSelect: (properties: WorkspaceFormProperties) => void; } diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes.tsx index 780e4ca7..4b44a76d 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes.tsx @@ -23,11 +23,11 @@ import { Tr, } from '@patternfly/react-table/dist/esm/components/Table'; import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; -import { WorkspacePodVolumeMount } from '~/shared/api/backendApiTypes'; +import { WorkspacesPodVolumeMount } from '~/generated/data-contracts'; interface WorkspaceFormPropertiesVolumesProps { - volumes: WorkspacePodVolumeMount[]; - setVolumes: (volumes: WorkspacePodVolumeMount[]) => void; + volumes: WorkspacesPodVolumeMount[]; + setVolumes: (volumes: WorkspacesPodVolumeMount[]) => void; } export const WorkspaceFormPropertiesVolumes: React.FC = ({ @@ -36,7 +36,7 @@ export const WorkspaceFormPropertiesVolumes: React.FC { const [isModalOpen, setIsModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - const [formData, setFormData] = useState({ + const [formData, setFormData] = useState({ pvcName: '', mountPath: '', readOnly: false, diff --git a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConfigDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConfigDetails.tsx index 1270943c..83485ff1 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConfigDetails.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConfigDetails.tsx @@ -5,11 +5,11 @@ import { DescriptionListGroup, DescriptionListDescription, } from '@patternfly/react-core/dist/esm/components/DescriptionList'; -import { Workspace } from '~/shared/api/backendApiTypes'; import { formatResourceFromWorkspace } from '~/shared/utilities/WorkspaceUtils'; +import { WorkspacesWorkspace } from '~/generated/data-contracts'; interface WorkspaceConfigDetailsProps { - workspace: Workspace; + workspace: WorkspacesWorkspace; } export const WorkspaceConfigDetails: React.FC = ({ workspace }) => ( diff --git a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConnectAction.tsx b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConnectAction.tsx index 4b231354..428a8502 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConnectAction.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConnectAction.tsx @@ -9,10 +9,10 @@ import { MenuToggleElement, MenuToggleAction, } from '@patternfly/react-core/dist/esm/components/MenuToggle'; -import { Workspace, WorkspaceState } from '~/shared/api/backendApiTypes'; +import { WorkspacesWorkspace, WorkspacesWorkspaceState } from '~/generated/data-contracts'; type WorkspaceConnectActionProps = { - workspace: Workspace; + workspace: WorkspacesWorkspace; }; export const WorkspaceConnectAction: React.FunctionComponent = ({ @@ -57,7 +57,7 @@ export const WorkspaceConnectAction: React.FunctionComponent = ({ workspace }) => { diff --git a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceStorage.tsx b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceStorage.tsx index 9f55940a..a6bd3da4 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceStorage.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceStorage.tsx @@ -5,11 +5,11 @@ import { DescriptionListGroup, DescriptionListDescription, } from '@patternfly/react-core/dist/esm/components/DescriptionList'; -import { Workspace } from '~/shared/api/backendApiTypes'; import { DataVolumesList } from '~/app/pages/Workspaces/DataVolumesList'; +import { WorkspacesWorkspace } from '~/generated/data-contracts'; interface WorkspaceStorageProps { - workspace: Workspace; + workspace: WorkspacesWorkspace; } export const WorkspaceStorage: React.FC = ({ workspace }) => ( diff --git a/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx b/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx index 0c76e373..3e461396 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx @@ -5,12 +5,12 @@ import { Stack, StackItem } from '@patternfly/react-core/dist/esm/layouts/Stack' import WorkspaceTable from '~/app/components/WorkspaceTable'; import { useNamespaceContext } from '~/app/context/NamespaceContextProvider'; import { useWorkspacesByNamespace } from '~/app/hooks/useWorkspaces'; -import { WorkspaceState } from '~/shared/api/backendApiTypes'; import { DEFAULT_POLLING_RATE_MS } from '~/app/const'; import { LoadingSpinner } from '~/app/components/LoadingSpinner'; import { LoadError } from '~/app/components/LoadError'; import { useWorkspaceRowActions } from '~/app/hooks/useWorkspaceRowActions'; import { usePolling } from '~/app/hooks/usePolling'; +import { WorkspacesWorkspaceState } from '~/generated/data-contracts'; export const Workspaces: React.FunctionComponent = () => { const { selectedNamespace } = useNamespaceContext(); @@ -27,17 +27,17 @@ export const Workspaces: React.FunctionComponent = () => { { id: 'separator' }, { id: 'stop', - isVisible: (w) => w.state === WorkspaceState.WorkspaceStateRunning, + isVisible: (w) => w.state === WorkspacesWorkspaceState.WorkspaceStateRunning, onActionDone: refreshWorkspaces, }, { id: 'start', - isVisible: (w) => w.state !== WorkspaceState.WorkspaceStateRunning, + isVisible: (w) => w.state !== WorkspacesWorkspaceState.WorkspaceStateRunning, onActionDone: refreshWorkspaces, }, { id: 'restart', - isVisible: (w) => w.state === WorkspaceState.WorkspaceStateRunning, + isVisible: (w) => w.state === WorkspacesWorkspaceState.WorkspaceStateRunning, onActionDone: refreshWorkspaces, }, ]); diff --git a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView.tsx b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView.tsx index 72b2786f..4d50ff5a 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView.tsx @@ -7,7 +7,7 @@ import { ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/ex import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; import { InfoCircleIcon } from '@patternfly/react-icons/dist/esm/icons/info-circle-icon'; import useWorkspaceKindByName from '~/app/hooks/useWorkspaceKindByName'; -import { WorkspaceKind } from '~/shared/api/backendApiTypes'; +import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts'; const getLevelIcon = (level: string | undefined) => { switch (level) { @@ -48,9 +48,9 @@ export const WorkspaceRedirectInformationView: React.FC(0); const [workspaceKind, workspaceKindLoaded] = useWorkspaceKindByName(kind); const [imageConfig, setImageConfig] = - useState(); + useState(); const [podConfig, setPodConfig] = - useState(); + useState(); useEffect(() => { if (!workspaceKindLoaded) { diff --git a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRestartActionModal.tsx b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRestartActionModal.tsx index 3e341701..8a641a5d 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRestartActionModal.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceRestartActionModal.tsx @@ -8,13 +8,13 @@ import { ModalHeader, } from '@patternfly/react-core/dist/esm/components/Modal'; import { TabTitleText } from '@patternfly/react-core/dist/esm/components/Tabs'; -import { Workspace } from '~/shared/api/backendApiTypes'; import { WorkspaceRedirectInformationView } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView'; +import { WorkspacesWorkspace } from '~/generated/data-contracts'; interface RestartActionAlertProps { onClose: () => void; isOpen: boolean; - workspace: Workspace | null; + workspace: WorkspacesWorkspace | null; } export const WorkspaceRestartActionModal: React.FC = ({ diff --git a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal.tsx b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal.tsx index bbd0c532..1bbfcaf4 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal.tsx @@ -8,14 +8,14 @@ import { } from '@patternfly/react-core/dist/esm/components/Modal'; import { TabTitleText } from '@patternfly/react-core/dist/esm/components/Tabs'; import { WorkspaceRedirectInformationView } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView'; -import { Workspace, WorkspacePauseState } from '~/shared/api/backendApiTypes'; import { ActionButton } from '~/shared/components/ActionButton'; +import { ApiWorkspaceActionPauseEnvelope, WorkspacesWorkspace } from '~/generated/data-contracts'; interface StartActionAlertProps { onClose: () => void; isOpen: boolean; - workspace: Workspace | null; - onStart: () => Promise; + workspace: WorkspacesWorkspace | null; + onStart: () => Promise; onUpdateAndStart: () => Promise; onActionDone?: () => void; } @@ -33,10 +33,16 @@ export const WorkspaceStartActionModal: React.FC = ({ const [actionOnGoing, setActionOnGoing] = useState(null); const executeAction = useCallback( - (args: { action: StartAction; callback: () => ReturnType }) => { - setActionOnGoing(args.action); + async ({ + action, + callback, + }: { + action: StartAction; + callback: () => Promise; + }): Promise => { + setActionOnGoing(action); try { - return args.callback(); + return await callback(); } finally { setActionOnGoing(null); } @@ -48,7 +54,7 @@ export const WorkspaceStartActionModal: React.FC = ({ try { const response = await executeAction({ action: 'start', callback: onStart }); // TODO: alert user about success - console.info('Workspace started successfully:', JSON.stringify(response)); + console.info('Workspace started successfully:', JSON.stringify(response.data)); onActionDone?.(); onClose(); } catch (error) { diff --git a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal.tsx b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal.tsx index 631724ba..793efddc 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal.tsx @@ -9,14 +9,14 @@ import { } from '@patternfly/react-core/dist/esm/components/Modal'; import { TabTitleText } from '@patternfly/react-core/dist/esm/components/Tabs'; import { WorkspaceRedirectInformationView } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView'; -import { Workspace, WorkspacePauseState } from '~/shared/api/backendApiTypes'; import { ActionButton } from '~/shared/components/ActionButton'; +import { ApiWorkspaceActionPauseEnvelope, WorkspacesWorkspace } from '~/generated/data-contracts'; interface StopActionAlertProps { onClose: () => void; isOpen: boolean; - workspace: Workspace | null; - onStop: () => Promise; + workspace: WorkspacesWorkspace | null; + onStop: () => Promise; onUpdateAndStop: () => Promise; onActionDone?: () => void; } @@ -35,10 +35,16 @@ export const WorkspaceStopActionModal: React.FC = ({ const [actionOnGoing, setActionOnGoing] = useState(null); const executeAction = useCallback( - (args: { action: StopAction; callback: () => ReturnType }) => { - setActionOnGoing(args.action); + async ({ + action, + callback, + }: { + action: StopAction; + callback: () => Promise; + }): Promise => { + setActionOnGoing(action); try { - return args.callback(); + return await callback(); } finally { setActionOnGoing(null); } @@ -50,7 +56,7 @@ export const WorkspaceStopActionModal: React.FC = ({ try { const response = await executeAction({ action: 'stop', callback: onStop }); // TODO: alert user about success - console.info('Workspace stopped successfully:', JSON.stringify(response)); + console.info('Workspace stopped successfully:', JSON.stringify(response.data)); onActionDone?.(); onClose(); } catch (error) { diff --git a/workspaces/frontend/src/app/types.ts b/workspaces/frontend/src/app/types.ts index b87f7f8a..b9c68f39 100644 --- a/workspaces/frontend/src/app/types.ts +++ b/workspaces/frontend/src/app/types.ts @@ -1,14 +1,14 @@ import { - WorkspaceImageConfigValue, - WorkspaceKind, - WorkspacePodConfigValue, - WorkspacePodVolumeMount, - WorkspacePodSecretMount, - Workspace, - WorkspaceImageRef, - WorkspacePodVolumeMounts, - WorkspaceKindPodMetadata, -} from '~/shared/api/backendApiTypes'; + WorkspacekindsImageConfigValue, + WorkspacekindsImageRef, + WorkspacekindsPodConfigValue, + WorkspacekindsPodMetadata, + WorkspacekindsPodVolumeMounts, + WorkspacekindsWorkspaceKind, + WorkspacesPodSecretMount, + WorkspacesPodVolumeMount, + WorkspacesWorkspace, +} from '~/generated/data-contracts'; export interface WorkspaceColumnDefinition { name: string; @@ -27,22 +27,22 @@ export interface WorkspaceFormProperties { workspaceName: string; deferUpdates: boolean; homeDirectory: string; - volumes: WorkspacePodVolumeMount[]; - secrets: WorkspacePodSecretMount[]; + volumes: WorkspacesPodVolumeMount[]; + secrets: WorkspacesPodSecretMount[]; } export interface WorkspaceFormData { - kind: WorkspaceKind | undefined; - image: WorkspaceImageConfigValue | undefined; - podConfig: WorkspacePodConfigValue | undefined; + kind: WorkspacekindsWorkspaceKind | undefined; + image: WorkspacekindsImageConfigValue | undefined; + podConfig: WorkspacekindsPodConfigValue | undefined; properties: WorkspaceFormProperties; } export interface WorkspaceCountPerOption { count: number; - countByImage: Record; - countByPodConfig: Record; - countByNamespace: Record; + countByImage: Record; + countByPodConfig: Record; + countByNamespace: Record; } export interface WorkspaceKindProperties { @@ -51,11 +51,11 @@ export interface WorkspaceKindProperties { deprecated: boolean; deprecationMessage: string; hidden: boolean; - icon: WorkspaceImageRef; - logo: WorkspaceImageRef; + icon: WorkspacekindsImageRef; + logo: WorkspacekindsImageRef; } -export interface WorkspaceKindImageConfigValue extends WorkspaceImageConfigValue { +export interface WorkspaceKindImageConfigValue extends WorkspacekindsImageConfigValue { imagePullPolicy?: ImagePullPolicy.IfNotPresent | ImagePullPolicy.Always | ImagePullPolicy.Never; ports?: WorkspaceKindImagePort[]; image?: string; @@ -74,7 +74,7 @@ export interface WorkspaceKindImagePort { protocol: 'HTTP'; // ONLY HTTP is supported at the moment, per https://github.com/thesuperzapper/kubeflow-notebooks-v2-design/blob/main/crds/workspace-kind.yaml#L275 } -export interface WorkspaceKindPodConfigValue extends WorkspacePodConfigValue { +export interface WorkspaceKindPodConfigValue extends WorkspacekindsPodConfigValue { resources?: { requests: { [key: string]: string; @@ -105,10 +105,10 @@ export interface WorkspaceKindPodCulling { } export interface WorkspaceKindPodTemplateData { - podMetadata: WorkspaceKindPodMetadata; - volumeMounts: WorkspacePodVolumeMounts; + podMetadata: WorkspacekindsPodMetadata; + volumeMounts: WorkspacekindsPodVolumeMounts; culling?: WorkspaceKindPodCulling; - extraVolumeMounts?: WorkspacePodVolumeMount[]; + extraVolumeMounts?: WorkspacesPodVolumeMount[]; } export interface WorkspaceKindFormData { diff --git a/workspaces/frontend/src/generated/Healthcheck.ts b/workspaces/frontend/src/generated/Healthcheck.ts new file mode 100644 index 00000000..d8b10945 --- /dev/null +++ b/workspaces/frontend/src/generated/Healthcheck.ts @@ -0,0 +1,34 @@ +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +/* + * --------------------------------------------------------------- + * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ## + * ## ## + * ## AUTHOR: acacode ## + * ## SOURCE: https://github.com/acacode/swagger-typescript-api ## + * --------------------------------------------------------------- + */ + +import { ApiErrorEnvelope, HealthCheckHealthCheck } from './data-contracts'; +import { HttpClient, RequestParams } from './http-client'; + +export class Healthcheck extends HttpClient { + /** + * @description Provides a healthcheck response indicating the status of key services. + * + * @tags healthcheck + * @name GetHealthcheck + * @summary Returns the health status of the application + * @request GET:/healthcheck + * @response `200` `HealthCheckHealthCheck` Successful healthcheck response + * @response `500` `ApiErrorEnvelope` Internal server error + */ + getHealthcheck = (params: RequestParams = {}) => + this.request({ + path: `/healthcheck`, + method: 'GET', + format: 'json', + ...params, + }); +} diff --git a/workspaces/frontend/src/generated/Namespaces.ts b/workspaces/frontend/src/generated/Namespaces.ts new file mode 100644 index 00000000..f626fc92 --- /dev/null +++ b/workspaces/frontend/src/generated/Namespaces.ts @@ -0,0 +1,36 @@ +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +/* + * --------------------------------------------------------------- + * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ## + * ## ## + * ## AUTHOR: acacode ## + * ## SOURCE: https://github.com/acacode/swagger-typescript-api ## + * --------------------------------------------------------------- + */ + +import { ApiErrorEnvelope, ApiNamespaceListEnvelope } from './data-contracts'; +import { HttpClient, RequestParams } from './http-client'; + +export class Namespaces extends HttpClient { + /** + * @description Provides a list of all namespaces that the user has access to + * + * @tags namespaces + * @name ListNamespaces + * @summary Returns a list of all namespaces + * @request GET:/namespaces + * @response `200` `ApiNamespaceListEnvelope` Successful namespaces response + * @response `401` `ApiErrorEnvelope` Unauthorized + * @response `403` `ApiErrorEnvelope` Forbidden + * @response `500` `ApiErrorEnvelope` Internal server error + */ + listNamespaces = (params: RequestParams = {}) => + this.request({ + path: `/namespaces`, + method: 'GET', + format: 'json', + ...params, + }); +} diff --git a/workspaces/frontend/src/generated/Workspacekinds.ts b/workspaces/frontend/src/generated/Workspacekinds.ts new file mode 100644 index 00000000..4b250e17 --- /dev/null +++ b/workspaces/frontend/src/generated/Workspacekinds.ts @@ -0,0 +1,89 @@ +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +/* + * --------------------------------------------------------------- + * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ## + * ## ## + * ## AUTHOR: acacode ## + * ## SOURCE: https://github.com/acacode/swagger-typescript-api ## + * --------------------------------------------------------------- + */ + +import { + ApiErrorEnvelope, + ApiWorkspaceKindEnvelope, + ApiWorkspaceKindListEnvelope, + CreateWorkspaceKindPayload, +} from './data-contracts'; +import { ContentType, HttpClient, RequestParams } from './http-client'; + +export class Workspacekinds extends HttpClient { + /** + * @description Returns a list of all available workspace kinds. Workspace kinds define the different types of workspaces that can be created in the system. + * + * @tags workspacekinds + * @name ListWorkspaceKinds + * @summary List workspace kinds + * @request GET:/workspacekinds + * @response `200` `ApiWorkspaceKindListEnvelope` Successful operation. Returns a list of all available workspace kinds. + * @response `401` `ApiErrorEnvelope` Unauthorized. Authentication is required. + * @response `403` `ApiErrorEnvelope` Forbidden. User does not have permission to list workspace kinds. + * @response `500` `ApiErrorEnvelope` Internal server error. An unexpected error occurred on the server. + */ + listWorkspaceKinds = (params: RequestParams = {}) => + this.request({ + path: `/workspacekinds`, + method: 'GET', + type: ContentType.Json, + format: 'json', + ...params, + }); + /** + * @description Creates a new workspace kind. + * + * @tags workspacekinds + * @name CreateWorkspaceKind + * @summary Create workspace kind + * @request POST:/workspacekinds + * @response `201` `ApiWorkspaceKindEnvelope` WorkspaceKind created successfully + * @response `400` `ApiErrorEnvelope` Bad Request. + * @response `401` `ApiErrorEnvelope` Unauthorized. Authentication is required. + * @response `403` `ApiErrorEnvelope` Forbidden. User does not have permission to create WorkspaceKind. + * @response `409` `ApiErrorEnvelope` Conflict. WorkspaceKind with the same name already exists. + * @response `413` `ApiErrorEnvelope` Request Entity Too Large. The request body is too large. + * @response `415` `ApiErrorEnvelope` Unsupported Media Type. Content-Type header is not correct. + * @response `422` `ApiErrorEnvelope` Unprocessable Entity. Validation error. + * @response `500` `ApiErrorEnvelope` Internal server error. An unexpected error occurred on the server. + */ + createWorkspaceKind = (body: CreateWorkspaceKindPayload, params: RequestParams = {}) => + this.request({ + path: `/workspacekinds`, + method: 'POST', + body: body, + format: 'json', + ...params, + }); + /** + * @description Returns details of a specific workspace kind identified by its name. Workspace kinds define the available types of workspaces that can be created. + * + * @tags workspacekinds + * @name GetWorkspaceKind + * @summary Get workspace kind + * @request GET:/workspacekinds/{name} + * @response `200` `ApiWorkspaceKindEnvelope` Successful operation. Returns the requested workspace kind details. + * @response `400` `ApiErrorEnvelope` Bad Request. Invalid workspace kind name format. + * @response `401` `ApiErrorEnvelope` Unauthorized. Authentication is required. + * @response `403` `ApiErrorEnvelope` Forbidden. User does not have permission to access the workspace kind. + * @response `404` `ApiErrorEnvelope` Not Found. Workspace kind does not exist. + * @response `500` `ApiErrorEnvelope` Internal server error. An unexpected error occurred on the server. + */ + getWorkspaceKind = (name: string, params: RequestParams = {}) => + this.request({ + path: `/workspacekinds/${name}`, + method: 'GET', + type: ContentType.Json, + format: 'json', + ...params, + }); +} diff --git a/workspaces/frontend/src/generated/Workspaces.ts b/workspaces/frontend/src/generated/Workspaces.ts new file mode 100644 index 00000000..4c616f6c --- /dev/null +++ b/workspaces/frontend/src/generated/Workspaces.ts @@ -0,0 +1,167 @@ +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +/* + * --------------------------------------------------------------- + * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ## + * ## ## + * ## AUTHOR: acacode ## + * ## SOURCE: https://github.com/acacode/swagger-typescript-api ## + * --------------------------------------------------------------- + */ + +import { + ApiErrorEnvelope, + ApiWorkspaceActionPauseEnvelope, + ApiWorkspaceCreateEnvelope, + ApiWorkspaceEnvelope, + ApiWorkspaceListEnvelope, +} from './data-contracts'; +import { ContentType, HttpClient, RequestParams } from './http-client'; + +export class Workspaces extends HttpClient { + /** + * @description Returns a list of all workspaces across all namespaces. + * + * @tags workspaces + * @name ListAllWorkspaces + * @summary List all workspaces + * @request GET:/workspaces + * @response `200` `ApiWorkspaceListEnvelope` Successful operation. Returns a list of all workspaces. + * @response `401` `ApiErrorEnvelope` Unauthorized. Authentication is required. + * @response `403` `ApiErrorEnvelope` Forbidden. User does not have permission to list workspaces. + * @response `500` `ApiErrorEnvelope` Internal server error. An unexpected error occurred on the server. + */ + listAllWorkspaces = (params: RequestParams = {}) => + this.request({ + path: `/workspaces`, + method: 'GET', + type: ContentType.Json, + format: 'json', + ...params, + }); + /** + * @description Returns a list of workspaces in a specific namespace. + * + * @tags workspaces + * @name ListWorkspacesByNamespace + * @summary List workspaces by namespace + * @request GET:/workspaces/{namespace} + * @response `200` `ApiWorkspaceListEnvelope` Successful operation. Returns a list of workspaces in the specified namespace. + * @response `400` `ApiErrorEnvelope` Bad Request. Invalid namespace format. + * @response `401` `ApiErrorEnvelope` Unauthorized. Authentication is required. + * @response `403` `ApiErrorEnvelope` Forbidden. User does not have permission to list workspaces. + * @response `500` `ApiErrorEnvelope` Internal server error. An unexpected error occurred on the server. + */ + listWorkspacesByNamespace = (namespace: string, params: RequestParams = {}) => + this.request({ + path: `/workspaces/${namespace}`, + method: 'GET', + type: ContentType.Json, + format: 'json', + ...params, + }); + /** + * @description Creates a new workspace in the specified namespace. + * + * @tags workspaces + * @name CreateWorkspace + * @summary Create workspace + * @request POST:/workspaces/{namespace} + * @response `201` `ApiWorkspaceEnvelope` Workspace created successfully + * @response `400` `ApiErrorEnvelope` Bad Request. Invalid request body or namespace format. + * @response `401` `ApiErrorEnvelope` Unauthorized. Authentication is required. + * @response `403` `ApiErrorEnvelope` Forbidden. User does not have permission to create workspace. + * @response `409` `ApiErrorEnvelope` Conflict. Workspace with the same name already exists. + * @response `413` `ApiErrorEnvelope` Request Entity Too Large. The request body is too large. + * @response `415` `ApiErrorEnvelope` Unsupported Media Type. Content-Type header is not correct. + * @response `500` `ApiErrorEnvelope` Internal server error. An unexpected error occurred on the server. + */ + createWorkspace = ( + namespace: string, + body: ApiWorkspaceCreateEnvelope, + params: RequestParams = {}, + ) => + this.request({ + path: `/workspaces/${namespace}`, + method: 'POST', + body: body, + type: ContentType.Json, + format: 'json', + ...params, + }); + /** + * @description Pauses or unpauses a workspace, stopping or resuming all associated pods. + * + * @tags workspaces + * @name UpdateWorkspacePauseState + * @summary Pause or unpause a workspace + * @request POST:/workspaces/{namespace}/{workspaceName}/actions/pause + * @response `200` `ApiWorkspaceActionPauseEnvelope` Successful action. Returns the current pause state. + * @response `400` `ApiErrorEnvelope` Bad Request. + * @response `401` `ApiErrorEnvelope` Unauthorized. Authentication is required. + * @response `403` `ApiErrorEnvelope` Forbidden. User does not have permission to access the workspace. + * @response `404` `ApiErrorEnvelope` Not Found. Workspace does not exist. + * @response `413` `ApiErrorEnvelope` Request Entity Too Large. The request body is too large. + * @response `415` `ApiErrorEnvelope` Unsupported Media Type. Content-Type header is not correct. + * @response `422` `ApiErrorEnvelope` Unprocessable Entity. Workspace is not in appropriate state. + * @response `500` `ApiErrorEnvelope` Internal server error. An unexpected error occurred on the server. + */ + updateWorkspacePauseState = ( + namespace: string, + workspaceName: string, + body: ApiWorkspaceActionPauseEnvelope, + params: RequestParams = {}, + ) => + this.request({ + path: `/workspaces/${namespace}/${workspaceName}/actions/pause`, + method: 'POST', + body: body, + type: ContentType.Json, + format: 'json', + ...params, + }); + /** + * @description Returns details of a specific workspace identified by namespace and workspace name. + * + * @tags workspaces + * @name GetWorkspace + * @summary Get workspace + * @request GET:/workspaces/{namespace}/{workspace_name} + * @response `200` `ApiWorkspaceEnvelope` Successful operation. Returns the requested workspace details. + * @response `400` `ApiErrorEnvelope` Bad Request. Invalid namespace or workspace name format. + * @response `401` `ApiErrorEnvelope` Unauthorized. Authentication is required. + * @response `403` `ApiErrorEnvelope` Forbidden. User does not have permission to access the workspace. + * @response `404` `ApiErrorEnvelope` Not Found. Workspace does not exist. + * @response `500` `ApiErrorEnvelope` Internal server error. An unexpected error occurred on the server. + */ + getWorkspace = (namespace: string, workspaceName: string, params: RequestParams = {}) => + this.request({ + path: `/workspaces/${namespace}/${workspaceName}`, + method: 'GET', + type: ContentType.Json, + format: 'json', + ...params, + }); + /** + * @description Deletes a specific workspace identified by namespace and workspace name. + * + * @tags workspaces + * @name DeleteWorkspace + * @summary Delete workspace + * @request DELETE:/workspaces/{namespace}/{workspace_name} + * @response `204` `void` Workspace deleted successfully + * @response `400` `ApiErrorEnvelope` Bad Request. Invalid namespace or workspace name format. + * @response `401` `ApiErrorEnvelope` Unauthorized. Authentication is required. + * @response `403` `ApiErrorEnvelope` Forbidden. User does not have permission to delete the workspace. + * @response `404` `ApiErrorEnvelope` Not Found. Workspace does not exist. + * @response `500` `ApiErrorEnvelope` Internal server error. An unexpected error occurred on the server. + */ + deleteWorkspace = (namespace: string, workspaceName: string, params: RequestParams = {}) => + this.request({ + path: `/workspaces/${namespace}/${workspaceName}`, + method: 'DELETE', + type: ContentType.Json, + ...params, + }); +} diff --git a/workspaces/frontend/src/generated/data-contracts.ts b/workspaces/frontend/src/generated/data-contracts.ts new file mode 100644 index 00000000..12a6a45e --- /dev/null +++ b/workspaces/frontend/src/generated/data-contracts.ts @@ -0,0 +1,373 @@ +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +/* + * --------------------------------------------------------------- + * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ## + * ## ## + * ## AUTHOR: acacode ## + * ## SOURCE: https://github.com/acacode/swagger-typescript-api ## + * --------------------------------------------------------------- + */ + +export enum WorkspacesWorkspaceState { + WorkspaceStateRunning = 'Running', + WorkspaceStateTerminating = 'Terminating', + WorkspaceStatePaused = 'Paused', + WorkspaceStatePending = 'Pending', + WorkspaceStateError = 'Error', + WorkspaceStateUnknown = 'Unknown', +} + +export enum WorkspacesRedirectMessageLevel { + RedirectMessageLevelInfo = 'Info', + RedirectMessageLevelWarning = 'Warning', + RedirectMessageLevelDanger = 'Danger', +} + +export enum WorkspacesProbeResult { + ProbeResultSuccess = 'Success', + ProbeResultFailure = 'Failure', + ProbeResultTimeout = 'Timeout', +} + +export enum WorkspacekindsRedirectMessageLevel { + RedirectMessageLevelInfo = 'Info', + RedirectMessageLevelWarning = 'Warning', + RedirectMessageLevelDanger = 'Danger', +} + +export enum HealthCheckServiceStatus { + ServiceStatusHealthy = 'Healthy', + ServiceStatusUnhealthy = 'Unhealthy', +} + +export enum FieldErrorType { + ErrorTypeNotFound = 'FieldValueNotFound', + ErrorTypeRequired = 'FieldValueRequired', + ErrorTypeDuplicate = 'FieldValueDuplicate', + ErrorTypeInvalid = 'FieldValueInvalid', + ErrorTypeNotSupported = 'FieldValueNotSupported', + ErrorTypeForbidden = 'FieldValueForbidden', + ErrorTypeTooLong = 'FieldValueTooLong', + ErrorTypeTooMany = 'FieldValueTooMany', + ErrorTypeInternal = 'InternalError', + ErrorTypeTypeInvalid = 'FieldValueTypeInvalid', +} + +export interface ActionsWorkspaceActionPause { + paused: boolean; +} + +export interface ApiErrorCause { + validation_errors?: ApiValidationError[]; +} + +export interface ApiErrorEnvelope { + error: ApiHTTPError; +} + +export interface ApiHTTPError { + cause?: ApiErrorCause; + code: string; + message: string; +} + +export interface ApiNamespaceListEnvelope { + data: NamespacesNamespace[]; +} + +export interface ApiValidationError { + field: string; + message: string; + type: FieldErrorType; +} + +export interface ApiWorkspaceActionPauseEnvelope { + data: ActionsWorkspaceActionPause; +} + +export interface ApiWorkspaceCreateEnvelope { + data: WorkspacesWorkspaceCreate; +} + +export interface ApiWorkspaceEnvelope { + data: WorkspacesWorkspace; +} + +export interface ApiWorkspaceKindEnvelope { + data: WorkspacekindsWorkspaceKind; +} + +export interface ApiWorkspaceKindListEnvelope { + data: WorkspacekindsWorkspaceKind[]; +} + +export interface ApiWorkspaceListEnvelope { + data: WorkspacesWorkspace[]; +} + +export interface HealthCheckHealthCheck { + status: HealthCheckServiceStatus; + systemInfo: HealthCheckSystemInfo; +} + +export interface HealthCheckSystemInfo { + version: string; +} + +export interface NamespacesNamespace { + name: string; +} + +export interface WorkspacekindsImageConfig { + default: string; + values: WorkspacekindsImageConfigValue[]; +} + +export interface WorkspacekindsImageConfigValue { + clusterMetrics?: WorkspacekindsClusterMetrics; + description: string; + displayName: string; + hidden: boolean; + id: string; + labels: WorkspacekindsOptionLabel[]; + redirect?: WorkspacekindsOptionRedirect; +} + +export interface WorkspacekindsImageRef { + url: string; +} + +export interface WorkspacekindsOptionLabel { + key: string; + value: string; +} + +export interface WorkspacekindsOptionRedirect { + message?: WorkspacekindsRedirectMessage; + to: string; +} + +export interface WorkspacekindsPodConfig { + default: string; + values: WorkspacekindsPodConfigValue[]; +} + +export interface WorkspacekindsPodConfigValue { + clusterMetrics?: WorkspacekindsClusterMetrics; + description: string; + displayName: string; + hidden: boolean; + id: string; + labels: WorkspacekindsOptionLabel[]; + redirect?: WorkspacekindsOptionRedirect; +} + +export interface WorkspacekindsPodMetadata { + annotations: Record; + labels: Record; +} + +export interface WorkspacekindsPodTemplate { + options: WorkspacekindsPodTemplateOptions; + podMetadata: WorkspacekindsPodMetadata; + volumeMounts: WorkspacekindsPodVolumeMounts; +} + +export interface WorkspacekindsPodTemplateOptions { + imageConfig: WorkspacekindsImageConfig; + podConfig: WorkspacekindsPodConfig; +} + +export interface WorkspacekindsPodVolumeMounts { + home: string; +} + +export interface WorkspacekindsRedirectMessage { + level: WorkspacekindsRedirectMessageLevel; + text: string; +} + +export interface WorkspacekindsWorkspaceKind { + clusterMetrics?: WorkspacekindsClusterMetrics; + deprecated: boolean; + deprecationMessage: string; + description: string; + displayName: string; + hidden: boolean; + icon: WorkspacekindsImageRef; + logo: WorkspacekindsImageRef; + name: string; + podTemplate: WorkspacekindsPodTemplate; +} + +export interface WorkspacekindsClusterMetrics { + workspacesCount: number; +} + +export interface WorkspacesActivity { + /** Unix Epoch time */ + lastActivity: number; + lastProbe?: WorkspacesLastProbeInfo; + /** Unix Epoch time */ + lastUpdate: number; +} + +export interface WorkspacesHttpService { + displayName: string; + httpPath: string; +} + +export interface WorkspacesImageConfig { + current: WorkspacesOptionInfo; + desired?: WorkspacesOptionInfo; + redirectChain?: WorkspacesRedirectStep[]; +} + +export interface WorkspacesImageRef { + url: string; +} + +export interface WorkspacesLastProbeInfo { + /** Unix Epoch time in milliseconds */ + endTimeMs: number; + message: string; + result: WorkspacesProbeResult; + /** Unix Epoch time in milliseconds */ + startTimeMs: number; +} + +export interface WorkspacesOptionInfo { + description: string; + displayName: string; + id: string; + labels: WorkspacesOptionLabel[]; +} + +export interface WorkspacesOptionLabel { + key: string; + value: string; +} + +export interface WorkspacesPodConfig { + current: WorkspacesOptionInfo; + desired?: WorkspacesOptionInfo; + redirectChain?: WorkspacesRedirectStep[]; +} + +export interface WorkspacesPodMetadata { + annotations: Record; + labels: Record; +} + +export interface WorkspacesPodMetadataMutate { + annotations: Record; + labels: Record; +} + +export interface WorkspacesPodSecretInfo { + defaultMode?: number; + mountPath: string; + secretName: string; +} + +export interface WorkspacesPodSecretMount { + defaultMode?: number; + mountPath: string; + secretName: string; +} + +export interface WorkspacesPodTemplate { + options: WorkspacesPodTemplateOptions; + podMetadata: WorkspacesPodMetadata; + volumes: WorkspacesPodVolumes; +} + +export interface WorkspacesPodTemplateMutate { + options: WorkspacesPodTemplateOptionsMutate; + podMetadata: WorkspacesPodMetadataMutate; + volumes: WorkspacesPodVolumesMutate; +} + +export interface WorkspacesPodTemplateOptions { + imageConfig: WorkspacesImageConfig; + podConfig: WorkspacesPodConfig; +} + +export interface WorkspacesPodTemplateOptionsMutate { + imageConfig: string; + podConfig: string; +} + +export interface WorkspacesPodVolumeInfo { + mountPath: string; + pvcName: string; + readOnly: boolean; +} + +export interface WorkspacesPodVolumeMount { + mountPath: string; + pvcName: string; + readOnly?: boolean; +} + +export interface WorkspacesPodVolumes { + data: WorkspacesPodVolumeInfo[]; + home?: WorkspacesPodVolumeInfo; + secrets?: WorkspacesPodSecretInfo[]; +} + +export interface WorkspacesPodVolumesMutate { + data: WorkspacesPodVolumeMount[]; + home?: string; + secrets?: WorkspacesPodSecretMount[]; +} + +export interface WorkspacesRedirectMessage { + level: WorkspacesRedirectMessageLevel; + text: string; +} + +export interface WorkspacesRedirectStep { + message?: WorkspacesRedirectMessage; + sourceId: string; + targetId: string; +} + +export interface WorkspacesService { + httpService?: WorkspacesHttpService; +} + +export interface WorkspacesWorkspace { + activity: WorkspacesActivity; + deferUpdates: boolean; + name: string; + namespace: string; + paused: boolean; + pausedTime: number; + pendingRestart: boolean; + podTemplate: WorkspacesPodTemplate; + services: WorkspacesService[]; + state: WorkspacesWorkspaceState; + stateMessage: string; + workspaceKind: WorkspacesWorkspaceKindInfo; +} + +export interface WorkspacesWorkspaceCreate { + deferUpdates: boolean; + kind: string; + name: string; + paused: boolean; + podTemplate: WorkspacesPodTemplateMutate; +} + +export interface WorkspacesWorkspaceKindInfo { + icon: WorkspacesImageRef; + logo: WorkspacesImageRef; + missing: boolean; + name: string; +} + +/** Kubernetes YAML manifest of a WorkspaceKind */ +export type CreateWorkspaceKindPayload = string; diff --git a/workspaces/frontend/src/generated/http-client.ts b/workspaces/frontend/src/generated/http-client.ts new file mode 100644 index 00000000..911e057f --- /dev/null +++ b/workspaces/frontend/src/generated/http-client.ts @@ -0,0 +1,163 @@ +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +/* + * --------------------------------------------------------------- + * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ## + * ## ## + * ## AUTHOR: acacode ## + * ## SOURCE: https://github.com/acacode/swagger-typescript-api ## + * --------------------------------------------------------------- + */ + +import type { AxiosInstance, AxiosRequestConfig, HeadersDefaults, ResponseType } from 'axios'; +import axios from 'axios'; + +export type QueryParamsType = Record; + +export interface FullRequestParams + extends Omit { + /** set parameter to `true` for call `securityWorker` for this request */ + secure?: boolean; + /** request path */ + path: string; + /** content type of request body */ + type?: ContentType; + /** query params */ + query?: QueryParamsType; + /** format of response (i.e. response.json() -> format: "json") */ + format?: ResponseType; + /** request body */ + body?: unknown; +} + +export type RequestParams = Omit; + +export interface ApiConfig + extends Omit { + securityWorker?: ( + securityData: SecurityDataType | null, + ) => Promise | AxiosRequestConfig | void; + secure?: boolean; + format?: ResponseType; +} + +export enum ContentType { + Json = 'application/json', + JsonApi = 'application/vnd.api+json', + FormData = 'multipart/form-data', + UrlEncoded = 'application/x-www-form-urlencoded', + Text = 'text/plain', +} + +export class HttpClient { + public instance: AxiosInstance; + private securityData: SecurityDataType | null = null; + private securityWorker?: ApiConfig['securityWorker']; + private secure?: boolean; + private format?: ResponseType; + + constructor({ + securityWorker, + secure, + format, + ...axiosConfig + }: ApiConfig = {}) { + this.instance = axios.create({ + ...axiosConfig, + baseURL: axiosConfig.baseURL || 'http://localhost:4000/api/v1', + }); + this.secure = secure; + this.format = format; + this.securityWorker = securityWorker; + } + + public setSecurityData = (data: SecurityDataType | null) => { + this.securityData = data; + }; + + protected mergeRequestParams( + params1: AxiosRequestConfig, + params2?: AxiosRequestConfig, + ): AxiosRequestConfig { + const method = params1.method || (params2 && params2.method); + + return { + ...this.instance.defaults, + ...params1, + ...(params2 || {}), + headers: { + ...((method && + this.instance.defaults.headers[method.toLowerCase() as keyof HeadersDefaults]) || + {}), + ...(params1.headers || {}), + ...((params2 && params2.headers) || {}), + }, + }; + } + + protected stringifyFormItem(formItem: unknown) { + if (typeof formItem === 'object' && formItem !== null) { + return JSON.stringify(formItem); + } else { + return `${formItem}`; + } + } + + protected createFormData(input: Record): FormData { + if (input instanceof FormData) { + return input; + } + return Object.keys(input || {}).reduce((formData, key) => { + const property = input[key]; + const propertyContent: any[] = property instanceof Array ? property : [property]; + + for (const formItem of propertyContent) { + const isFileType = formItem instanceof Blob || formItem instanceof File; + formData.append(key, isFileType ? formItem : this.stringifyFormItem(formItem)); + } + + return formData; + }, new FormData()); + } + + public request = async ({ + secure, + path, + type, + query, + format, + body, + ...params + }: FullRequestParams): Promise => { + const secureParams = + ((typeof secure === 'boolean' ? secure : this.secure) && + this.securityWorker && + (await this.securityWorker(this.securityData))) || + {}; + const requestParams = this.mergeRequestParams(params, secureParams); + const responseFormat = format || this.format || undefined; + + if (type === ContentType.FormData && body && body !== null && typeof body === 'object') { + body = this.createFormData(body as Record); + } + + if (type === ContentType.Text && body && body !== null && typeof body !== 'string') { + body = JSON.stringify(body); + } + + return this.instance + .request({ + ...requestParams, + headers: { + ...(requestParams.headers || {}), + ...(type ? { 'Content-Type': type } : {}), + }, + params: query, + responseType: responseFormat, + data: body, + url: path, + }) + .then((response) => response.data); + }; +} diff --git a/workspaces/frontend/src/shared/api/__tests__/errorUtils.spec.ts b/workspaces/frontend/src/shared/api/__tests__/errorUtils.spec.ts deleted file mode 100644 index 099905e5..00000000 --- a/workspaces/frontend/src/shared/api/__tests__/errorUtils.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { mockNamespaces } from '~/__mocks__/mockNamespaces'; -import { mockBFFResponse } from '~/__mocks__/utils'; -import { ErrorEnvelopeException } from '~/shared/api/apiUtils'; -import { ErrorEnvelope } from '~/shared/api/backendApiTypes'; -import { handleRestFailures } from '~/shared/api/errorUtils'; -import { NotReadyError } from '~/shared/utilities/useFetchState'; - -describe('handleRestFailures', () => { - it('should successfully return namespaces', async () => { - const result = await handleRestFailures(Promise.resolve(mockBFFResponse(mockNamespaces))); - expect(result.data).toStrictEqual(mockNamespaces); - }); - - it('should handle and throw notebook errors', async () => { - const errorEnvelope: ErrorEnvelope = { - error: { - code: '', - message: '', - }, - }; - const expectedError = new ErrorEnvelopeException(errorEnvelope); - await expect(handleRestFailures(Promise.reject(errorEnvelope))).rejects.toThrow(expectedError); - }); - - it('should handle common state errors ', async () => { - await expect(handleRestFailures(Promise.reject(new NotReadyError('error')))).rejects.toThrow( - 'error', - ); - }); - - it('should handle other errors', async () => { - await expect(handleRestFailures(Promise.reject(new Error('error')))).rejects.toThrow( - 'Error communicating with server', - ); - }); -}); diff --git a/workspaces/frontend/src/shared/api/__tests__/notebookService.spec.ts b/workspaces/frontend/src/shared/api/__tests__/notebookService.spec.ts deleted file mode 100644 index 27a650b5..00000000 --- a/workspaces/frontend/src/shared/api/__tests__/notebookService.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { BFF_API_VERSION } from '~/app/const'; -import { restGET, wrapRequest } from '~/shared/api/apiUtils'; -import { listNamespaces } from '~/shared/api/notebookService'; - -const mockRestResponse = { data: {} }; -const mockRestPromise = Promise.resolve(mockRestResponse); - -jest.mock('~/shared/api/apiUtils', () => ({ - restCREATE: jest.fn(() => mockRestPromise), - restGET: jest.fn(() => mockRestPromise), - restPATCH: jest.fn(() => mockRestPromise), - isNotebookResponse: jest.fn(() => true), - extractNotebookResponse: jest.fn(() => mockRestResponse), - wrapRequest: jest.fn(() => mockRestPromise), -})); - -const wrapRequestMock = jest.mocked(wrapRequest); -const restGETMock = jest.mocked(restGET); -const APIOptionsMock = {}; - -describe('getNamespaces', () => { - it('should call restGET and handleRestFailures to fetch namespaces', async () => { - const response = await listNamespaces(`/api/${BFF_API_VERSION}/namespaces`)(APIOptionsMock); - expect(response).toEqual(mockRestResponse); - expect(restGETMock).toHaveBeenCalledTimes(1); - expect(restGETMock).toHaveBeenCalledWith( - `/api/${BFF_API_VERSION}/namespaces`, - `/namespaces`, - {}, - APIOptionsMock, - ); - expect(wrapRequestMock).toHaveBeenCalledTimes(1); - expect(wrapRequestMock).toHaveBeenCalledWith(mockRestPromise); - }); -}); diff --git a/workspaces/frontend/src/shared/api/apiUtils.ts b/workspaces/frontend/src/shared/api/apiUtils.ts index 09c925e8..196f9ea9 100644 --- a/workspaces/frontend/src/shared/api/apiUtils.ts +++ b/workspaces/frontend/src/shared/api/apiUtils.ts @@ -1,243 +1,34 @@ -import { ErrorEnvelope } from '~/shared/api/backendApiTypes'; -import { handleRestFailures } from '~/shared/api/errorUtils'; -import { APIOptions, ResponseBody } from '~/shared/api/types'; -import { EitherOrNone } from '~/shared/typeHelpers'; -import { AUTH_HEADER, DEV_MODE } from '~/shared/utilities/const'; +import axios from 'axios'; +import { ApiErrorEnvelope } from '~/generated/data-contracts'; +import { ApiCallResult } from '~/shared/api/types'; -export const mergeRequestInit = ( - opts: APIOptions = {}, - specificOpts: RequestInit = {}, -): RequestInit => ({ - ...specificOpts, - ...(opts.signal && { signal: opts.signal }), - headers: { - ...(opts.headers ?? {}), - ...(specificOpts.headers ?? {}), - }, -}); - -type CallRestJSONOptions = { - queryParams?: Record; - parseJSON?: boolean; - directYAML?: boolean; -} & EitherOrNone< - { - fileContents: string; - }, - { - data: Record; - } ->; - -const callRestJSON = ( - host: string, - path: string, - requestInit: RequestInit, - { data, fileContents, queryParams, parseJSON = true, directYAML = false }: CallRestJSONOptions, -): Promise => { - const { method, ...otherOptions } = requestInit; - - const sanitizedQueryParams = queryParams - ? Object.entries(queryParams).reduce((acc, [key, value]) => { - if (value) { - return { ...acc, [key]: value }; - } - - return acc; - }, {}) - : null; - - const searchParams = sanitizedQueryParams - ? new URLSearchParams(sanitizedQueryParams).toString() - : null; - - let requestData: string | undefined; - let contentType: string | undefined; - let formData: FormData | undefined; - if (fileContents) { - if (directYAML) { - requestData = fileContents; - contentType = 'application/yaml'; - } else { - formData = new FormData(); - formData.append( - 'uploadfile', - new Blob([fileContents], { type: 'application/x-yaml' }), - 'uploadedFile.yml', - ); - } - } else if (data) { - // It's OK for contentType and requestData to BOTH be undefined for e.g. a GET request or POST with no body. - contentType = 'application/json;charset=UTF-8'; - requestData = JSON.stringify(data); +function isApiErrorEnvelope(data: unknown): data is ApiErrorEnvelope { + if (typeof data !== 'object' || data === null || !('error' in data)) { + return false; } - return fetch(`${host}${path}${searchParams ? `?${searchParams}` : ''}`, { - ...otherOptions, - headers: { - ...otherOptions.headers, - ...(DEV_MODE && { [AUTH_HEADER]: localStorage.getItem(AUTH_HEADER) }), - ...(contentType && { 'Content-Type': contentType }), - }, - method, - body: formData ?? requestData, - }).then((response) => - response.text().then((fetchedData) => { - if (parseJSON) { - return JSON.parse(fetchedData); - } - return fetchedData; - }), - ); -}; + const { error } = data as { error: unknown }; -export const restGET = ( - host: string, - path: string, - queryParams: Record = {}, - options?: APIOptions, -): Promise => - callRestJSON(host, path, mergeRequestInit(options, { method: 'GET' }), { - queryParams, - parseJSON: options?.parseJSON, - }); - -/** Standard POST */ -export const restCREATE = ( - host: string, - path: string, - data: Record, - queryParams: Record = {}, - options?: APIOptions, -): Promise => - callRestJSON(host, path, mergeRequestInit(options, { method: 'POST' }), { - data, - queryParams, - parseJSON: options?.parseJSON, - }); - -/** POST -- but with file content instead of body data */ -export const restFILE = ( - host: string, - path: string, - fileContents: string, - queryParams: Record = {}, - options?: APIOptions, -): Promise => - callRestJSON(host, path, mergeRequestInit(options, { method: 'POST' }), { - fileContents, - queryParams, - parseJSON: options?.parseJSON, - directYAML: options?.directYAML, - }); - -/** POST -- but no body data -- targets simple endpoints */ -export const restENDPOINT = ( - host: string, - path: string, - queryParams: Record = {}, - options?: APIOptions, -): Promise => - callRestJSON(host, path, mergeRequestInit(options, { method: 'POST' }), { - queryParams, - parseJSON: options?.parseJSON, - }); - -export const restUPDATE = ( - host: string, - path: string, - data: Record, - queryParams: Record = {}, - options?: APIOptions, -): Promise => - callRestJSON(host, path, mergeRequestInit(options, { method: 'PUT' }), { - data, - queryParams, - parseJSON: options?.parseJSON, - }); - -export const restPATCH = ( - host: string, - path: string, - data: Record, - options?: APIOptions, -): Promise => - callRestJSON(host, path, mergeRequestInit(options, { method: 'PATCH' }), { - data, - parseJSON: options?.parseJSON, - }); - -export const restDELETE = ( - host: string, - path: string, - data: Record, - queryParams: Record = {}, - options?: APIOptions, -): Promise => - callRestJSON(host, path, mergeRequestInit(options, { method: 'DELETE' }), { - data, - queryParams, - parseJSON: options?.parseJSON, - }); - -export const isNotebookResponse = (response: unknown): response is ResponseBody => { - if (typeof response === 'object' && response !== null) { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const notebookBody = response as { data?: T }; - return notebookBody.data !== undefined; + if (typeof error !== 'object' || error === null || !('message' in error)) { + return false; } - return false; -}; -export const isErrorEnvelope = (e: unknown): e is ErrorEnvelope => - typeof e === 'object' && - e !== null && - 'error' in e && - typeof (e as Record).error === 'object' && - (e as { error: unknown }).error !== null && - typeof (e as { error: { message: unknown } }).error.message === 'string'; + const { message } = error as { message?: unknown }; -export function extractNotebookResponse(response: unknown): T { - // Check if this is an error envelope first - if (isErrorEnvelope(response)) { - throw new ErrorEnvelopeException(response); - } - if (isNotebookResponse(response)) { - return response.data; - } - throw new Error('Invalid response format'); + return typeof message === 'string'; } -export function extractErrorEnvelope(error: unknown): ErrorEnvelope { - if (isErrorEnvelope(error)) { - return error; - } - - const message = - error instanceof Error ? error.message : typeof error === 'string' ? error : 'Unexpected error'; - - return { - error: { - message, - code: 'UNKNOWN_ERROR', - }, - }; -} - -export async function wrapRequest(promise: Promise, extractData = true): Promise { +export async function safeApiCall(fn: () => Promise): Promise> { try { - const res = await handleRestFailures(promise); - return extractData ? extractNotebookResponse(res) : res; - } catch (error) { - if (error instanceof ErrorEnvelopeException) { - throw error; + const data = await fn(); + return { ok: true, data }; + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + const apiError = error.response?.data; + if (apiError && isApiErrorEnvelope(apiError)) { + return { ok: false, errorEnvelope: apiError }; + } } - throw new ErrorEnvelopeException(extractErrorEnvelope(error)); - } -} - -export class ErrorEnvelopeException extends Error { - constructor(public envelope: ErrorEnvelope) { - super(envelope.error?.message ?? 'Unknown error'); + throw error; } } diff --git a/workspaces/frontend/src/shared/api/backendApiTypes.ts b/workspaces/frontend/src/shared/api/backendApiTypes.ts deleted file mode 100644 index 643c300a..00000000 --- a/workspaces/frontend/src/shared/api/backendApiTypes.ts +++ /dev/null @@ -1,322 +0,0 @@ -export enum WorkspaceServiceStatus { - ServiceStatusHealthy = 'Healthy', - ServiceStatusUnhealthy = 'Unhealthy', -} - -export interface WorkspaceSystemInfo { - version: string; -} - -export interface HealthCheckResponse { - status: WorkspaceServiceStatus; - systemInfo: WorkspaceSystemInfo; -} - -export interface Namespace { - name: string; -} - -export interface WorkspaceImageRef { - url: string; -} - -export interface WorkspacePodConfigValue { - id: string; - displayName: string; - description: string; - labels: WorkspaceOptionLabel[]; - hidden: boolean; - redirect?: WorkspaceOptionRedirect; - clusterMetrics?: WorkspaceKindClusterMetrics; -} - -export interface WorkspaceKindPodConfig { - default: string; - values: WorkspacePodConfigValue[]; -} - -export interface WorkspaceKindPodMetadata { - labels: Record; - annotations: Record; -} - -export interface WorkspacePodVolumeMounts { - home: string; -} - -export interface WorkspaceOptionLabel { - key: string; - value: string; -} - -export enum WorkspaceRedirectMessageLevel { - RedirectMessageLevelInfo = 'Info', - RedirectMessageLevelWarning = 'Warning', - RedirectMessageLevelDanger = 'Danger', -} - -export interface WorkspaceRedirectMessage { - text: string; - level: WorkspaceRedirectMessageLevel; -} - -export interface WorkspaceOptionRedirect { - to: string; - message?: WorkspaceRedirectMessage; -} - -export interface WorkspaceImageConfigValue { - id: string; - displayName: string; - description: string; - labels: WorkspaceOptionLabel[]; - hidden: boolean; - redirect?: WorkspaceOptionRedirect; - clusterMetrics?: WorkspaceKindClusterMetrics; -} - -export interface WorkspaceKindImageConfig { - default: string; - values: WorkspaceImageConfigValue[]; -} - -export interface WorkspaceKindPodTemplateOptions { - imageConfig: WorkspaceKindImageConfig; - podConfig: WorkspaceKindPodConfig; -} - -export interface WorkspaceKindPodTemplate { - podMetadata: WorkspaceKindPodMetadata; - volumeMounts: WorkspacePodVolumeMounts; - options: WorkspaceKindPodTemplateOptions; -} - -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export type WorkspaceKindCreate = string; - -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface WorkspaceKindUpdate {} - -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface WorkspaceKindPatch {} - -export interface WorkspaceKind { - name: string; - displayName: string; - description: string; - deprecated: boolean; - deprecationMessage: string; - hidden: boolean; - icon: WorkspaceImageRef; - logo: WorkspaceImageRef; - clusterMetrics?: WorkspaceKindClusterMetrics; - podTemplate: WorkspaceKindPodTemplate; -} - -export interface WorkspaceKindClusterMetrics { - workspacesCount: number; -} - -export enum WorkspaceState { - WorkspaceStateRunning = 'Running', - WorkspaceStateTerminating = 'Terminating', - WorkspaceStatePaused = 'Paused', - WorkspaceStatePending = 'Pending', - WorkspaceStateError = 'Error', - WorkspaceStateUnknown = 'Unknown', -} - -export interface WorkspaceKindInfo { - name: string; - missing: boolean; - icon: WorkspaceImageRef; - logo: WorkspaceImageRef; -} - -export interface WorkspacePodMetadata { - labels: Record; - annotations: Record; -} - -export interface WorkspacePodVolumeInfo { - pvcName: string; - mountPath: string; - readOnly: boolean; -} - -export interface WorkspacePodSecretInfo { - secretName: string; - mountPath: string; - defaultMode?: number; -} - -export interface WorkspaceOptionInfo { - id: string; - displayName: string; - description: string; - labels: WorkspaceOptionLabel[]; -} - -export interface WorkspaceRedirectStep { - sourceId: string; - targetId: string; - message?: WorkspaceRedirectMessage; -} - -export interface WorkspaceImageConfig { - current: WorkspaceOptionInfo; - desired?: WorkspaceOptionInfo; - redirectChain?: WorkspaceRedirectStep[]; -} - -export interface WorkspacePodConfig { - current: WorkspaceOptionInfo; - desired?: WorkspaceOptionInfo; - redirectChain?: WorkspaceRedirectStep[]; -} - -export interface WorkspacePodTemplateOptions { - imageConfig: WorkspaceImageConfig; - podConfig: WorkspacePodConfig; -} - -export interface WorkspacePodVolumes { - home?: WorkspacePodVolumeInfo; - data: WorkspacePodVolumeInfo[]; - secrets?: WorkspacePodSecretInfo[]; -} - -export interface WorkspacePodTemplate { - podMetadata: WorkspacePodMetadata; - volumes: WorkspacePodVolumes; - options: WorkspacePodTemplateOptions; -} - -export enum WorkspaceProbeResult { - ProbeResultSuccess = 'Success', - ProbeResultFailure = 'Failure', - ProbeResultTimeout = 'Timeout', -} - -export interface WorkspaceLastProbeInfo { - startTimeMs: number; - endTimeMs: number; - result: WorkspaceProbeResult; - message: string; -} - -export interface WorkspaceActivity { - lastActivity: number; - lastUpdate: number; - lastProbe?: WorkspaceLastProbeInfo; -} - -export interface WorkspaceHttpService { - displayName: string; - httpPath: string; -} - -export interface WorkspaceService { - httpService?: WorkspaceHttpService; -} - -export interface WorkspacePodMetadataMutate { - labels: Record; - annotations: Record; -} - -export interface WorkspacePodVolumeMount { - pvcName: string; - mountPath: string; - readOnly?: boolean; -} - -export interface WorkspacePodSecretMount { - secretName: string; - mountPath: string; - defaultMode?: number; -} - -export interface WorkspacePodVolumesMutate { - home?: string; - data?: WorkspacePodVolumeMount[]; - secrets?: WorkspacePodSecretMount[]; -} - -export interface WorkspacePodTemplateOptionsMutate { - imageConfig: string; - podConfig: string; -} - -export interface WorkspacePodTemplateMutate { - podMetadata: WorkspacePodMetadataMutate; - volumes: WorkspacePodVolumesMutate; - options: WorkspacePodTemplateOptionsMutate; -} - -export interface Workspace { - name: string; - namespace: string; - workspaceKind: WorkspaceKindInfo; - deferUpdates: boolean; - paused: boolean; - pausedTime: number; - pendingRestart: boolean; - state: WorkspaceState; - stateMessage: string; - podTemplate: WorkspacePodTemplate; - activity: WorkspaceActivity; - services: WorkspaceService[]; -} - -export interface WorkspaceCreate { - name: string; - kind: string; - paused: boolean; - deferUpdates: boolean; - podTemplate: WorkspacePodTemplateMutate; -} - -// TODO: Update this type when applicable; meanwhile, it inherits from WorkspaceCreate -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface WorkspaceUpdate extends WorkspaceCreate {} - -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface WorkspacePatch {} - -export interface WorkspacePauseState { - paused: boolean; -} - -export enum FieldErrorType { - FieldValueRequired = 'FieldValueRequired', - FieldValueInvalid = 'FieldValueInvalid', - FieldValueNotSupported = 'FieldValueNotSupported', - FieldValueDuplicate = 'FieldValueDuplicate', - FieldValueTooLong = 'FieldValueTooLong', - FieldValueForbidden = 'FieldValueForbidden', - FieldValueNotFound = 'FieldValueNotFound', - FieldValueConflict = 'FieldValueConflict', - FieldValueTooShort = 'FieldValueTooShort', - FieldValueUnknown = 'FieldValueUnknown', -} - -export interface ValidationError { - type: FieldErrorType; - field: string; - message: string; -} - -export interface ErrorCause { - validation_errors?: ValidationError[]; // TODO: backend is not using camelCase for this field -} - -export type HTTPError = { - code: string; - message: string; - cause?: ErrorCause; -}; - -export type ErrorEnvelope = { - error: HTTPError | null; -}; diff --git a/workspaces/frontend/src/shared/api/callTypes.ts b/workspaces/frontend/src/shared/api/callTypes.ts deleted file mode 100644 index 72a8d6bc..00000000 --- a/workspaces/frontend/src/shared/api/callTypes.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - CreateWorkspace, - CreateWorkspaceKind, - DeleteWorkspace, - DeleteWorkspaceKind, - GetHealthCheck, - GetWorkspace, - GetWorkspaceKind, - ListAllWorkspaces, - ListNamespaces, - ListWorkspaceKinds, - ListWorkspaces, - PatchWorkspace, - PatchWorkspaceKind, - PauseWorkspace, - UpdateWorkspace, - UpdateWorkspaceKind, -} from '~/shared/api/notebookApi'; -import { APIOptions } from '~/shared/api/types'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type KubeflowSpecificAPICall = (opts: APIOptions, ...args: any[]) => Promise; -type KubeflowAPICall = (hostPath: string) => ActualCall; - -// Health -export type GetHealthCheckAPI = KubeflowAPICall; - -// Namespace -export type ListNamespacesAPI = KubeflowAPICall; - -// Workspace -export type ListAllWorkspacesAPI = KubeflowAPICall; -export type ListWorkspacesAPI = KubeflowAPICall; -export type CreateWorkspaceAPI = KubeflowAPICall; -export type GetWorkspaceAPI = KubeflowAPICall; -export type UpdateWorkspaceAPI = KubeflowAPICall; -export type PatchWorkspaceAPI = KubeflowAPICall; -export type DeleteWorkspaceAPI = KubeflowAPICall; -export type PauseWorkspaceAPI = KubeflowAPICall; - -// WorkspaceKind -export type ListWorkspaceKindsAPI = KubeflowAPICall; -export type CreateWorkspaceKindAPI = KubeflowAPICall; -export type GetWorkspaceKindAPI = KubeflowAPICall; -export type UpdateWorkspaceKindAPI = KubeflowAPICall; -export type PatchWorkspaceKindAPI = KubeflowAPICall; -export type DeleteWorkspaceKindAPI = KubeflowAPICall; diff --git a/workspaces/frontend/src/shared/api/errorUtils.ts b/workspaces/frontend/src/shared/api/errorUtils.ts deleted file mode 100644 index 9205a8f0..00000000 --- a/workspaces/frontend/src/shared/api/errorUtils.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ErrorEnvelopeException, isErrorEnvelope } from '~/shared/api//apiUtils'; -import { isCommonStateError } from '~/shared/utilities/useFetchState'; - -export const handleRestFailures = (promise: Promise): Promise => - promise.catch((e) => { - if (isErrorEnvelope(e)) { - throw new ErrorEnvelopeException(e); - } - if (isCommonStateError(e)) { - // Common state errors are handled by useFetchState at storage level, let them deal with it - throw e; - } - // eslint-disable-next-line no-console - console.error('Unknown API error', e); - throw new Error('Error communicating with server'); - }); diff --git a/workspaces/frontend/src/shared/api/experimental.ts b/workspaces/frontend/src/shared/api/experimental.ts new file mode 100644 index 00000000..14e7d1c4 --- /dev/null +++ b/workspaces/frontend/src/shared/api/experimental.ts @@ -0,0 +1,23 @@ +/** + * ----------------------------------------------------------------------------- + * Experimental API Extensions + * ----------------------------------------------------------------------------- + * + * This file contains manually implemented API endpoints that are not yet + * available in the official Swagger specification provided by the backend. + * + * The structure, naming, and typing follow the same conventions as the + * `swagger-typescript-api` generated clients (HttpClient, RequestParams, etc.) + * under `src/generated` folder to ensure consistency across the codebase + * and future compatibility. + * + * These endpoints are "experimental" in the sense that they either: + * - Reflect endpoints that exist but are not documented in the Swagger spec. + * - Represent planned or internal APIs not yet formalized by the backend. + * + * Once the backend Swagger specification includes these endpoints, code in this + * file should be removed, and the corresponding generated modules should be + * used instead. + */ + +// NOTE: All available endpoints are already implemented in the generated code. diff --git a/workspaces/frontend/src/shared/api/notebookApi.ts b/workspaces/frontend/src/shared/api/notebookApi.ts index bf2f2bce..ba4efb0b 100644 --- a/workspaces/frontend/src/shared/api/notebookApi.ts +++ b/workspaces/frontend/src/shared/api/notebookApi.ts @@ -1,95 +1,23 @@ -import { - HealthCheckResponse, - Namespace, - Workspace, - WorkspaceCreate, - WorkspaceKind, - WorkspaceKindPatch, - WorkspaceKindUpdate, - WorkspacePatch, - WorkspacePauseState, - WorkspaceUpdate, -} from '~/shared/api/backendApiTypes'; -import { APIOptions, RequestData } from '~/shared/api/types'; +import { Healthcheck } from '~/generated/Healthcheck'; +import { Namespaces } from '~/generated/Namespaces'; +import { Workspacekinds } from '~/generated/Workspacekinds'; +import { Workspaces } from '~/generated/Workspaces'; +import { ApiInstance } from '~/shared/api/types'; -// Health -export type GetHealthCheck = (opts: APIOptions) => Promise; +export interface NotebookApis { + healthCheck: ApiInstance; + namespaces: ApiInstance; + workspaces: ApiInstance; + workspaceKinds: ApiInstance; +} -// Namespace -export type ListNamespaces = (opts: APIOptions) => Promise; +export const notebookApisImpl = (path: string): NotebookApis => { + const commonConfig = { baseURL: path }; -// Workspace -export type ListAllWorkspaces = (opts: APIOptions) => Promise; -export type ListWorkspaces = (opts: APIOptions, namespace: string) => Promise; -export type GetWorkspace = ( - opts: APIOptions, - namespace: string, - workspace: string, -) => Promise; -export type CreateWorkspace = ( - opts: APIOptions, - namespace: string, - data: RequestData, -) => Promise; -export type UpdateWorkspace = ( - opts: APIOptions, - namespace: string, - workspace: string, - data: RequestData, -) => Promise; -export type PatchWorkspace = ( - opts: APIOptions, - namespace: string, - workspace: string, - data: RequestData, -) => Promise; -export type DeleteWorkspace = ( - opts: APIOptions, - namespace: string, - workspace: string, -) => Promise; -export type PauseWorkspace = ( - opts: APIOptions, - namespace: string, - workspace: string, - data: RequestData, -) => Promise; - -// WorkspaceKind -export type ListWorkspaceKinds = (opts: APIOptions) => Promise; -export type GetWorkspaceKind = (opts: APIOptions, kind: string) => Promise; -export type CreateWorkspaceKind = (opts: APIOptions, data: string) => Promise; -export type UpdateWorkspaceKind = ( - opts: APIOptions, - kind: string, - data: RequestData, -) => Promise; -export type PatchWorkspaceKind = ( - opts: APIOptions, - kind: string, - data: RequestData, -) => Promise; -export type DeleteWorkspaceKind = (opts: APIOptions, kind: string) => Promise; - -export type NotebookAPIs = { - // Health - getHealthCheck: GetHealthCheck; - // Namespace - listNamespaces: ListNamespaces; - // Workspace - listAllWorkspaces: ListAllWorkspaces; - listWorkspaces: ListWorkspaces; - getWorkspace: GetWorkspace; - createWorkspace: CreateWorkspace; - updateWorkspace: UpdateWorkspace; - patchWorkspace: PatchWorkspace; - deleteWorkspace: DeleteWorkspace; - pauseWorkspace: PauseWorkspace; - // WorkspaceKind - listWorkspaceKinds: ListWorkspaceKinds; - getWorkspaceKind: GetWorkspaceKind; - createWorkspaceKind: CreateWorkspaceKind; - updateWorkspaceKind: UpdateWorkspaceKind; - patchWorkspaceKind: PatchWorkspaceKind; - deleteWorkspaceKind: DeleteWorkspaceKind; + return { + healthCheck: new Healthcheck(commonConfig), + namespaces: new Namespaces(commonConfig), + workspaces: new Workspaces(commonConfig), + workspaceKinds: new Workspacekinds(commonConfig), + }; }; diff --git a/workspaces/frontend/src/shared/api/notebookService.ts b/workspaces/frontend/src/shared/api/notebookService.ts deleted file mode 100644 index 7df0890d..00000000 --- a/workspaces/frontend/src/shared/api/notebookService.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { - restCREATE, - restDELETE, - restFILE, - restGET, - restPATCH, - restUPDATE, - wrapRequest, -} from '~/shared/api/apiUtils'; -import { - CreateWorkspaceAPI, - CreateWorkspaceKindAPI, - DeleteWorkspaceAPI, - DeleteWorkspaceKindAPI, - GetHealthCheckAPI, - GetWorkspaceAPI, - GetWorkspaceKindAPI, - ListAllWorkspacesAPI, - ListNamespacesAPI, - ListWorkspaceKindsAPI, - ListWorkspacesAPI, - PatchWorkspaceAPI, - PatchWorkspaceKindAPI, - PauseWorkspaceAPI, - UpdateWorkspaceAPI, - UpdateWorkspaceKindAPI, -} from '~/shared/api/callTypes'; - -export const getHealthCheck: GetHealthCheckAPI = (hostPath) => (opts) => - wrapRequest(restGET(hostPath, `/healthcheck`, {}, opts), false); - -export const listNamespaces: ListNamespacesAPI = (hostPath) => (opts) => - wrapRequest(restGET(hostPath, `/namespaces`, {}, opts)); - -export const listAllWorkspaces: ListAllWorkspacesAPI = (hostPath) => (opts) => - wrapRequest(restGET(hostPath, `/workspaces`, {}, opts)); - -export const listWorkspaces: ListWorkspacesAPI = (hostPath) => (opts, namespace) => - wrapRequest(restGET(hostPath, `/workspaces/${namespace}`, {}, opts)); - -export const getWorkspace: GetWorkspaceAPI = (hostPath) => (opts, namespace, workspace) => - wrapRequest(restGET(hostPath, `/workspaces/${namespace}/${workspace}`, {}, opts)); - -export const createWorkspace: CreateWorkspaceAPI = (hostPath) => (opts, namespace, data) => - wrapRequest(restCREATE(hostPath, `/workspaces/${namespace}`, data, {}, opts)); - -export const updateWorkspace: UpdateWorkspaceAPI = - (hostPath) => (opts, namespace, workspace, data) => - wrapRequest(restUPDATE(hostPath, `/workspaces/${namespace}/${workspace}`, data, {}, opts)); - -export const patchWorkspace: PatchWorkspaceAPI = (hostPath) => (opts, namespace, workspace, data) => - wrapRequest(restPATCH(hostPath, `/workspaces/${namespace}/${workspace}`, data, opts)); - -export const deleteWorkspace: DeleteWorkspaceAPI = (hostPath) => (opts, namespace, workspace) => - wrapRequest(restDELETE(hostPath, `/workspaces/${namespace}/${workspace}`, {}, {}, opts), false); - -export const pauseWorkspace: PauseWorkspaceAPI = (hostPath) => (opts, namespace, workspace, data) => - wrapRequest( - restCREATE(hostPath, `/workspaces/${namespace}/${workspace}/actions/pause`, data, {}, opts), - ); - -export const listWorkspaceKinds: ListWorkspaceKindsAPI = (hostPath) => (opts) => - wrapRequest(restGET(hostPath, `/workspacekinds`, {}, opts)); - -export const getWorkspaceKind: GetWorkspaceKindAPI = (hostPath) => (opts, kind) => - wrapRequest(restGET(hostPath, `/workspacekinds/${kind}`, {}, opts)); - -export const createWorkspaceKind: CreateWorkspaceKindAPI = (hostPath) => (opts, data) => - wrapRequest(restFILE(hostPath, `/workspacekinds`, data, {}, opts)); - -export const updateWorkspaceKind: UpdateWorkspaceKindAPI = (hostPath) => (opts, kind, data) => - wrapRequest(restUPDATE(hostPath, `/workspacekinds/${kind}`, data, {}, opts)); - -export const patchWorkspaceKind: PatchWorkspaceKindAPI = (hostPath) => (opts, kind, data) => - wrapRequest(restPATCH(hostPath, `/workspacekinds/${kind}`, data, opts)); - -export const deleteWorkspaceKind: DeleteWorkspaceKindAPI = (hostPath) => (opts, kind) => - wrapRequest(restDELETE(hostPath, `/workspacekinds/${kind}`, {}, {}, opts), false); diff --git a/workspaces/frontend/src/shared/api/types.ts b/workspaces/frontend/src/shared/api/types.ts index 7c2bad1a..6685d177 100644 --- a/workspaces/frontend/src/shared/api/types.ts +++ b/workspaces/frontend/src/shared/api/types.ts @@ -1,9 +1,11 @@ +import { ApiErrorEnvelope } from '~/generated/data-contracts'; +import { ApiConfig, HttpClient } from '~/generated/http-client'; + export type APIOptions = { dryRun?: boolean; signal?: AbortSignal; parseJSON?: boolean; headers?: Record; - directYAML?: boolean; }; export type APIState = { @@ -13,11 +15,14 @@ export type APIState = { api: T; }; -export type ResponseBody = { - data: T; - metadata?: Record; +export type RemoveHttpClient = Omit>; + +export type WithExperimental = TBase & { + experimental: TExperimental; }; -export type RequestData = { - data: T; -}; +export type ApiClass = abstract new (config?: ApiConfig) => object; +export type ApiInstance = RemoveHttpClient>; +export type ApiCallResult = + | { ok: true; data: T } + | { ok: false; errorEnvelope: ApiErrorEnvelope }; diff --git a/workspaces/frontend/src/shared/mock/mockBuilder.ts b/workspaces/frontend/src/shared/mock/mockBuilder.ts index 8b6eeccc..ef4434b2 100644 --- a/workspaces/frontend/src/shared/mock/mockBuilder.ts +++ b/workspaces/frontend/src/shared/mock/mockBuilder.ts @@ -1,31 +1,33 @@ import { - HealthCheckResponse, - Namespace, - Workspace, - WorkspaceKind, - WorkspaceKindInfo, - WorkspacePauseState, - WorkspaceRedirectMessageLevel, - WorkspaceServiceStatus, - WorkspaceState, -} from '~/shared/api/backendApiTypes'; + ActionsWorkspaceActionPause, + HealthCheckHealthCheck, + HealthCheckServiceStatus, + NamespacesNamespace, + WorkspacekindsRedirectMessageLevel, + WorkspacekindsWorkspaceKind, + WorkspacesWorkspace, + WorkspacesWorkspaceKindInfo, + WorkspacesWorkspaceState, +} from '~/generated/data-contracts'; export const buildMockHealthCheckResponse = ( - healthCheckResponse?: Partial, -): HealthCheckResponse => ({ - status: WorkspaceServiceStatus.ServiceStatusHealthy, + healthCheckResponse?: Partial, +): HealthCheckHealthCheck => ({ + status: HealthCheckServiceStatus.ServiceStatusHealthy, systemInfo: { version: '1.0.0' }, ...healthCheckResponse, }); -export const buildMockNamespace = (namespace?: Partial): Namespace => ({ +export const buildMockNamespace = ( + namespace?: Partial, +): NamespacesNamespace => ({ name: 'default', ...namespace, }); export const buildMockWorkspaceKindInfo = ( - workspaceKindInfo?: Partial, -): WorkspaceKindInfo => ({ + workspaceKindInfo?: Partial, +): WorkspacesWorkspaceKindInfo => ({ name: 'jupyterlab', missing: false, icon: { @@ -37,14 +39,16 @@ export const buildMockWorkspaceKindInfo = ( ...workspaceKindInfo, }); -export const buildMockWorkspace = (workspace?: Partial): Workspace => ({ +export const buildMockWorkspace = ( + workspace?: Partial, +): WorkspacesWorkspace => ({ name: 'My First Jupyter Notebook', namespace: 'default', workspaceKind: buildMockWorkspaceKindInfo(), paused: true, deferUpdates: true, pausedTime: new Date(2025, 3, 1).getTime(), - state: WorkspaceState.WorkspaceStateRunning, + state: WorkspacesWorkspaceState.WorkspaceStateRunning, stateMessage: 'Workspace is running', podTemplate: { podMetadata: { @@ -133,7 +137,9 @@ export const buildMockWorkspace = (workspace?: Partial): Workspace => ...workspace, }); -export const buildMockWorkspaceKind = (workspaceKind?: Partial): WorkspaceKind => ({ +export const buildMockWorkspaceKind = ( + workspaceKind?: Partial, +): WorkspacekindsWorkspaceKind => ({ name: 'jupyterlab', displayName: 'JupyterLab Notebook', description: 'A Workspace which runs JupyterLab in a Pod', @@ -182,7 +188,7 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial): to: 'jupyterlab_scipy_190', message: { text: 'This update will change...', - level: WorkspaceRedirectMessageLevel.RedirectMessageLevelInfo, + level: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelInfo, }, }, }, @@ -199,7 +205,7 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial): to: 'jupyterlab_scipy_200', message: { text: 'This update will change...', - level: WorkspaceRedirectMessageLevel.RedirectMessageLevelWarning, + level: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelWarning, }, }, clusterMetrics: { @@ -219,7 +225,7 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial): to: 'jupyterlab_scipy_210', message: { text: 'This update will change...', - level: WorkspaceRedirectMessageLevel.RedirectMessageLevelWarning, + level: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelWarning, }, }, clusterMetrics: { @@ -239,7 +245,7 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial): to: 'jupyterlab_scipy_220', message: { text: 'This update will change...', - level: WorkspaceRedirectMessageLevel.RedirectMessageLevelWarning, + level: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelWarning, }, }, clusterMetrics: { @@ -264,7 +270,7 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial): to: 'small_cpu', message: { text: 'This update will change...', - level: WorkspaceRedirectMessageLevel.RedirectMessageLevelDanger, + level: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelDanger, }, }, clusterMetrics: { @@ -285,7 +291,7 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial): to: 'large_cpu', message: { text: 'This update will change...', - level: WorkspaceRedirectMessageLevel.RedirectMessageLevelDanger, + level: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelDanger, }, }, clusterMetrics: { @@ -299,9 +305,9 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial): ...workspaceKind, }); -export const buildMockPauseStateResponse = ( - pauseState?: Partial, -): WorkspacePauseState => ({ +export const buildMockActionsWorkspaceActionPause = ( + pauseState?: Partial, +): ActionsWorkspaceActionPause => ({ paused: true, ...pauseState, }); @@ -309,9 +315,9 @@ export const buildMockPauseStateResponse = ( export const buildMockWorkspaceList = (args: { count: number; namespace: string; - kind: WorkspaceKindInfo; -}): Workspace[] => { - const states = Object.values(WorkspaceState); + kind: WorkspacesWorkspaceKindInfo; +}): WorkspacesWorkspace[] => { + const states = Object.values(WorkspacesWorkspaceState); const imageConfigs = [ { id: 'jupyterlab_scipy_190', @@ -345,7 +351,7 @@ export const buildMockWorkspaceList = (args: { { id: 'large_cpu', displayName: 'Large CPU' }, ]; - const workspaces: Workspace[] = []; + const workspaces: WorkspacesWorkspace[] = []; for (let i = 1; i <= args.count; i++) { const state = states[(i - 1) % states.length]; const labels = { @@ -368,7 +374,7 @@ export const buildMockWorkspaceList = (args: { workspaceKind: args.kind, state, stateMessage: `Workspace is in ${state} state`, - paused: state === WorkspaceState.WorkspaceStatePaused, + paused: state === WorkspacesWorkspaceState.WorkspaceStatePaused, pendingRestart: booleanValue, podTemplate: { podMetadata: { labels, annotations }, diff --git a/workspaces/frontend/src/shared/mock/mockNotebookApis.ts b/workspaces/frontend/src/shared/mock/mockNotebookApis.ts new file mode 100644 index 00000000..3947b6ee --- /dev/null +++ b/workspaces/frontend/src/shared/mock/mockNotebookApis.ts @@ -0,0 +1,83 @@ +import { ApiErrorEnvelope, FieldErrorType } from '~/generated/data-contracts'; +import { NotebookApis } from '~/shared/api/notebookApi'; +import { + mockAllWorkspaces, + mockedHealthCheckResponse, + mockNamespaces, + mockWorkspace1, + mockWorkspaceKind1, + mockWorkspaceKinds, +} from '~/shared/mock/mockNotebookServiceData'; +import { buildAxiosError, isInvalidYaml } from '~/shared/mock/mockUtils'; + +const delay = (ms: number) => + new Promise((resolve) => { + setTimeout(resolve, ms); + }); + +export const mockNotebookApisImpl = (): NotebookApis => ({ + healthCheck: { + getHealthcheck: async () => mockedHealthCheckResponse, + }, + namespaces: { + listNamespaces: async () => ({ data: mockNamespaces }), + }, + workspaces: { + listAllWorkspaces: async () => ({ data: mockAllWorkspaces }), + listWorkspacesByNamespace: async (namespace) => ({ + data: mockAllWorkspaces.filter((w) => w.namespace === namespace), + }), + getWorkspace: async (namespace, workspace) => ({ + data: mockAllWorkspaces.find((w) => w.name === workspace && w.namespace === namespace)!, + }), + createWorkspace: async () => ({ data: mockWorkspace1 }), + deleteWorkspace: async () => { + await delay(1500); + }, + updateWorkspacePauseState: async (_namespace, _workspaceName, body) => { + await delay(1500); + return { + data: { paused: body.data.paused }, + }; + }, + }, + workspaceKinds: { + listWorkspaceKinds: async () => ({ data: mockWorkspaceKinds }), + getWorkspaceKind: async (kind) => ({ + data: mockWorkspaceKinds.find((w) => w.name === kind)!, + }), + createWorkspaceKind: async (body) => { + if (isInvalidYaml(body)) { + const apiErrorEnvelope: ApiErrorEnvelope = { + error: { + code: 'invalid_yaml', + message: 'Invalid YAML provided', + cause: { + // eslint-disable-next-line camelcase + validation_errors: [ + { + type: FieldErrorType.ErrorTypeRequired, + field: 'spec.spawner.displayName', + message: "Missing required 'spec.spawner.displayName' property", + }, + { + type: FieldErrorType.ErrorTypeInvalid, + field: 'spec.spawner.xyz', + message: "Unknown property 'spec.spawner.xyz'", + }, + { + type: FieldErrorType.ErrorTypeNotSupported, + field: 'spec.spawner.hidden', + message: "Invalid data type for 'spec.spawner.hidden', expected 'boolean'", + }, + ], + }, + }, + }; + + throw buildAxiosError(apiErrorEnvelope); + } + return { data: mockWorkspaceKind1 }; + }, + }, +}); diff --git a/workspaces/frontend/src/shared/mock/mockNotebookService.ts b/workspaces/frontend/src/shared/mock/mockNotebookService.ts deleted file mode 100644 index 78767f03..00000000 --- a/workspaces/frontend/src/shared/mock/mockNotebookService.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { ErrorEnvelopeException } from '~/shared/api/apiUtils'; -import { FieldErrorType } from '~/shared/api/backendApiTypes'; -import { - CreateWorkspaceAPI, - CreateWorkspaceKindAPI, - DeleteWorkspaceAPI, - DeleteWorkspaceKindAPI, - GetHealthCheckAPI, - GetWorkspaceAPI, - GetWorkspaceKindAPI, - ListAllWorkspacesAPI, - ListNamespacesAPI, - ListWorkspaceKindsAPI, - ListWorkspacesAPI, - PatchWorkspaceAPI, - PatchWorkspaceKindAPI, - PauseWorkspaceAPI, - UpdateWorkspaceAPI, - UpdateWorkspaceKindAPI, -} from '~/shared/api/callTypes'; -import { - mockAllWorkspaces, - mockedHealthCheckResponse, - mockNamespaces, - mockWorkspace1, - mockWorkspaceKind1, - mockWorkspaceKinds, -} from '~/shared/mock/mockNotebookServiceData'; -import { isInvalidYaml } from '~/shared/mock/mockUtils'; - -const delay = (ms: number) => - new Promise((resolve) => { - setTimeout(resolve, ms); - }); - -export const mockGetHealthCheck: GetHealthCheckAPI = () => async () => mockedHealthCheckResponse; - -export const mockListNamespaces: ListNamespacesAPI = () => async () => mockNamespaces; - -export const mockListAllWorkspaces: ListAllWorkspacesAPI = () => async () => mockAllWorkspaces; - -export const mockListWorkspaces: ListWorkspacesAPI = () => async (_opts, namespace) => - mockAllWorkspaces.filter((workspace) => workspace.namespace === namespace); - -export const mockGetWorkspace: GetWorkspaceAPI = () => async (_opts, namespace, workspace) => - mockAllWorkspaces.find((w) => w.name === workspace && w.namespace === namespace)!; - -export const mockCreateWorkspace: CreateWorkspaceAPI = () => async () => mockWorkspace1; - -export const mockUpdateWorkspace: UpdateWorkspaceAPI = () => async () => mockWorkspace1; - -export const mockPatchWorkspace: PatchWorkspaceAPI = () => async () => mockWorkspace1; - -export const mockDeleteWorkspace: DeleteWorkspaceAPI = () => async () => { - await delay(1500); -}; - -export const mockPauseWorkspace: PauseWorkspaceAPI = - () => async (_opts, _namespace, _workspace, requestData) => { - await delay(1500); - return { paused: requestData.data.paused }; - }; - -export const mockListWorkspaceKinds: ListWorkspaceKindsAPI = () => async () => mockWorkspaceKinds; - -export const mockGetWorkspaceKind: GetWorkspaceKindAPI = () => async (_opts, kind) => - mockWorkspaceKinds.find((w) => w.name === kind)!; - -export const mockCreateWorkspaceKind: CreateWorkspaceKindAPI = () => async (_opts, data) => { - if (isInvalidYaml(data)) { - throw new ErrorEnvelopeException({ - error: { - code: 'invalid_yaml', - message: 'Invalid YAML provided', - cause: { - // eslint-disable-next-line camelcase - validation_errors: [ - { - type: FieldErrorType.FieldValueRequired, - field: 'spec.spawner.displayName', - message: "Missing required 'spec.spawner.displayName' property", - }, - { - type: FieldErrorType.FieldValueUnknown, - field: 'spec.spawner.xyz', - message: "Unknown property 'spec.spawner.xyz'", - }, - { - type: FieldErrorType.FieldValueNotSupported, - field: 'spec.spawner.hidden', - message: "Invalid data type for 'spec.spawner.hidden', expected 'boolean'", - }, - ], - }, - }, - }); - } - return mockWorkspaceKind1; -}; - -export const mockUpdateWorkspaceKind: UpdateWorkspaceKindAPI = () => async () => mockWorkspaceKind1; - -export const mockPatchWorkspaceKind: PatchWorkspaceKindAPI = () => async () => mockWorkspaceKind1; - -export const mockDeleteWorkspaceKind: DeleteWorkspaceKindAPI = () => async () => { - await delay(1500); -}; diff --git a/workspaces/frontend/src/shared/mock/mockNotebookServiceData.ts b/workspaces/frontend/src/shared/mock/mockNotebookServiceData.ts index fdbf7de0..8a2cf687 100644 --- a/workspaces/frontend/src/shared/mock/mockNotebookServiceData.ts +++ b/workspaces/frontend/src/shared/mock/mockNotebookServiceData.ts @@ -1,9 +1,9 @@ import { - Workspace, - WorkspaceKind, - WorkspaceKindInfo, - WorkspaceState, -} from '~/shared/api/backendApiTypes'; + WorkspacekindsWorkspaceKind, + WorkspacesWorkspace, + WorkspacesWorkspaceKindInfo, + WorkspacesWorkspaceState, +} from '~/generated/data-contracts'; import { buildMockHealthCheckResponse, buildMockNamespace, @@ -24,7 +24,7 @@ export const mockNamespace3 = buildMockNamespace({ name: 'workspace-test-3' }); export const mockNamespaces = [mockNamespace1, mockNamespace2, mockNamespace3]; // WorkspaceKind -export const mockWorkspaceKind1: WorkspaceKind = buildMockWorkspaceKind({ +export const mockWorkspaceKind1: WorkspacekindsWorkspaceKind = buildMockWorkspaceKind({ name: 'jupyterlab1', displayName: 'JupyterLab Notebook 1', clusterMetrics: { @@ -32,7 +32,7 @@ export const mockWorkspaceKind1: WorkspaceKind = buildMockWorkspaceKind({ }, }); -export const mockWorkspaceKind2: WorkspaceKind = buildMockWorkspaceKind({ +export const mockWorkspaceKind2: WorkspacekindsWorkspaceKind = buildMockWorkspaceKind({ name: 'jupyterlab2', displayName: 'JupyterLab Notebook 2', clusterMetrics: { @@ -40,7 +40,7 @@ export const mockWorkspaceKind2: WorkspaceKind = buildMockWorkspaceKind({ }, }); -export const mockWorkspaceKind3: WorkspaceKind = buildMockWorkspaceKind({ +export const mockWorkspaceKind3: WorkspacekindsWorkspaceKind = buildMockWorkspaceKind({ name: 'jupyterlab3', displayName: 'JupyterLab Notebook 3', clusterMetrics: { @@ -50,25 +50,25 @@ export const mockWorkspaceKind3: WorkspaceKind = buildMockWorkspaceKind({ export const mockWorkspaceKinds = [mockWorkspaceKind1, mockWorkspaceKind2, mockWorkspaceKind3]; -export const mockWorkspaceKindInfo1: WorkspaceKindInfo = buildMockWorkspaceKindInfo({ +export const mockWorkspaceKindInfo1: WorkspacesWorkspaceKindInfo = buildMockWorkspaceKindInfo({ name: mockWorkspaceKind1.name, }); -export const mockWorkspaceKindInfo2: WorkspaceKindInfo = buildMockWorkspaceKindInfo({ +export const mockWorkspaceKindInfo2: WorkspacesWorkspaceKindInfo = buildMockWorkspaceKindInfo({ name: mockWorkspaceKind2.name, }); // Workspace -export const mockWorkspace1: Workspace = buildMockWorkspace({ +export const mockWorkspace1: WorkspacesWorkspace = buildMockWorkspace({ workspaceKind: mockWorkspaceKindInfo1, namespace: mockNamespace1.name, }); -export const mockWorkspace2: Workspace = buildMockWorkspace({ +export const mockWorkspace2: WorkspacesWorkspace = buildMockWorkspace({ name: 'My Second Jupyter Notebook', workspaceKind: mockWorkspaceKindInfo1, namespace: mockNamespace2.name, - state: WorkspaceState.WorkspaceStatePaused, + state: WorkspacesWorkspaceState.WorkspaceStatePaused, paused: false, deferUpdates: false, activity: { @@ -133,11 +133,11 @@ export const mockWorkspace2: Workspace = buildMockWorkspace({ }, }); -export const mockWorkspace3: Workspace = buildMockWorkspace({ +export const mockWorkspace3: WorkspacesWorkspace = buildMockWorkspace({ name: 'My Third Jupyter Notebook', namespace: mockNamespace1.name, workspaceKind: mockWorkspaceKindInfo1, - state: WorkspaceState.WorkspaceStateRunning, + state: WorkspacesWorkspaceState.WorkspaceStateRunning, pendingRestart: true, activity: { lastActivity: new Date(2025, 2, 15).getTime(), @@ -145,17 +145,17 @@ export const mockWorkspace3: Workspace = buildMockWorkspace({ }, }); -export const mockWorkspace4 = buildMockWorkspace({ +export const mockWorkspace4: WorkspacesWorkspace = buildMockWorkspace({ name: 'My Fourth Jupyter Notebook', namespace: mockNamespace2.name, - state: WorkspaceState.WorkspaceStateError, + state: WorkspacesWorkspaceState.WorkspaceStateError, workspaceKind: mockWorkspaceKindInfo2, }); -export const mockWorkspace5 = buildMockWorkspace({ +export const mockWorkspace5: WorkspacesWorkspace = buildMockWorkspace({ name: 'My Fifth Jupyter Notebook', namespace: mockNamespace2.name, - state: WorkspaceState.WorkspaceStateTerminating, + state: WorkspacesWorkspaceState.WorkspaceStateTerminating, workspaceKind: mockWorkspaceKindInfo2, }); diff --git a/workspaces/frontend/src/shared/mock/mockUtils.ts b/workspaces/frontend/src/shared/mock/mockUtils.ts index c4af8e1a..789ab2f6 100644 --- a/workspaces/frontend/src/shared/mock/mockUtils.ts +++ b/workspaces/frontend/src/shared/mock/mockUtils.ts @@ -1,7 +1,38 @@ +import { AxiosError, AxiosHeaders, AxiosResponse } from 'axios'; import yaml from 'js-yaml'; +import { ApiErrorEnvelope } from '~/generated/data-contracts'; // For testing purposes, a YAML string is considered invalid if it contains a specific pattern in the metadata name. export function isInvalidYaml(yamlString: string): boolean { const parsed = yaml.load(yamlString) as { metadata?: { name?: string } }; return parsed.metadata?.name?.includes('-invalid') ?? false; } + +export function buildAxiosError( + envelope: ApiErrorEnvelope, + status = 400, + configOverrides: Partial = {}, +): AxiosError { + const config = { + url: '', + method: 'GET', + headers: new AxiosHeaders(), + ...configOverrides, + }; + + const response: AxiosResponse = { + data: envelope, + status, + statusText: 'Bad Request', + headers: {}, + config, + }; + + return new AxiosError( + envelope.error.message, + envelope.error.code, + config, + undefined, + response, + ); +} diff --git a/workspaces/frontend/src/shared/utilities/WorkspaceUtils.ts b/workspaces/frontend/src/shared/utilities/WorkspaceUtils.ts index f1ee0bc9..8da8f10a 100644 --- a/workspaces/frontend/src/shared/utilities/WorkspaceUtils.ts +++ b/workspaces/frontend/src/shared/utilities/WorkspaceUtils.ts @@ -1,4 +1,8 @@ -import { Workspace, WorkspaceState, WorkspaceOptionLabel } from '~/shared/api/backendApiTypes'; +import { + WorkspacesOptionLabel, + WorkspacesWorkspace, + WorkspacesWorkspaceState, +} from '~/generated/data-contracts'; import { CPU_UNITS, MEMORY_UNITS_FOR_PARSING, @@ -28,7 +32,7 @@ export const parseResourceValue = ( }; export const extractResourceValue = ( - workspace: Workspace, + workspace: WorkspacesWorkspace, resourceType: ResourceType, ): string | undefined => workspace.podTemplate.options.podConfig.current.labels.find((label) => label.key === resourceType) @@ -48,35 +52,40 @@ export const formatResourceValue = (v: string | undefined, resourceType?: Resour }; export const formatResourceFromWorkspace = ( - workspace: Workspace, + workspace: WorkspacesWorkspace, resourceType: ResourceType, ): string => formatResourceValue(extractResourceValue(workspace, resourceType), resourceType); -export const formatWorkspaceIdleState = (workspace: Workspace): string => - workspace.state !== WorkspaceState.WorkspaceStateRunning ? YesNoValue.Yes : YesNoValue.No; +export const formatWorkspaceIdleState = (workspace: WorkspacesWorkspace): string => + workspace.state !== WorkspacesWorkspaceState.WorkspaceStateRunning + ? YesNoValue.Yes + : YesNoValue.No; -export const isWorkspaceWithGpu = (workspace: Workspace): boolean => +export const isWorkspaceWithGpu = (workspace: WorkspacesWorkspace): boolean => workspace.podTemplate.options.podConfig.current.labels.some((label) => label.key === 'gpu'); -export const isWorkspaceIdle = (workspace: Workspace): boolean => - workspace.state !== WorkspaceState.WorkspaceStateRunning; +export const isWorkspaceIdle = (workspace: WorkspacesWorkspace): boolean => + workspace.state !== WorkspacesWorkspaceState.WorkspaceStateRunning; -export const filterWorkspacesWithGpu = (workspaces: Workspace[]): Workspace[] => +export const filterWorkspacesWithGpu = (workspaces: WorkspacesWorkspace[]): WorkspacesWorkspace[] => workspaces.filter(isWorkspaceWithGpu); -export const filterIdleWorkspaces = (workspaces: Workspace[]): Workspace[] => +export const filterIdleWorkspaces = (workspaces: WorkspacesWorkspace[]): WorkspacesWorkspace[] => workspaces.filter(isWorkspaceIdle); -export const filterRunningWorkspaces = (workspaces: Workspace[]): Workspace[] => - workspaces.filter((workspace) => workspace.state === WorkspaceState.WorkspaceStateRunning); +export const filterRunningWorkspaces = (workspaces: WorkspacesWorkspace[]): WorkspacesWorkspace[] => + workspaces.filter( + (workspace) => workspace.state === WorkspacesWorkspaceState.WorkspaceStateRunning, + ); -export const filterIdleWorkspacesWithGpu = (workspaces: Workspace[]): Workspace[] => - filterIdleWorkspaces(filterWorkspacesWithGpu(workspaces)); +export const filterIdleWorkspacesWithGpu = ( + workspaces: WorkspacesWorkspace[], +): WorkspacesWorkspace[] => filterIdleWorkspaces(filterWorkspacesWithGpu(workspaces)); -export type WorkspaceGpuCountRecord = { workspaces: Workspace[]; gpuCount: number }; +export type WorkspaceGpuCountRecord = { workspaces: WorkspacesWorkspace[]; gpuCount: number }; export const groupWorkspacesByNamespaceAndGpu = ( - workspaces: Workspace[], + workspaces: WorkspacesWorkspace[], order: 'ASC' | 'DESC' = 'DESC', ): Record => { const grouped: Record = {}; @@ -97,7 +106,7 @@ export const groupWorkspacesByNamespaceAndGpu = ( ); }; -export const countGpusFromWorkspaces = (workspaces: Workspace[]): number => +export const countGpusFromWorkspaces = (workspaces: WorkspacesWorkspace[]): number => workspaces.reduce((total, workspace) => { const [gpuValue] = splitValueUnit(extractResourceValue(workspace, 'gpu') || '0', OTHER); return total + (gpuValue ?? 0); @@ -124,7 +133,7 @@ export const formatLabelKey = (key: string): string => { export const isPackageLabel = (key: string): boolean => key.endsWith('Version'); // Extract package labels from workspace image config -export const extractPackageLabels = (workspace: Workspace): WorkspaceOptionLabel[] => +export const extractPackageLabels = (workspace: WorkspacesWorkspace): WorkspacesOptionLabel[] => workspace.podTemplate.options.imageConfig.current.labels.filter((label) => isPackageLabel(label.key), ); diff --git a/workspaces/frontend/src/shared/utilities/const.ts b/workspaces/frontend/src/shared/utilities/const.ts index 6b3c5d9b..1ddc2224 100644 --- a/workspaces/frontend/src/shared/utilities/const.ts +++ b/workspaces/frontend/src/shared/utilities/const.ts @@ -1,4 +1,8 @@ -const DEV_MODE = process.env.APP_ENV === 'development'; -const AUTH_HEADER = process.env.AUTH_HEADER || 'kubeflow-userid'; +export const DEV_MODE = process.env.APP_ENV === 'development'; +export const AUTH_HEADER = process.env.AUTH_HEADER || 'kubeflow-userid'; -export { DEV_MODE, AUTH_HEADER }; +export const CONTENT_TYPE_KEY = 'Content-Type'; + +export enum ContentType { + YAML = 'application/yaml', +} From 673989fcea20a9c77b7d2fdedd9118c436697c43 Mon Sep 17 00:00:00 2001 From: Charles Thao Date: Tue, 5 Aug 2025 14:25:53 -0400 Subject: [PATCH 53/68] feat: Make Frontend Basepath Configurable via APP_PREFIX env variable (#517) * Make Frontend Basepath Configurable via APP_PREFIX env variable Signed-off-by: Charles Thao * Fix Cypress tests Signed-off-by: Charles Thao --------- Signed-off-by: Charles Thao --- workspaces/frontend/config/webpack.common.js | 8 ++++++-- workspaces/frontend/config/webpack.dev.js | 7 ++++++- workspaces/frontend/package.json | 2 +- .../frontend/src/__tests__/cypress/cypress.config.ts | 3 ++- .../cypress/cypress/tests/mocked/application.cy.ts | 2 +- .../mocked/workspaces/WorkspaceDetailsActivity.cy.ts | 2 +- workspaces/frontend/src/app/NavSidebar.tsx | 3 ++- workspaces/frontend/src/index.html | 6 +++--- workspaces/frontend/src/index.tsx | 3 ++- 9 files changed, 24 insertions(+), 12 deletions(-) diff --git a/workspaces/frontend/config/webpack.common.js b/workspaces/frontend/config/webpack.common.js index cfa03d36..bbbba485 100644 --- a/workspaces/frontend/config/webpack.common.js +++ b/workspaces/frontend/config/webpack.common.js @@ -6,7 +6,8 @@ const CopyPlugin = require('copy-webpack-plugin'); const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); const Dotenv = require('dotenv-webpack'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); -const ASSET_PATH = process.env.ASSET_PATH || '/'; +const { EnvironmentPlugin } = require('webpack'); +const APP_PREFIX = process.env.APP_PREFIX || '/workspaces'; const IMAGES_DIRNAME = 'images'; const relativeDir = path.resolve(__dirname, '..'); module.exports = (env) => { @@ -153,7 +154,7 @@ module.exports = (env) => { output: { filename: '[name].bundle.js', path: path.resolve(relativeDir, 'dist'), - publicPath: ASSET_PATH, + publicPath: APP_PREFIX, }, plugins: [ new HtmlWebpackPlugin({ @@ -167,6 +168,9 @@ module.exports = (env) => { patterns: [{ from: './src/images', to: 'images' }], }), new ForkTsCheckerWebpackPlugin(), + new EnvironmentPlugin({ + APP_PREFIX: process.env.APP_PREFIX || '/workspaces', + }), ], resolve: { extensions: ['.js', '.ts', '.tsx', '.jsx'], diff --git a/workspaces/frontend/config/webpack.dev.js b/workspaces/frontend/config/webpack.dev.js index 9283bfc1..0fb6bdee 100644 --- a/workspaces/frontend/config/webpack.dev.js +++ b/workspaces/frontend/config/webpack.dev.js @@ -12,6 +12,7 @@ const PROXY_PORT = process.env.PROXY_PORT || '4000'; const PROXY_PROTOCOL = process.env.PROXY_PROTOCOL || 'http:'; const MOCK_API_ENABLED = process.env.MOCK_API_ENABLED || 'false'; const relativeDir = path.resolve(__dirname, '..'); +const APP_PREFIX = process.env.APP_PREFIX || '/workspaces'; module.exports = merge(common('development'), { mode: 'development', @@ -19,9 +20,13 @@ module.exports = merge(common('development'), { devServer: { host: HOST, port: PORT, - historyApiFallback: true, + historyApiFallback: { + index: APP_PREFIX + '/index.html', + }, + open: [APP_PREFIX], static: { directory: path.resolve(relativeDir, 'dist'), + publicPath: APP_PREFIX, }, client: { overlay: true, diff --git a/workspaces/frontend/package.json b/workspaces/frontend/package.json index a8e29468..0855b3e8 100644 --- a/workspaces/frontend/package.json +++ b/workspaces/frontend/package.json @@ -35,7 +35,7 @@ "cypress:open:mock": "CY_MOCK=1 CY_WS_PORT=9002 npm run cypress:open -- ", "cypress:run": "cypress run -b chrome --project src/__tests__/cypress", "cypress:run:mock": "CY_MOCK=1 npm run cypress:run -- ", - "cypress:server:build": "POLL_INTERVAL=9999999 FAST_POLL_INTERVAL=9999999 npm run build", + "cypress:server:build": "POLL_INTERVAL=9999999 FAST_POLL_INTERVAL=9999999 APP_PREFIX=/ webpack --config ./config/webpack.prod.js", "cypress:server": "serve ./dist -p 9001 -s -L", "prettier": "prettier --ignore-path .gitignore --write \"**/*{.ts,.tsx,.js,.cjs,.jsx,.css,.json}\"", "prettier:check": "prettier --ignore-path .gitignore --check \"**/*{.ts,.tsx,.js,.cjs,.jsx,.css,.json}\"", diff --git a/workspaces/frontend/src/__tests__/cypress/cypress.config.ts b/workspaces/frontend/src/__tests__/cypress/cypress.config.ts index 349ff312..3708b3d9 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress.config.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress.config.ts @@ -41,6 +41,7 @@ export default defineConfig({ env: { MOCK: !!env.CY_MOCK, coverage: !!env.CY_COVERAGE, + APP_PREFIX: env.APP_PREFIX || '/workspaces', codeCoverage: { exclude: [path.resolve(__dirname, '../../third_party/**')], }, @@ -48,7 +49,7 @@ export default defineConfig({ }, defaultCommandTimeout: 10000, e2e: { - baseUrl: env.CY_MOCK ? BASE_URL : 'http://localhost:9000', + baseUrl: env.CY_MOCK ? BASE_URL || 'http://localhost:9001' : 'http://localhost:9000', specPattern: env.CY_MOCK ? `cypress/tests/mocked/**/*.cy.ts` : `cypress/tests/e2e/**/*.cy.ts`, experimentalInteractiveRunEvents: true, setupNodeEvents(on, config) { diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts index ce5a4ba3..4bbfcdc2 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts @@ -13,7 +13,7 @@ describe('Application', () => { cy.intercept('GET', `/api/v1/workspaces/${mockNamespaces[0].name}`, { body: mockBFFResponse({ mockWorkspace1 }), }).as('getWorkspaces'); - cy.visit('/'); + cy.visit('/workspaces'); cy.wait('@getNamespaces'); cy.wait('@getWorkspaces'); }); diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/WorkspaceDetailsActivity.cy.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/WorkspaceDetailsActivity.cy.ts index 528ca287..73648410 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/WorkspaceDetailsActivity.cy.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/WorkspaceDetailsActivity.cy.ts @@ -6,7 +6,7 @@ describe('WorkspaceDetailsActivity Component', () => { cy.intercept('GET', 'api/v1/workspaces', { body: mockBFFResponse(mockWorkspaces), }).as('getWorkspaces'); - cy.visit('/'); + cy.visit('/workspaces'); }); // This tests depends on the mocked workspaces data at home page, needs revisit once workspace data fetched from BE diff --git a/workspaces/frontend/src/app/NavSidebar.tsx b/workspaces/frontend/src/app/NavSidebar.tsx index bad77bd8..d4f5e272 100644 --- a/workspaces/frontend/src/app/NavSidebar.tsx +++ b/workspaces/frontend/src/app/NavSidebar.tsx @@ -12,6 +12,7 @@ import { useTypedLocation } from '~/app/routerHelper'; import { useNavData, isNavDataGroup, NavDataHref, NavDataGroup } from './AppRoutes'; import { isMUITheme, LOGO_LIGHT } from './const'; +const APP_PREFIX = process.env.APP_PREFIX || '/workspaces'; const NavHref: React.FC<{ item: NavDataHref }> = ({ item }) => { const location = useTypedLocation(); @@ -61,7 +62,7 @@ const NavSidebar: React.FC = () => { diff --git a/workspaces/frontend/src/index.html b/workspaces/frontend/src/index.html index d6b28baa..e460f04e 100644 --- a/workspaces/frontend/src/index.html +++ b/workspaces/frontend/src/index.html @@ -9,8 +9,8 @@ Kubeflow Workspaces - - + + @@ -18,4 +18,4 @@
- + \ No newline at end of file diff --git a/workspaces/frontend/src/index.tsx b/workspaces/frontend/src/index.tsx index 7bb65e46..10ba5a76 100644 --- a/workspaces/frontend/src/index.tsx +++ b/workspaces/frontend/src/index.tsx @@ -6,10 +6,11 @@ import App from './app/App'; const theme = createTheme({ cssVariables: true }); const root = ReactDOM.createRoot(document.getElementById('root')!); +const APP_PREFIX = process.env.APP_PREFIX || '/workspaces'; root.render( - + From 053f2781ae453e4da24a9f63196c1f21ee804238 Mon Sep 17 00:00:00 2001 From: Charles Thao Date: Tue, 5 Aug 2025 15:10:54 -0400 Subject: [PATCH 54/68] feat: Conditionally render masthead on Production and Standalone modes (#516) Signed-off-by: Charles Thao --- workspaces/frontend/config/webpack.prod.js | 6 ++++++ workspaces/frontend/package.json | 2 +- workspaces/frontend/src/app/App.tsx | 20 ++++++++++++------- .../frontend/src/shared/style/MUI-theme.scss | 6 ++++++ 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/workspaces/frontend/config/webpack.prod.js b/workspaces/frontend/config/webpack.prod.js index eb6f453d..b0c839ea 100644 --- a/workspaces/frontend/config/webpack.prod.js +++ b/workspaces/frontend/config/webpack.prod.js @@ -2,11 +2,14 @@ const { merge } = require('webpack-merge'); const common = require('./webpack.common.js'); +const { EnvironmentPlugin } = require('webpack'); const { stylePaths } = require('./stylePaths'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const TerserJSPlugin = require('terser-webpack-plugin'); +const PRODUCTION = process.env.PRODUCTION || 'false'; + module.exports = merge(common('production'), { mode: 'production', devtool: 'source-map', @@ -25,6 +28,9 @@ module.exports = merge(common('production'), { filename: '[name].css', chunkFilename: '[name].bundle.css', }), + new EnvironmentPlugin({ + PRODUCTION, + }), ], module: { rules: [ diff --git a/workspaces/frontend/package.json b/workspaces/frontend/package.json index 0855b3e8..e6419766 100644 --- a/workspaces/frontend/package.json +++ b/workspaces/frontend/package.json @@ -18,7 +18,7 @@ "build:bundle-profile": "webpack --config ./config/webpack.prod.js --profile --json > ./bundle.stats.json", "build:bundle-analyze": "webpack-bundle-analyzer ./bundle.stats.json", "build:clean": "rimraf ./dist", - "build:prod": "webpack --config ./config/webpack.prod.js", + "build:prod": "cross-env PRODUCTION=true webpack --config ./config/webpack.prod.js", "generate:api": "./scripts/generate-api.sh && npm run prettier", "start:dev": "cross-env STYLE_THEME=$npm_config_theme webpack serve --hot --color --config ./config/webpack.dev.js", "start:dev:mock": "cross-env MOCK_API_ENABLED=true STYLE_THEME=$npm_config_theme npm run start:dev", diff --git a/workspaces/frontend/src/app/App.tsx b/workspaces/frontend/src/app/App.tsx index 239beb13..df321091 100644 --- a/workspaces/frontend/src/app/App.tsx +++ b/workspaces/frontend/src/app/App.tsx @@ -12,7 +12,11 @@ import { MastheadMain, MastheadToggle, } from '@patternfly/react-core/dist/esm/components/Masthead'; -import { Page, PageToggleButton } from '@patternfly/react-core/dist/esm/components/Page'; +import { + Page, + PageSidebar, + PageToggleButton, +} from '@patternfly/react-core/dist/esm/components/Page'; import { Title } from '@patternfly/react-core/dist/esm/components/Title'; import { BarsIcon } from '@patternfly/react-icons/dist/esm/icons/bars-icon'; import ErrorBoundary from '~/app/error/ErrorBoundary'; @@ -25,6 +29,8 @@ import { NotebookContextProvider } from './context/NotebookContext'; import { isMUITheme, Theme } from './const'; import { BrowserStorageContextProvider } from './context/BrowserStorageContext'; +const isStandalone = process.env.PRODUCTION !== 'true'; + const App: React.FC = () => { useEffect(() => { // Apply the theme based on the value of STYLE_THEME @@ -43,14 +49,12 @@ const App: React.FC = () => { - {!isMUITheme() ? ( + {!isMUITheme() && ( - ) : ( - '' )} @@ -63,6 +67,7 @@ const App: React.FC = () => { ); + const sidebar = ; return ( @@ -71,10 +76,11 @@ const App: React.FC = () => { } + sidebar={isStandalone ? : sidebar} + isManagedSidebar={isStandalone} + className={isStandalone ? '' : 'embedded'} > diff --git a/workspaces/frontend/src/shared/style/MUI-theme.scss b/workspaces/frontend/src/shared/style/MUI-theme.scss index 9d98e53c..aacf6428 100644 --- a/workspaces/frontend/src/shared/style/MUI-theme.scss +++ b/workspaces/frontend/src/shared/style/MUI-theme.scss @@ -887,6 +887,12 @@ /* Move content area below the app bar */ } +.mui-theme .pf-v6-c-page.embedded { + .pf-v6-c-page__main-container { + margin: 0px; + } +} + /* Hide Masthead Toggle by default */ .mui-theme .pf-v6-c-masthead__toggle { display: none; From d2b97e78eb82610f42c5f0eeb1288e8c56ff02ef Mon Sep 17 00:00:00 2001 From: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> Date: Thu, 7 Aug 2025 15:20:56 -0300 Subject: [PATCH 55/68] ci(ws): run client generator on frontend PR check (#519) Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> --- .github/workflows/ws-frontend-test.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ws-frontend-test.yml b/.github/workflows/ws-frontend-test.yml index 5e3b5bec..e34b7cfb 100644 --- a/.github/workflows/ws-frontend-test.yml +++ b/.github/workflows/ws-frontend-test.yml @@ -22,6 +22,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Node.js uses: actions/setup-node@v3 @@ -36,6 +38,10 @@ jobs: working-directory: workspaces/frontend run: npm run build:clean + - name: Regenerate API layer code + working-directory: workspaces/frontend + run: npm run generate:api + - name: Build working-directory: workspaces/frontend run: npm run build From fd2dc790ffdcb14e71bc1e4c5d5485984af99ed7 Mon Sep 17 00:00:00 2001 From: Charles Thao Date: Thu, 7 Aug 2025 15:58:56 -0400 Subject: [PATCH 56/68] feat: Refactor APP_PREFIX to const.ts (#523) Signed-off-by: Charles Thao --- workspaces/frontend/src/app/NavSidebar.tsx | 3 +-- workspaces/frontend/src/app/const.ts | 2 ++ workspaces/frontend/src/app/context/NotebookContext.tsx | 6 ++++-- workspaces/frontend/src/index.tsx | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/workspaces/frontend/src/app/NavSidebar.tsx b/workspaces/frontend/src/app/NavSidebar.tsx index d4f5e272..3fd90631 100644 --- a/workspaces/frontend/src/app/NavSidebar.tsx +++ b/workspaces/frontend/src/app/NavSidebar.tsx @@ -10,9 +10,8 @@ import { import { PageSidebar, PageSidebarBody } from '@patternfly/react-core/dist/esm/components/Page'; import { useTypedLocation } from '~/app/routerHelper'; import { useNavData, isNavDataGroup, NavDataHref, NavDataGroup } from './AppRoutes'; -import { isMUITheme, LOGO_LIGHT } from './const'; +import { APP_PREFIX, isMUITheme, LOGO_LIGHT } from './const'; -const APP_PREFIX = process.env.APP_PREFIX || '/workspaces'; const NavHref: React.FC<{ item: NavDataHref }> = ({ item }) => { const location = useTypedLocation(); diff --git a/workspaces/frontend/src/app/const.ts b/workspaces/frontend/src/app/const.ts index d0661b7f..04f215bc 100644 --- a/workspaces/frontend/src/app/const.ts +++ b/workspaces/frontend/src/app/const.ts @@ -12,3 +12,5 @@ const STYLE_THEME = process.env.STYLE_THEME || Theme.MUI; export const LOGO_LIGHT = process.env.LOGO || 'logo.svg'; export const DEFAULT_POLLING_RATE_MS = 10000; + +export const APP_PREFIX = process.env.APP_PREFIX || '/workspaces'; diff --git a/workspaces/frontend/src/app/context/NotebookContext.tsx b/workspaces/frontend/src/app/context/NotebookContext.tsx index 2f660c72..5cd35053 100644 --- a/workspaces/frontend/src/app/context/NotebookContext.tsx +++ b/workspaces/frontend/src/app/context/NotebookContext.tsx @@ -1,5 +1,5 @@ import React, { ReactNode, useMemo } from 'react'; -import { BFF_API_VERSION } from '~/app/const'; +import { APP_PREFIX, BFF_API_VERSION } from '~/app/const'; import EnsureAPIAvailability from '~/app/EnsureAPIAvailability'; import useNotebookAPIState, { NotebookAPIState } from './useNotebookAPIState'; @@ -19,7 +19,9 @@ interface NotebookContextProviderProps { } export const NotebookContextProvider: React.FC = ({ children }) => { - const hostPath = `/api/${BFF_API_VERSION}`; + // Remove trailing slash from APP_PREFIX to avoid double slashes + const cleanPrefix = APP_PREFIX.replace(/\/$/, ''); + const hostPath = `${cleanPrefix}/api/${BFF_API_VERSION}`; const [apiState, refreshAPIState] = useNotebookAPIState(hostPath); diff --git a/workspaces/frontend/src/index.tsx b/workspaces/frontend/src/index.tsx index 10ba5a76..3ad481ce 100644 --- a/workspaces/frontend/src/index.tsx +++ b/workspaces/frontend/src/index.tsx @@ -3,10 +3,10 @@ import ReactDOM from 'react-dom/client'; import { BrowserRouter as Router } from 'react-router-dom'; import { ThemeProvider, createTheme } from '@mui/material/styles'; import App from './app/App'; +import { APP_PREFIX } from './app/const'; const theme = createTheme({ cssVariables: true }); const root = ReactDOM.createRoot(document.getElementById('root')!); -const APP_PREFIX = process.env.APP_PREFIX || '/workspaces'; root.render( From a932c0fc4c473f91a122c827529bc733e1f76a70 Mon Sep 17 00:00:00 2001 From: Paulo Rego <832830+paulovmr@users.noreply.github.com> Date: Thu, 7 Aug 2025 16:59:55 -0300 Subject: [PATCH 57/68] fix(ws): fixed filter by labels during workspace creation (#524) Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> --- .../Form/image/WorkspaceFormImageList.tsx | 21 +------- .../image/WorkspaceFormImageSelection.tsx | 12 ++--- .../Form/labelFilter/FilterByLabels.tsx | 52 +++++++++++++------ .../podConfig/WorkspaceFormPodConfigList.tsx | 22 ++------ .../WorkspaceFormPodConfigSelection.tsx | 13 +++-- 5 files changed, 53 insertions(+), 67 deletions(-) diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageList.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageList.tsx index e36d1d5f..ddf5c4aa 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageList.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageList.tsx @@ -22,34 +22,18 @@ type FilterableDataFieldKeys = FilterableDataFieldKey; type WorkspaceFormImageListProps = { images: WorkspacekindsImageConfigValue[]; - selectedLabels: Map>; selectedImage: WorkspacekindsImageConfigValue | undefined; onSelect: (workspaceImage: WorkspacekindsImageConfigValue | undefined) => void; }; export const WorkspaceFormImageList: React.FunctionComponent = ({ images, - selectedLabels, selectedImage, onSelect, }) => { const [filters, setFilters] = useState([]); const filterRef = useRef(null); - const getFilteredWorkspaceImagesByLabels = useCallback( - (unfilteredImages: WorkspacekindsImageConfigValue[]) => - unfilteredImages.filter((image) => - image.labels.reduce((accumulator, label) => { - if (selectedLabels.has(label.key)) { - const labelValues: Set | undefined = selectedLabels.get(label.key); - return accumulator && labelValues !== undefined && labelValues.has(label.value); - } - return accumulator; - }, true), - ), - [selectedLabels], - ); - const clearAllFilters = useCallback(() => { filterRef.current?.clearAll(); }, []); @@ -67,7 +51,7 @@ export const WorkspaceFormImageList: React.FunctionComponent { + return result.filter((image) => { if (filter.value === '') { return true; } @@ -82,9 +66,8 @@ export const WorkspaceFormImageList: React.FunctionComponent) => { diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx index 6123161f..fc43e378 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection.tsx @@ -16,17 +16,16 @@ const WorkspaceFormImageSelection: React.FunctionComponent { - const [selectedLabels, setSelectedLabels] = useState>>(new Map()); + const [filteredImages, setFilteredImages] = useState(images); const imageFilterContent = useMemo( () => ( image.labels)} - selectedLabels={selectedLabels} - onSelect={setSelectedLabels} + labelledObjects={images} + setLabelledObjects={(obj) => setFilteredImages(obj as WorkspacekindsImageConfigValue[])} /> ), - [images, selectedLabels, setSelectedLabels], + [images, setFilteredImages], ); return ( @@ -35,8 +34,7 @@ const WorkspaceFormImageSelection: React.FunctionComponent{imageFilterContent} diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/labelFilter/FilterByLabels.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/labelFilter/FilterByLabels.tsx index 8ab9586e..bfe31b1f 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/labelFilter/FilterByLabels.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/labelFilter/FilterByLabels.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { FilterSidePanel, FilterSidePanelCategory, @@ -6,27 +6,28 @@ import { } from '@patternfly/react-catalog-view-extension'; import '@patternfly/react-catalog-view-extension/dist/css/react-catalog-view-extension.css'; import { formatLabelKey } from '~/shared/utilities/WorkspaceUtils'; -import { WorkspacesOptionLabel } from '~/generated/data-contracts'; type FilterByLabelsProps = { - labelledObjects: WorkspacesOptionLabel[]; - selectedLabels: Map>; - onSelect: (labels: Map>) => void; + labelledObjects: { labels: { key: string; value: string }[] }[]; + setLabelledObjects: (labelledObjects: { labels: { key: string; value: string }[] }[]) => void; }; export const FilterByLabels: React.FunctionComponent = ({ labelledObjects, - selectedLabels, - onSelect, + setLabelledObjects, }) => { + const [selectedLabels, setSelectedLabels] = useState>>(new Map()); + const filterMap = useMemo(() => { const labelsMap = new Map>(); - labelledObjects.forEach((labelledObject) => { - if (!labelsMap.has(labelledObject.key)) { - labelsMap.set(labelledObject.key, new Set()); - } - labelsMap.get(labelledObject.key)?.add(labelledObject.value); - }); + labelledObjects + .flatMap((labelledObject) => labelledObject.labels) + .forEach((label) => { + if (!labelsMap.has(label.key)) { + labelsMap.set(label.key, new Set()); + } + labelsMap.get(label.key)?.add(label.value); + }); return labelsMap; }, [labelledObjects]); @@ -53,9 +54,30 @@ export const FilterByLabels: React.FunctionComponent = ({ } } - onSelect(newSelectedLabels); + setLabelledObjects( + labelledObjects.filter((labelledObject) => + [...newSelectedLabels.entries()].reduce( + (accumulator, [selectedLabelKey, selectedLabelValues]) => { + if (selectedLabelValues.size > 0) { + return ( + accumulator && + labelledObject.labels.some( + (imageLabel) => + imageLabel.key === selectedLabelKey && + selectedLabelValues.has(imageLabel.value), + ) + ); + } + return accumulator; + }, + true, + ), + ), + ); + + setSelectedLabels(newSelectedLabels); }, - [selectedLabels, onSelect], + [selectedLabels, labelledObjects, setLabelledObjects], ); return ( diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList.tsx index 7c200ec7..87a36fec 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList.tsx @@ -22,31 +22,16 @@ type FilterableDataFieldKeys = FilterableDataFieldKey; type WorkspaceFormPodConfigListProps = { podConfigs: WorkspacekindsPodConfigValue[]; - selectedLabels: Map>; selectedPodConfig: WorkspacekindsPodConfigValue | undefined; onSelect: (workspacePodConfig: WorkspacekindsPodConfigValue | undefined) => void; }; export const WorkspaceFormPodConfigList: React.FunctionComponent< WorkspaceFormPodConfigListProps -> = ({ podConfigs, selectedLabels, selectedPodConfig, onSelect }) => { +> = ({ podConfigs, selectedPodConfig, onSelect }) => { const [filters, setFilters] = useState([]); const filterRef = useRef(null); - const getFilteredWorkspacePodConfigsByLabels = useCallback( - (unfilteredPodConfigs: WorkspacekindsPodConfigValue[]) => - unfilteredPodConfigs.filter((podConfig) => - podConfig.labels.reduce((accumulator, label) => { - if (selectedLabels.has(label.key)) { - const labelValues: Set | undefined = selectedLabels.get(label.key); - return accumulator && labelValues !== undefined && labelValues.has(label.value); - } - return accumulator; - }, true), - ), - [selectedLabels], - ); - const clearAllFilters = useCallback(() => { filterRef.current?.clearAll(); }, []); @@ -62,7 +47,7 @@ export const WorkspaceFormPodConfigList: React.FunctionComponent< } catch { searchValueInput = new RegExp(filter.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'); } - const filterResult = result.filter((podConfig) => { + return result.filter((podConfig) => { if (filter.value === '') { return true; } @@ -77,9 +62,8 @@ export const WorkspaceFormPodConfigList: React.FunctionComponent< return true; } }); - return getFilteredWorkspacePodConfigsByLabels(filterResult); }, podConfigs); - }, [filters, getFilteredWorkspacePodConfigsByLabels, podConfigs]); + }, [filters, podConfigs]); const onChange = useCallback( (event: React.FormEvent) => { diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx index a82a2870..f11b4b09 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection.tsx @@ -14,17 +14,17 @@ interface WorkspaceFormPodConfigSelectionProps { const WorkspaceFormPodConfigSelection: React.FunctionComponent< WorkspaceFormPodConfigSelectionProps > = ({ podConfigs, selectedPodConfig, onSelect }) => { - const [selectedLabels, setSelectedLabels] = useState>>(new Map()); + const [filteredPodConfigs, setFilteredPodConfigs] = + useState(podConfigs); const podConfigFilterContent = useMemo( () => ( podConfig.labels)} - selectedLabels={selectedLabels} - onSelect={setSelectedLabels} + labelledObjects={podConfigs} + setLabelledObjects={(obj) => setFilteredPodConfigs(obj as WorkspacekindsPodConfigValue[])} /> ), - [podConfigs, selectedLabels, setSelectedLabels], + [podConfigs, setFilteredPodConfigs], ); return ( @@ -33,8 +33,7 @@ const WorkspaceFormPodConfigSelection: React.FunctionComponent< {podConfigFilterContent} From 1b14efde66580f0ca66ac7a34059286e0cfa951e Mon Sep 17 00:00:00 2001 From: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> Date: Tue, 12 Aug 2025 09:31:11 -0300 Subject: [PATCH 58/68] test: add unit tests for frontend hooks (#527) Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> --- .../__tests__/useCurrentRouteKey.spec.tsx | 31 +++ .../src/app/hooks/__tests__/useMount.spec.tsx | 10 + .../hooks/__tests__/useNamespaces.spec.tsx | 52 +++++ .../hooks/__tests__/useNotebookAPI.spec.tsx | 33 +++ .../__tests__/useWorkspaceFormData.spec.tsx | 78 +++++++ .../useWorkspaceFormLocationData.spec.tsx | 60 ++++++ .../__tests__/useWorkspaceKindByName.spec.tsx | 69 +++++++ .../__tests__/useWorkspaceKinds.spec.tsx | 51 +++++ .../__tests__/useWorkspaceRowActions.spec.tsx | 92 +++++++++ .../hooks/__tests__/useWorkspaces.spec.tsx | 193 ++++++++++++++++++ .../src/app/hooks/useWorkspaceFormData.ts | 6 +- .../frontend/src/shared/mock/mockBuilder.ts | 167 +++++++++------ 12 files changed, 774 insertions(+), 68 deletions(-) create mode 100644 workspaces/frontend/src/app/hooks/__tests__/useCurrentRouteKey.spec.tsx create mode 100644 workspaces/frontend/src/app/hooks/__tests__/useMount.spec.tsx create mode 100644 workspaces/frontend/src/app/hooks/__tests__/useNamespaces.spec.tsx create mode 100644 workspaces/frontend/src/app/hooks/__tests__/useNotebookAPI.spec.tsx create mode 100644 workspaces/frontend/src/app/hooks/__tests__/useWorkspaceFormData.spec.tsx create mode 100644 workspaces/frontend/src/app/hooks/__tests__/useWorkspaceFormLocationData.spec.tsx create mode 100644 workspaces/frontend/src/app/hooks/__tests__/useWorkspaceKindByName.spec.tsx create mode 100644 workspaces/frontend/src/app/hooks/__tests__/useWorkspaceKinds.spec.tsx create mode 100644 workspaces/frontend/src/app/hooks/__tests__/useWorkspaceRowActions.spec.tsx create mode 100644 workspaces/frontend/src/app/hooks/__tests__/useWorkspaces.spec.tsx diff --git a/workspaces/frontend/src/app/hooks/__tests__/useCurrentRouteKey.spec.tsx b/workspaces/frontend/src/app/hooks/__tests__/useCurrentRouteKey.spec.tsx new file mode 100644 index 00000000..ae6e5a8d --- /dev/null +++ b/workspaces/frontend/src/app/hooks/__tests__/useCurrentRouteKey.spec.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { renderHook } from '~/__tests__/unit/testUtils/hooks'; +import { useCurrentRouteKey } from '~/app/hooks/useCurrentRouteKey'; +import { AppRouteKey, AppRoutePaths } from '~/app/routes'; + +describe('useCurrentRouteKey', () => { + const wrapper: React.FC> = ({ + children, + initialEntries, + }) => {children}; + + const fillParams = (pattern: string) => pattern.replace(/:([^/]+)/g, 'test'); + const cases: ReadonlyArray = ( + Object.entries(AppRoutePaths) as [AppRouteKey, string][] + ).map(([key, pattern]) => [fillParams(pattern), key]); + + it.each(cases)('matches route keys by path: %s', (path, expected) => { + const { result } = renderHook(() => useCurrentRouteKey(), { + wrapper: (props) => wrapper({ ...props, initialEntries: [path] }), + }); + expect(result.current).toBe(expected); + }); + + it('returns undefined for unknown paths', () => { + const { result } = renderHook(() => useCurrentRouteKey(), { + wrapper: (props) => wrapper({ ...props, initialEntries: ['/unknown'] }), + }); + expect(result.current).toBeUndefined(); + }); +}); diff --git a/workspaces/frontend/src/app/hooks/__tests__/useMount.spec.tsx b/workspaces/frontend/src/app/hooks/__tests__/useMount.spec.tsx new file mode 100644 index 00000000..cdee8730 --- /dev/null +++ b/workspaces/frontend/src/app/hooks/__tests__/useMount.spec.tsx @@ -0,0 +1,10 @@ +import { renderHook } from '~/__tests__/unit/testUtils/hooks'; +import useMount from '~/app/hooks/useMount'; + +describe('useMount', () => { + it('invokes callback on mount', () => { + const cb = jest.fn(); + renderHook(() => useMount(cb)); + expect(cb).toHaveBeenCalledTimes(1); + }); +}); diff --git a/workspaces/frontend/src/app/hooks/__tests__/useNamespaces.spec.tsx b/workspaces/frontend/src/app/hooks/__tests__/useNamespaces.spec.tsx new file mode 100644 index 00000000..74c8618f --- /dev/null +++ b/workspaces/frontend/src/app/hooks/__tests__/useNamespaces.spec.tsx @@ -0,0 +1,52 @@ +import { renderHook } from '~/__tests__/unit/testUtils/hooks'; +import useNamespaces from '~/app/hooks/useNamespaces'; +import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; +import { NotebookApis } from '~/shared/api/notebookApi'; +import { APIState } from '~/shared/api/types'; + +jest.mock('~/app/hooks/useNotebookAPI', () => ({ + useNotebookAPI: jest.fn(), +})); + +const mockUseNotebookAPI = useNotebookAPI as jest.MockedFunction; + +describe('useNamespaces', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('rejects when API not available', async () => { + const unavailableState: APIState = { + apiAvailable: false, + api: {} as NotebookApis, + }; + mockUseNotebookAPI.mockReturnValue({ ...unavailableState, refreshAllAPI: jest.fn() }); + + const { result, waitForNextUpdate } = renderHook(() => useNamespaces()); + await waitForNextUpdate(); + + const [namespacesData, loaded, loadError] = result.current; + expect(namespacesData).toBeNull(); + expect(loaded).toBe(false); + expect(loadError).toBeDefined(); + }); + + it('returns data when API is available', async () => { + const listNamespaces = jest.fn().mockResolvedValue({ ok: true, data: [{ name: 'ns1' }] }); + const api = { namespaces: { listNamespaces } } as unknown as NotebookApis; + + const availableState: APIState = { + apiAvailable: true, + api, + }; + mockUseNotebookAPI.mockReturnValue({ ...availableState, refreshAllAPI: jest.fn() }); + + const { result, waitForNextUpdate } = renderHook(() => useNamespaces()); + await waitForNextUpdate(); + + const [namespacesData, loaded, loadError] = result.current; + expect(namespacesData).toEqual([{ name: 'ns1' }]); + expect(loaded).toBe(true); + expect(loadError).toBeUndefined(); + }); +}); diff --git a/workspaces/frontend/src/app/hooks/__tests__/useNotebookAPI.spec.tsx b/workspaces/frontend/src/app/hooks/__tests__/useNotebookAPI.spec.tsx new file mode 100644 index 00000000..79d84ff5 --- /dev/null +++ b/workspaces/frontend/src/app/hooks/__tests__/useNotebookAPI.spec.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { renderHook } from '~/__tests__/unit/testUtils/hooks'; +import { NotebookContext } from '~/app/context/NotebookContext'; +import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; + +jest.mock('~/app/EnsureAPIAvailability', () => ({ + default: ({ children }: { children?: React.ReactNode }) => children as React.ReactElement, +})); + +describe('useNotebookAPI', () => { + it('returns api state and refresh function from context', () => { + const refreshAPIState = jest.fn(); + const api = {} as ReturnType['api']; + const wrapper: React.FC = ({ children }) => ( + + {children} + + ); + + const { result } = renderHook(() => useNotebookAPI(), { wrapper }); + + expect(result.current.apiAvailable).toBe(true); + expect(result.current.api).toBe(api); + + result.current.refreshAllAPI(); + expect(refreshAPIState).toHaveBeenCalled(); + }); +}); diff --git a/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceFormData.spec.tsx b/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceFormData.spec.tsx new file mode 100644 index 00000000..32e1bd1b --- /dev/null +++ b/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceFormData.spec.tsx @@ -0,0 +1,78 @@ +import { renderHook } from '~/__tests__/unit/testUtils/hooks'; +import useWorkspaceFormData, { EMPTY_FORM_DATA } from '~/app/hooks/useWorkspaceFormData'; +import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; +import { NotebookApis } from '~/shared/api/notebookApi'; +import { buildMockWorkspace, buildMockWorkspaceKind } from '~/shared/mock/mockBuilder'; + +jest.mock('~/app/hooks/useNotebookAPI', () => ({ + useNotebookAPI: jest.fn(), +})); + +const mockUseNotebookAPI = useNotebookAPI as jest.MockedFunction; + +describe('useWorkspaceFormData', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns empty form data when missing namespace or name', async () => { + mockUseNotebookAPI.mockReturnValue({ + api: {} as NotebookApis, + apiAvailable: true, + refreshAllAPI: jest.fn(), + }); + const { result, waitForNextUpdate } = renderHook(() => + useWorkspaceFormData({ namespace: undefined, workspaceName: undefined }), + ); + await waitForNextUpdate(); + + const workspaceFormData = result.current[0]; + expect(workspaceFormData).toEqual(EMPTY_FORM_DATA); + }); + + it('maps workspace and kind into form data when API available', async () => { + const mockWorkspace = buildMockWorkspace({}); + const mockWorkspaceKind = buildMockWorkspaceKind({}); + const getWorkspace = jest.fn().mockResolvedValue({ + ok: true, + data: mockWorkspace, + }); + const getWorkspaceKind = jest.fn().mockResolvedValue({ ok: true, data: mockWorkspaceKind }); + + const api = { + workspaces: { getWorkspace }, + workspaceKinds: { getWorkspaceKind }, + } as unknown as NotebookApis; + + mockUseNotebookAPI.mockReturnValue({ + api, + apiAvailable: true, + refreshAllAPI: jest.fn(), + }); + + const { result, waitForNextUpdate } = renderHook(() => + useWorkspaceFormData({ namespace: 'ns', workspaceName: 'ws' }), + ); + await waitForNextUpdate(); + + const workspaceFormData = result.current[0]; + expect(workspaceFormData).toEqual({ + kind: mockWorkspaceKind, + image: { + ...mockWorkspace.podTemplate.options.imageConfig.current, + hidden: mockWorkspaceKind.hidden, + }, + podConfig: { + ...mockWorkspace.podTemplate.options.podConfig.current, + hidden: mockWorkspaceKind.hidden, + }, + properties: { + workspaceName: mockWorkspace.name, + deferUpdates: mockWorkspace.deferUpdates, + volumes: mockWorkspace.podTemplate.volumes.data, + secrets: mockWorkspace.podTemplate.volumes.secrets, + homeDirectory: mockWorkspace.podTemplate.volumes.home?.mountPath, + }, + }); + }); +}); diff --git a/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceFormLocationData.spec.tsx b/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceFormLocationData.spec.tsx new file mode 100644 index 00000000..3f7ec181 --- /dev/null +++ b/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceFormLocationData.spec.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { renderHook } from '~/__tests__/unit/testUtils/hooks'; +import { useWorkspaceFormLocationData } from '~/app/hooks/useWorkspaceFormLocationData'; +import { NamespaceContextProvider } from '~/app/context/NamespaceContextProvider'; + +jest.mock('~/app/context/NamespaceContextProvider', () => { + const ReactActual = jest.requireActual('react'); + const mockNamespaceValue = { + namespaces: ['ns1', 'ns2', 'ns3'], + selectedNamespace: 'ns1', + setSelectedNamespace: jest.fn(), + lastUsedNamespace: 'ns1', + updateLastUsedNamespace: jest.fn(), + }; + const MockContext = ReactActual.createContext(mockNamespaceValue); + return { + NamespaceContextProvider: ({ children }: { children: React.ReactNode }) => ( + {children} + ), + useNamespaceContext: () => ReactActual.useContext(MockContext), + }; +}); + +describe('useWorkspaceFormLocationData', () => { + const wrapper: React.FC< + React.PropsWithChildren<{ initialEntries: (string | { pathname: string; state?: unknown })[] }> + > = ({ children, initialEntries }) => ( + + {children} + + ); + + it('returns edit mode data', () => { + const initialEntries = [ + { pathname: '/workspaces/edit', state: { namespace: 'ns2', workspaceName: 'ws' } }, + ]; + const { result } = renderHook(() => useWorkspaceFormLocationData(), { + wrapper: (props) => wrapper({ ...props, initialEntries }), + }); + expect(result.current).toEqual({ mode: 'edit', namespace: 'ns2', workspaceName: 'ws' }); + }); + + it('throws when missing workspaceName in edit mode', () => { + const initialEntries = [{ pathname: '/workspaces/edit', state: { namespace: 'ns1' } }]; + expect(() => + renderHook(() => useWorkspaceFormLocationData(), { + wrapper: (props) => wrapper({ ...props, initialEntries }), + }), + ).toThrow(); + }); + + it('returns create mode data using selected namespace when state not provided', () => { + const initialEntries = [{ pathname: '/workspaces/create' }]; + const { result } = renderHook(() => useWorkspaceFormLocationData(), { + wrapper: (props) => wrapper({ ...props, initialEntries }), + }); + expect(result.current).toEqual({ mode: 'create', namespace: 'ns1' }); + }); +}); diff --git a/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceKindByName.spec.tsx b/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceKindByName.spec.tsx new file mode 100644 index 00000000..88fd8339 --- /dev/null +++ b/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceKindByName.spec.tsx @@ -0,0 +1,69 @@ +import { renderHook } from '~/__tests__/unit/testUtils/hooks'; +import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; +import useWorkspaceKindByName from '~/app/hooks/useWorkspaceKindByName'; +import { NotebookApis } from '~/shared/api/notebookApi'; +import { buildMockWorkspaceKind } from '~/shared/mock/mockBuilder'; + +jest.mock('~/app/hooks/useNotebookAPI', () => ({ + useNotebookAPI: jest.fn(), +})); + +const mockUseNotebookAPI = useNotebookAPI as jest.MockedFunction; + +describe('useWorkspaceKindByName', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('rejects when API not available', async () => { + mockUseNotebookAPI.mockReturnValue({ + api: {} as NotebookApis, + apiAvailable: false, + refreshAllAPI: jest.fn(), + }); + + const { result, waitForNextUpdate } = renderHook(() => useWorkspaceKindByName('jupyter')); + await waitForNextUpdate(); + + const [workspaceKind, loaded, error] = result.current; + expect(workspaceKind).toBeNull(); + expect(loaded).toBe(false); + expect(error).toBeDefined(); + }); + + it('returns null when no kind provided', async () => { + mockUseNotebookAPI.mockReturnValue({ + api: {} as NotebookApis, + apiAvailable: true, + refreshAllAPI: jest.fn(), + }); + + const { result, waitForNextUpdate } = renderHook(() => useWorkspaceKindByName(undefined)); + await waitForNextUpdate(); + + const [workspaceKind, loaded, error] = result.current; + expect(workspaceKind).toBeNull(); + expect(loaded).toBe(true); + expect(error).toBeUndefined(); + }); + + it('returns kind when API is available', async () => { + const mockWorkspaceKind = buildMockWorkspaceKind({}); + const getWorkspaceKind = jest.fn().mockResolvedValue({ ok: true, data: mockWorkspaceKind }); + mockUseNotebookAPI.mockReturnValue({ + api: { workspaceKinds: { getWorkspaceKind } } as unknown as NotebookApis, + apiAvailable: true, + refreshAllAPI: jest.fn(), + }); + + const { result, waitForNextUpdate } = renderHook(() => + useWorkspaceKindByName(mockWorkspaceKind.name), + ); + await waitForNextUpdate(); + + const [workspaceKind, loaded, error] = result.current; + expect(workspaceKind).toEqual(mockWorkspaceKind); + expect(loaded).toBe(true); + expect(error).toBeUndefined(); + }); +}); diff --git a/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceKinds.spec.tsx b/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceKinds.spec.tsx new file mode 100644 index 00000000..4637bb69 --- /dev/null +++ b/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceKinds.spec.tsx @@ -0,0 +1,51 @@ +import { renderHook } from '~/__tests__/unit/testUtils/hooks'; +import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; +import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds'; +import { NotebookApis } from '~/shared/api/notebookApi'; +import { buildMockWorkspaceKind } from '~/shared/mock/mockBuilder'; + +jest.mock('~/app/hooks/useNotebookAPI', () => ({ + useNotebookAPI: jest.fn(), +})); + +const mockUseNotebookAPI = useNotebookAPI as jest.MockedFunction; + +describe('useWorkspaceKinds', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('rejects when API not available', async () => { + mockUseNotebookAPI.mockReturnValue({ + api: {} as NotebookApis, + apiAvailable: false, + refreshAllAPI: jest.fn(), + }); + + const { result, waitForNextUpdate } = renderHook(() => useWorkspaceKinds()); + await waitForNextUpdate(); + + const [workspaceKinds, loaded, error] = result.current; + expect(workspaceKinds).toEqual([]); + expect(loaded).toBe(false); + expect(error).toBeDefined(); + }); + + it('returns kinds when API is available', async () => { + const mockWorkspaceKind = buildMockWorkspaceKind({}); + const listWorkspaceKinds = jest.fn().mockResolvedValue({ ok: true, data: [mockWorkspaceKind] }); + mockUseNotebookAPI.mockReturnValue({ + api: { workspaceKinds: { listWorkspaceKinds } } as unknown as NotebookApis, + apiAvailable: true, + refreshAllAPI: jest.fn(), + }); + + const { result, waitForNextUpdate } = renderHook(() => useWorkspaceKinds()); + await waitForNextUpdate(); + + const [workspaceKinds, loaded, error] = result.current; + expect(workspaceKinds).toEqual([mockWorkspaceKind]); + expect(loaded).toBe(true); + expect(error).toBeUndefined(); + }); +}); diff --git a/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceRowActions.spec.tsx b/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceRowActions.spec.tsx new file mode 100644 index 00000000..16d9779b --- /dev/null +++ b/workspaces/frontend/src/app/hooks/__tests__/useWorkspaceRowActions.spec.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { renderHook } from '~/__tests__/unit/testUtils/hooks'; +import { WorkspaceActionsContext } from '~/app/context/WorkspaceActionsContext'; +import { useWorkspaceRowActions } from '~/app/hooks/useWorkspaceRowActions'; +import { WorkspacesWorkspace } from '~/generated/data-contracts'; +import { buildMockWorkspace } from '~/shared/mock/mockBuilder'; + +jest.mock('~/app/context/WorkspaceActionsContext', () => { + const ReactActual = jest.requireActual('react'); + const MockContext = ReactActual.createContext(undefined); + return { + WorkspaceActionsContext: MockContext, + useWorkspaceActionsContext: () => ReactActual.useContext(MockContext), + }; +}); + +describe('useWorkspaceRowActions', () => { + const workspace = buildMockWorkspace({ name: 'ws', namespace: 'ns' }); + + type MinimalAction = { title?: string; isSeparator?: boolean; onClick?: () => void }; + type RequestActionArgs = { workspace: WorkspacesWorkspace; onActionDone?: () => void }; + type WorkspaceActionsContextLike = { + requestViewDetailsAction: (args: RequestActionArgs) => void; + requestEditAction: (args: RequestActionArgs) => void; + requestDeleteAction: (args: RequestActionArgs) => void; + requestStartAction: (args: RequestActionArgs) => void; + requestRestartAction: (args: RequestActionArgs) => void; + requestStopAction: (args: RequestActionArgs) => void; + }; + + const contextValue: WorkspaceActionsContextLike = { + requestViewDetailsAction: jest.fn(), + requestEditAction: jest.fn(), + requestDeleteAction: jest.fn(), + requestStartAction: jest.fn(), + requestRestartAction: jest.fn(), + requestStopAction: jest.fn(), + }; + + const wrapper: React.FC = ({ children }) => ( + + {children} + + ); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('builds actions respecting visibility and separators', () => { + const actionsToInclude = [ + { id: 'viewDetails' as const }, + { id: 'separator' as const }, + { id: 'edit' as const, isVisible: (w: WorkspacesWorkspace) => w.name === 'ws' }, + { id: 'delete' as const, isVisible: false }, + ]; + + const { result } = renderHook(() => useWorkspaceRowActions(actionsToInclude), { wrapper }); + const actions = result.current(workspace); + + expect(actions).toHaveLength(3); + expect((actions[0] as MinimalAction).title).toBe('View Details'); + expect((actions[1] as MinimalAction).isSeparator).toBe(true); + expect((actions[2] as MinimalAction).title).toBe('Edit'); + }); + + it('triggers context requests on action click', () => { + const onActionDone = jest.fn(); + const { result } = renderHook( + () => + useWorkspaceRowActions([ + { id: 'start' }, + { id: 'stop' }, + { id: 'restart' }, + { id: 'delete', onActionDone }, + ]), + { wrapper }, + ); + + const actions = result.current(workspace); + act(() => (actions[0] as MinimalAction).onClick?.()); + act(() => (actions[1] as MinimalAction).onClick?.()); + act(() => (actions[2] as MinimalAction).onClick?.()); + act(() => (actions[3] as MinimalAction).onClick?.()); + + expect(contextValue.requestStartAction).toHaveBeenCalledWith({ workspace }); + expect(contextValue.requestStopAction).toHaveBeenCalledWith({ workspace }); + expect(contextValue.requestRestartAction).toHaveBeenCalledWith({ workspace }); + expect(contextValue.requestDeleteAction).toHaveBeenCalledWith({ workspace, onActionDone }); + }); +}); diff --git a/workspaces/frontend/src/app/hooks/__tests__/useWorkspaces.spec.tsx b/workspaces/frontend/src/app/hooks/__tests__/useWorkspaces.spec.tsx new file mode 100644 index 00000000..cd125d43 --- /dev/null +++ b/workspaces/frontend/src/app/hooks/__tests__/useWorkspaces.spec.tsx @@ -0,0 +1,193 @@ +import { renderHook } from '~/__tests__/unit/testUtils/hooks'; +import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; +import { useWorkspacesByKind, useWorkspacesByNamespace } from '~/app/hooks/useWorkspaces'; +import { NotebookApis } from '~/shared/api/notebookApi'; +import { + buildMockImageConfig, + buildMockOptionInfo, + buildMockPodConfig, + buildMockPodTemplate, + buildMockWorkspace, + buildMockWorkspaceKindInfo, + buildMockWorkspaceList, + buildPodTemplateOptions, +} from '~/shared/mock/mockBuilder'; + +jest.mock('~/app/hooks/useNotebookAPI', () => ({ + useNotebookAPI: jest.fn(), +})); + +const mockUseNotebookAPI = useNotebookAPI as jest.MockedFunction; + +describe('useWorkspaces', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('useWorkspacesByNamespace', () => { + it('returns error when API unavailable', async () => { + mockUseNotebookAPI.mockReturnValue({ + api: {} as NotebookApis, + apiAvailable: false, + refreshAllAPI: jest.fn(), + }); + const { result, waitForNextUpdate } = renderHook(() => useWorkspacesByNamespace('ns')); + await waitForNextUpdate(); + + const [workspaces, loaded, error] = result.current; + expect(workspaces).toEqual([]); + expect(loaded).toBe(false); + expect(error).toBeDefined(); + }); + + it('fetches workspaces by namespace', async () => { + const mockWorkspace = buildMockWorkspace({}); + const mockWorkspaces = buildMockWorkspaceList({ + count: 10, + namespace: 'ns', + kind: mockWorkspace.workspaceKind, + }); + const listWorkspacesByNamespace = jest + .fn() + .mockResolvedValue({ ok: true, data: mockWorkspaces }); + const api = { workspaces: { listWorkspacesByNamespace } } as unknown as NotebookApis; + + mockUseNotebookAPI.mockReturnValue({ + api, + apiAvailable: true, + refreshAllAPI: jest.fn(), + }); + + const { result, waitForNextUpdate } = renderHook(() => useWorkspacesByNamespace('ns')); + await waitForNextUpdate(); + + const [workspaces, loaded, error] = result.current; + expect(workspaces).toEqual(mockWorkspaces); + expect(loaded).toBe(true); + expect(error).toBeUndefined(); + }); + }); + + describe('useWorkspacesByKind', () => { + it('returns error when API unavailable', async () => { + mockUseNotebookAPI.mockReturnValue({ + api: {} as NotebookApis, + apiAvailable: false, + refreshAllAPI: jest.fn(), + }); + const { result, waitForNextUpdate } = renderHook(() => + useWorkspacesByKind({ kind: 'jupyter' }), + ); + await waitForNextUpdate(); + + const [workspaces, loaded, error] = result.current; + expect(workspaces).toEqual([]); + expect(loaded).toBe(false); + expect(error).toBeDefined(); + }); + + it('returns default state and error when kind missing', async () => { + mockUseNotebookAPI.mockReturnValue({ + api: {} as NotebookApis, + apiAvailable: true, + refreshAllAPI: jest.fn(), + }); + const { result, waitForNextUpdate } = renderHook(() => useWorkspacesByKind({ kind: '' })); + await waitForNextUpdate(); + + const [workspaces, loaded, error] = result.current; + expect(workspaces).toEqual([]); + expect(loaded).toBe(false); + expect(error).toBeDefined(); + }); + + it('filters workspaces by given criteria', async () => { + const mockWorkspace1 = buildMockWorkspace({ + name: 'workspace1', + namespace: 'ns1', + workspaceKind: buildMockWorkspaceKindInfo({ name: 'kind1' }), + podTemplate: buildMockPodTemplate({ + options: buildPodTemplateOptions({ + imageConfig: buildMockImageConfig({ + current: buildMockOptionInfo({ id: 'img1' }), + }), + podConfig: buildMockPodConfig({ + current: buildMockOptionInfo({ id: 'pod1' }), + }), + }), + }), + }); + const mockWorkspace2 = buildMockWorkspace({ + name: 'workspace2', + namespace: 'ns2', + workspaceKind: buildMockWorkspaceKindInfo({ name: 'kind1' }), + podTemplate: buildMockPodTemplate({ + options: buildPodTemplateOptions({ + imageConfig: buildMockImageConfig({ + current: buildMockOptionInfo({ id: 'img2' }), + }), + podConfig: buildMockPodConfig({ + current: buildMockOptionInfo({ id: 'pod2' }), + }), + }), + }), + }); + const mockWorkspace3 = buildMockWorkspace({ + name: 'workspace3', + namespace: 'ns1', + workspaceKind: buildMockWorkspaceKindInfo({ name: 'kind2' }), + podTemplate: buildMockPodTemplate({ + options: buildPodTemplateOptions({ + imageConfig: buildMockImageConfig({ + current: buildMockOptionInfo({ id: 'img1' }), + }), + podConfig: buildMockPodConfig({ + current: buildMockOptionInfo({ id: 'pod1' }), + }), + }), + }), + }); + const mockWorkspaces = [mockWorkspace1, mockWorkspace2, mockWorkspace3]; + + const listAllWorkspaces = jest.fn().mockResolvedValue({ ok: true, data: mockWorkspaces }); + const api = { workspaces: { listAllWorkspaces } } as unknown as NotebookApis; + mockUseNotebookAPI.mockReturnValue({ + api, + apiAvailable: true, + refreshAllAPI: jest.fn(), + }); + + const { result, waitForNextUpdate, rerender } = renderHook( + (props) => useWorkspacesByKind(props), + { + initialProps: { + kind: 'kind1', + namespace: 'ns1', + imageId: 'img1', + podConfigId: 'pod1', + }, + }, + ); + + const [workspaces, loaded, error] = result.current; + expect(workspaces).toEqual([]); + expect(loaded).toBe(false); + expect(error).toBeUndefined(); + + await waitForNextUpdate(); + + const [workspaces2, loaded2, error2] = result.current; + expect(workspaces2).toEqual([mockWorkspace1]); + expect(loaded2).toBe(true); + expect(error2).toBeUndefined(); + + rerender({ kind: 'kind2', namespace: 'ns1', imageId: 'img1', podConfigId: 'pod1' }); + await waitForNextUpdate(); + + const [workspaces3, loaded3, error3] = result.current; + expect(workspaces3).toEqual([mockWorkspace3]); + expect(loaded3).toBe(true); + expect(error3).toBeUndefined(); + }); + }); +}); diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts b/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts index 88b9acc6..97429986 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts @@ -6,7 +6,7 @@ import useFetchState, { FetchStateCallbackPromise, } from '~/shared/utilities/useFetchState'; -const EMPTY_FORM_DATA: WorkspaceFormData = { +export const EMPTY_FORM_DATA: WorkspaceFormData = { kind: undefined, image: undefined, podConfig: undefined, @@ -51,14 +51,14 @@ const useWorkspaceFormData = (args: { displayName: imageConfig.displayName, description: imageConfig.description, hidden: false, - labels: [], + labels: imageConfig.labels, }, podConfig: { id: podConfig.id, displayName: podConfig.displayName, description: podConfig.description, hidden: false, - labels: [], + labels: podConfig.labels, }, properties: { workspaceName: workspace.name, diff --git a/workspaces/frontend/src/shared/mock/mockBuilder.ts b/workspaces/frontend/src/shared/mock/mockBuilder.ts index ef4434b2..bb539c0f 100644 --- a/workspaces/frontend/src/shared/mock/mockBuilder.ts +++ b/workspaces/frontend/src/shared/mock/mockBuilder.ts @@ -5,6 +5,11 @@ import { NamespacesNamespace, WorkspacekindsRedirectMessageLevel, WorkspacekindsWorkspaceKind, + WorkspacesImageConfig, + WorkspacesOptionInfo, + WorkspacesPodConfig, + WorkspacesPodTemplate, + WorkspacesPodTemplateOptions, WorkspacesWorkspace, WorkspacesWorkspaceKindInfo, WorkspacesWorkspaceState, @@ -39,6 +44,102 @@ export const buildMockWorkspaceKindInfo = ( ...workspaceKindInfo, }); +export const buildMockOptionInfo = ( + optionInfo?: Partial, +): WorkspacesOptionInfo => ({ + id: 'jupyterlab_scipy_190', + displayName: 'jupyter-scipy:v1.9.0', + description: 'JupyterLab, with SciPy Packages', + labels: [ + { + key: 'pythonVersion', + value: '3.11', + }, + { + key: 'jupyterlabVersion', + value: '1.9.0', + }, + ], + ...optionInfo, +}); + +export const buildMockImageConfig = ( + imageConfig?: Partial, +): WorkspacesImageConfig => ({ + current: buildMockOptionInfo({}), + ...imageConfig, +}); + +export const buildMockPodConfig = ( + podConfig?: Partial, +): WorkspacesPodConfig => ({ + current: { + id: 'tiny_cpu', + displayName: 'Tiny CPU', + description: 'Pod with 0.1 CPU, 128 Mb RAM', + labels: [ + { + key: 'cpu', + value: '100m', + }, + { + key: 'memory', + value: '128Mi', + }, + { + key: 'gpu', + value: '1', + }, + ], + }, + ...podConfig, +}); + +export const buildPodTemplateOptions = ( + podTemplateOptions?: Partial, +): WorkspacesPodTemplateOptions => ({ + imageConfig: buildMockImageConfig({}), + podConfig: buildMockPodConfig({}), + ...podTemplateOptions, +}); + +export const buildMockPodTemplate = ( + podTemplate?: Partial, +): WorkspacesPodTemplate => ({ + podMetadata: { + labels: { labelKey1: 'labelValue1', labelKey2: 'labelValue2' }, + annotations: { annotationKey1: 'annotationValue1', annotationKey2: 'annotationValue2' }, + }, + volumes: { + home: { + pvcName: 'Volume-Home', + mountPath: '/home', + readOnly: false, + }, + data: [ + { + pvcName: 'Volume-Data1', + mountPath: '/data', + readOnly: true, + }, + { + pvcName: 'Volume-Data2', + mountPath: '/data', + readOnly: false, + }, + ], + secrets: [ + { + defaultMode: 0o644, + mountPath: '/secrets', + secretName: 'secret-1', + }, + ], + }, + options: buildPodTemplateOptions({}), + ...podTemplate, +}); + export const buildMockWorkspace = ( workspace?: Partial, ): WorkspacesWorkspace => ({ @@ -50,71 +151,7 @@ export const buildMockWorkspace = ( pausedTime: new Date(2025, 3, 1).getTime(), state: WorkspacesWorkspaceState.WorkspaceStateRunning, stateMessage: 'Workspace is running', - podTemplate: { - podMetadata: { - labels: { labelKey1: 'labelValue1', labelKey2: 'labelValue2' }, - annotations: { annotationKey1: 'annotationValue1', annotationKey2: 'annotationValue2' }, - }, - volumes: { - home: { - pvcName: 'Volume-Home', - mountPath: '/home', - readOnly: false, - }, - data: [ - { - pvcName: 'Volume-Data1', - mountPath: '/data', - readOnly: true, - }, - { - pvcName: 'Volume-Data2', - mountPath: '/data', - readOnly: false, - }, - ], - }, - options: { - imageConfig: { - current: { - id: 'jupyterlab_scipy_190', - displayName: 'jupyter-scipy:v1.9.0', - description: 'JupyterLab, with SciPy Packages', - labels: [ - { - key: 'pythonVersion', - value: '3.11', - }, - { - key: 'jupyterlabVersion', - value: '1.9.0', - }, - ], - }, - }, - podConfig: { - current: { - id: 'tiny_cpu', - displayName: 'Tiny CPU', - description: 'Pod with 0.1 CPU, 128 Mb RAM', - labels: [ - { - key: 'cpu', - value: '100m', - }, - { - key: 'memory', - value: '128Mi', - }, - { - key: 'gpu', - value: '1', - }, - ], - }, - }, - }, - }, + podTemplate: buildMockPodTemplate({}), activity: { lastActivity: new Date(2025, 5, 1).getTime(), lastUpdate: new Date(2025, 4, 1).getTime(), From 1f5c6e1fcb4d67923cf63c47f938c7cc6dc81c06 Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:16:06 -0400 Subject: [PATCH 59/68] chore: Upgrade PatternFly to 6.3.0 (#532) Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> --- workspaces/frontend/package-lock.json | 104 +++++++++++++------------- workspaces/frontend/package.json | 16 ++-- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/workspaces/frontend/package-lock.json b/workspaces/frontend/package-lock.json index 5687c07d..1b6c4ad9 100644 --- a/workspaces/frontend/package-lock.json +++ b/workspaces/frontend/package-lock.json @@ -9,14 +9,14 @@ "version": "0.0.1", "license": "Apache-2.0", "dependencies": { - "@patternfly/patternfly": "^6.2.3", - "@patternfly/react-catalog-view-extension": "^6.1.0", - "@patternfly/react-code-editor": "^6.2.0", - "@patternfly/react-core": "^6.2.0", - "@patternfly/react-icons": "^6.2.0", - "@patternfly/react-styles": "^6.2.0", - "@patternfly/react-table": "^6.2.0", - "@patternfly/react-tokens": "^6.2.0", + "@patternfly/patternfly": "^6.3.1", + "@patternfly/react-catalog-view-extension": "^6.2.0", + "@patternfly/react-code-editor": "^6.3.1", + "@patternfly/react-core": "^6.3.1", + "@patternfly/react-icons": "^6.3.1", + "@patternfly/react-styles": "^6.3.1", + "@patternfly/react-table": "^6.3.1", + "@patternfly/react-tokens": "^6.3.1", "@types/js-yaml": "^4.0.9", "axios": "^1.10.0", "date-fns": "^4.1.0", @@ -4766,92 +4766,92 @@ } }, "node_modules/@patternfly/patternfly": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-6.2.3.tgz", - "integrity": "sha512-FR027W7JygcQpvlRU/Iom936Vm0apzfi2o5lvtlcWW6IaeZCCTtTaDxehoYuELHlemzkLziQAgu6LuCJEVayjw==" + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-6.3.1.tgz", + "integrity": "sha512-O/lTo5EHKzer/HNzqMQOQEAMG7izDDkEHpAeJ5+sGaeQ/maB3RK7sQsOPS4DjrnMxt4/cC6LogK2mowlbf1j5Q==" }, "node_modules/@patternfly/react-catalog-view-extension": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-catalog-view-extension/-/react-catalog-view-extension-6.1.0.tgz", - "integrity": "sha512-cQnafDmY/PwcImWx8xI498jXxFwF9WlOl3d5MzGzmpZbKLHHHsKz+khh/CRtUA+Ws0GztlkFBlNpVMMuX4IdmA==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-catalog-view-extension/-/react-catalog-view-extension-6.2.0.tgz", + "integrity": "sha512-IGpldLLP8iV151XDwYs3A5SiE3kksLlclh2uUyQmlcFNhvbnnqDlNfqNGOpMtR1N6HwVUJlnC4hMeUpvJAkj7Q==", "dependencies": { "@patternfly/react-core": "^6.1.0", "@patternfly/react-styles": "^6.1.0" }, "peerDependencies": { - "react": "^17 || ^18", - "react-dom": "^17 || ^18" + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" } }, "node_modules/@patternfly/react-code-editor": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-code-editor/-/react-code-editor-6.2.0.tgz", - "integrity": "sha512-e26lO34RC8yCyKXcLw6R5K3jhiDQgoFhNbR1jv907/VJe8Kn8OLvNxFifHXYLu1OW/B5wPJIQ1jsUxC+AdO3Qg==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-code-editor/-/react-code-editor-6.3.1.tgz", + "integrity": "sha512-lzrION96CR2G3ASjE++dX/dExH08HVcCLXbHdmiiTL4eHfbqXt4edDc+UX619XrbaccJBE+BxNNGKyO8bgpKRg==", "dependencies": { "@monaco-editor/react": "^4.6.0", - "@patternfly/react-core": "^6.2.0", - "@patternfly/react-icons": "^6.2.0", - "@patternfly/react-styles": "^6.2.0", + "@patternfly/react-core": "^6.3.1", + "@patternfly/react-icons": "^6.3.1", + "@patternfly/react-styles": "^6.3.1", "react-dropzone": "14.3.5", "tslib": "^2.8.1" }, "peerDependencies": { - "react": "^17 || ^18", - "react-dom": "^17 || ^18" + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" } }, "node_modules/@patternfly/react-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-6.2.0.tgz", - "integrity": "sha512-yh5de7Tv1ft8c4+xHi5wr49yk4E/FgOXsxj3bl2VjdieTxXmZEmeWcqeYFXoUdnMSqCay4Mt5k6gyRYgO0y9oQ==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-6.3.1.tgz", + "integrity": "sha512-1qV20nU4M6PA28qnikH9fPLQlkteaZZToFlATjBNBw7aUI6zIvj7U0akkHz8raWcfHAI+tAzGV7dfKjiv035/g==", "dependencies": { - "@patternfly/react-icons": "^6.2.0", - "@patternfly/react-styles": "^6.2.0", - "@patternfly/react-tokens": "^6.2.0", + "@patternfly/react-icons": "^6.3.1", + "@patternfly/react-styles": "^6.3.1", + "@patternfly/react-tokens": "^6.3.1", "focus-trap": "7.6.4", "react-dropzone": "^14.3.5", "tslib": "^2.8.1" }, "peerDependencies": { - "react": "^17 || ^18", - "react-dom": "^17 || ^18" + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" } }, "node_modules/@patternfly/react-icons": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-6.2.0.tgz", - "integrity": "sha512-moGLd1qM80+yjVVVEl+aNHQn7K5ANMUgyQZ4ECxnA/vjPlWmNZSJ1imsaCxYrywp9zzO0yZ5uN5wO/Z2hdz3MA==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-6.3.1.tgz", + "integrity": "sha512-uiMounSIww1iZLM4pq+X8c3upzwl9iowXRPjR5CA8entb70lwgAXg3PqvypnuTAcilTq1Y3k5sFTqkhz7rgKcQ==", "peerDependencies": { - "react": "^17 || ^18", - "react-dom": "^17 || ^18" + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" } }, "node_modules/@patternfly/react-styles": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-6.2.0.tgz", - "integrity": "sha512-Cv2flqlc8GEuzshjQrLj1qfYAVx9IDOudi46yfiOIvG7GUPdDCH+Ib4XGC/oZry7qj1Dwr78BJ6QOinM1cSiog==" + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-6.3.1.tgz", + "integrity": "sha512-hyb+PlO8YITjKh2wBvjdeZhX6FyB3hlf4r6yG4rPOHk4SgneXHjNSdGwQ3szAxgGqtbENCYtOqwD/8ai72GrxQ==" }, "node_modules/@patternfly/react-table": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-6.2.0.tgz", - "integrity": "sha512-6FwyzvaajgN5x/qrpPWax+GNyRllAd1dHhrgpZqZbeWyoywEifr1K2dxYmYNzMKOyYj9jEK0NnCWWIzFvaVfDg==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-6.3.1.tgz", + "integrity": "sha512-ZndBbPcMr/vInP5eELRe9m7MWzRoejRAhWx+25xOdjVAd31/CmMK1nBgZk4QAXaWjH1P+uZaZYsTgr/FMTte2g==", "dependencies": { - "@patternfly/react-core": "^6.2.0", - "@patternfly/react-icons": "^6.2.0", - "@patternfly/react-styles": "^6.2.0", - "@patternfly/react-tokens": "^6.2.0", + "@patternfly/react-core": "^6.3.1", + "@patternfly/react-icons": "^6.3.1", + "@patternfly/react-styles": "^6.3.1", + "@patternfly/react-tokens": "^6.3.1", "lodash": "^4.17.21", "tslib": "^2.8.1" }, "peerDependencies": { - "react": "^17 || ^18", - "react-dom": "^17 || ^18" + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" } }, "node_modules/@patternfly/react-tokens": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-6.2.2.tgz", - "integrity": "sha512-2GRWDPBTrcTlGNFc5NPJjrjEVU90RpgcGX/CIe2MplLgM32tpVIkeUtqIoJPLRk5GrbhyFuHJYRU+O93gU4o3Q==" + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-6.3.1.tgz", + "integrity": "sha512-wt/xKU1tGCDXUueFb+8/Cwxlm4vUD/Xl26O8MxbSLm6NZAHOUPwytJ7gugloGSPvc/zcsXxEgKANL8UZNO6DTw==" }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", diff --git a/workspaces/frontend/package.json b/workspaces/frontend/package.json index e6419766..bf5e5ca4 100644 --- a/workspaces/frontend/package.json +++ b/workspaces/frontend/package.json @@ -105,14 +105,14 @@ "webpack-merge": "^5.10.0" }, "dependencies": { - "@patternfly/patternfly": "^6.2.3", - "@patternfly/react-catalog-view-extension": "^6.1.0", - "@patternfly/react-code-editor": "^6.2.0", - "@patternfly/react-core": "^6.2.0", - "@patternfly/react-icons": "^6.2.0", - "@patternfly/react-styles": "^6.2.0", - "@patternfly/react-table": "^6.2.0", - "@patternfly/react-tokens": "^6.2.0", + "@patternfly/patternfly": "^6.3.1", + "@patternfly/react-catalog-view-extension": "^6.2.0", + "@patternfly/react-code-editor": "^6.3.1", + "@patternfly/react-core": "^6.3.1", + "@patternfly/react-icons": "^6.3.1", + "@patternfly/react-styles": "^6.3.1", + "@patternfly/react-table": "^6.3.1", + "@patternfly/react-tokens": "^6.3.1", "@types/js-yaml": "^4.0.9", "axios": "^1.10.0", "date-fns": "^4.1.0", From 1950ea37b5f3d31410e6bf40cc64487f33cba817 Mon Sep 17 00:00:00 2001 From: Paulo Rego <832830+paulovmr@users.noreply.github.com> Date: Wed, 20 Aug 2025 15:17:05 -0300 Subject: [PATCH 60/68] fix: fixed workspace kind summary breadcrumb navigation (#535) Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> --- .../pages/WorkspaceKinds/summary/WorkspaceKindSummary.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummary.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummary.tsx index 18367fca..7cf7d3de 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummary.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummary.tsx @@ -4,7 +4,7 @@ import { PageSection } from '@patternfly/react-core/dist/esm/components/Page'; import { Stack, StackItem } from '@patternfly/react-core/dist/esm/layouts/Stack'; import { Breadcrumb } from '@patternfly/react-core/dist/esm/components/Breadcrumb'; import { BreadcrumbItem } from '@patternfly/react-core/dist/esm/components/Breadcrumb/BreadcrumbItem'; -import { useTypedLocation, useTypedParams } from '~/app/routerHelper'; +import { useTypedLocation, useTypedNavigate, useTypedParams } from '~/app/routerHelper'; import WorkspaceTable, { WorkspaceTableRef } from '~/app/components/WorkspaceTable'; import { useWorkspacesByKind } from '~/app/hooks/useWorkspaces'; import WorkspaceKindSummaryExpandableCard from '~/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard'; @@ -17,6 +17,7 @@ import { usePolling } from '~/app/hooks/usePolling'; const WorkspaceKindSummary: React.FC = () => { const [isSummaryExpanded, setIsSummaryExpanded] = useState(true); + const navigate = useTypedNavigate(); const { state } = useTypedLocation<'workspaceKindSummary'>(); const { namespace, imageId, podConfigId } = state || {}; const { kind } = useTypedParams<'workspaceKindSummary'>(); @@ -62,7 +63,9 @@ const WorkspaceKindSummary: React.FC = () => { - Workspace Kinds + navigate('workspaceKinds')}> + Workspace Kinds + Workspaces in {kind} From 877e6de8948c2b8fe6b71620735a6dde22f890f0 Mon Sep 17 00:00:00 2001 From: Liav Weiss <74174727+liavweiss@users.noreply.github.com> Date: Thu, 21 Aug 2025 20:09:06 +0300 Subject: [PATCH 61/68] feat(ws): add manifests for backend (#455) * feat(ws): Define k8s workload manifest for backend component #324 Signed-off-by: Liav Weiss (EXT-Nokia) * feat(ws): Define k8s workload manifest for backend component #324 Signed-off-by: Liav Weiss (EXT-Nokia) * feat(ws): add Istio AuthorizationPolicy for nb-backend #324 Signed-off-by: Liav Weiss (EXT-Nokia) * feat(ws): Define k8s workload manifest for backend component + istio - kubeflow#324 Signed-off-by: Liav Weiss (EXT-Nokia) --------- Signed-off-by: Liav Weiss (EXT-Nokia) Co-authored-by: Liav Weiss (EXT-Nokia) --- workspaces/backend/Makefile | 24 +++- .../manifests/kustomize/base/deployment.yaml | 63 +++++++++++ .../kustomize/base/kustomization.yaml | 16 +++ .../manifests/kustomize/base/namespace.yaml | 4 + .../manifests/kustomize/base/rbac.yaml | 39 +++++++ .../manifests/kustomize/base/service.yaml | 11 ++ .../kustomize/base/service_account.yaml | 4 + .../components/common/kustomization.yaml | 9 ++ .../istio/authorization-policy.yaml | 17 +++ .../components/istio/destination-rule.yaml | 9 ++ .../components/istio/kustomization.yaml | 11 ++ .../components/istio/virtual-service.yaml | 20 ++++ .../overlays/istio/kustomization.yaml | 104 ++++++++++++++++++ 13 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 workspaces/backend/manifests/kustomize/base/deployment.yaml create mode 100644 workspaces/backend/manifests/kustomize/base/kustomization.yaml create mode 100644 workspaces/backend/manifests/kustomize/base/namespace.yaml create mode 100644 workspaces/backend/manifests/kustomize/base/rbac.yaml create mode 100644 workspaces/backend/manifests/kustomize/base/service.yaml create mode 100644 workspaces/backend/manifests/kustomize/base/service_account.yaml create mode 100644 workspaces/backend/manifests/kustomize/components/common/kustomization.yaml create mode 100644 workspaces/backend/manifests/kustomize/components/istio/authorization-policy.yaml create mode 100644 workspaces/backend/manifests/kustomize/components/istio/destination-rule.yaml create mode 100644 workspaces/backend/manifests/kustomize/components/istio/kustomization.yaml create mode 100644 workspaces/backend/manifests/kustomize/components/istio/virtual-service.yaml create mode 100644 workspaces/backend/manifests/kustomize/overlays/istio/kustomization.yaml diff --git a/workspaces/backend/Makefile b/workspaces/backend/Makefile index d139d313..ed264a7a 100644 --- a/workspaces/backend/Makefile +++ b/workspaces/backend/Makefile @@ -1,5 +1,5 @@ # Image URL to use all building/pushing image targets -IMG ?= nbv2-backend:latest +IMG ?= nb-backend:latest # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. ENVTEST_K8S_VERSION = 1.31.0 @@ -124,11 +124,13 @@ $(LOCALBIN): ## Tool Binaries KUBECTL ?= kubectl +KUSTOMIZE := $(LOCALBIN)/kustomize ENVTEST ?= $(LOCALBIN)/setup-envtest GOLANGCI_LINT = $(LOCALBIN)/golangci-lint SWAGGER = $(LOCALBIN)/swag ## Tool Versions +KUSTOMIZE_VERSION ?= v5.5.0 ENVTEST_VERSION ?= release-0.19 GOLANGCI_LINT_VERSION ?= v1.61.0 SWAGGER_VERSION ?= v1.16.6 @@ -148,6 +150,26 @@ golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. $(GOLANGCI_LINT): $(LOCALBIN) $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION)) + +##@ deployment + +.PHONY: deploy +deploy: kustomize ## Deploy backend to the K8s cluster specified in ~/.kube/config. + cd manifests/kustomize/overlays/istio && $(KUSTOMIZE) edit set image workspaces-backend=${IMG} + $(KUBECTL) apply -k manifests/kustomize/overlays/istio + +.PHONY: undeploy +undeploy: kustomize ## Undeploy backend from the K8s cluster specified in ~/.kube/config. + $(KUBECTL) delete -k manifests/kustomize/overlays/istio --ignore-not-found=true + + +##@ Dependencies + +.PHONY: kustomize +kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. +$(KUSTOMIZE): $(LOCALBIN) + $(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION)) + # go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist # $1 - target path with name of binary # $2 - package url which can be installed diff --git a/workspaces/backend/manifests/kustomize/base/deployment.yaml b/workspaces/backend/manifests/kustomize/base/deployment.yaml new file mode 100644 index 00000000..db4a891c --- /dev/null +++ b/workspaces/backend/manifests/kustomize/base/deployment.yaml @@ -0,0 +1,63 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: workspaces-backend +spec: + replicas: 1 + selector: + matchLabels: {} + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + template: + metadata: + labels: {} + spec: + serviceAccountName: workspaces-backend + securityContext: + runAsNonRoot: true + terminationGracePeriodSeconds: 30 + containers: + - name: workspaces-backend + image: workspaces-backend + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - "ALL" + ports: + - name: http-api + containerPort: 4000 + env: + - name: PORT + value: "4000" + resources: + limits: + cpu: 1 + memory: 1Gi + requests: + cpu: 100m + memory: 512Mi + livenessProbe: + httpGet: + path: /api/v1/healthcheck + port: http-api + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + readinessProbe: + httpGet: + path: /api/v1/healthcheck + port: http-api + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 \ No newline at end of file diff --git a/workspaces/backend/manifests/kustomize/base/kustomization.yaml b/workspaces/backend/manifests/kustomize/base/kustomization.yaml new file mode 100644 index 00000000..6162f9d3 --- /dev/null +++ b/workspaces/backend/manifests/kustomize/base/kustomization.yaml @@ -0,0 +1,16 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: kubeflow-workspaces + +resources: +- namespace.yaml +- service_account.yaml +- rbac.yaml +- service.yaml +- deployment.yaml + +labels: +- includeSelectors: true + pairs: + app.kubernetes.io/component: api \ No newline at end of file diff --git a/workspaces/backend/manifests/kustomize/base/namespace.yaml b/workspaces/backend/manifests/kustomize/base/namespace.yaml new file mode 100644 index 00000000..0076fabf --- /dev/null +++ b/workspaces/backend/manifests/kustomize/base/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: kubeflow-workspaces \ No newline at end of file diff --git a/workspaces/backend/manifests/kustomize/base/rbac.yaml b/workspaces/backend/manifests/kustomize/base/rbac.yaml new file mode 100644 index 00000000..4a9cd586 --- /dev/null +++ b/workspaces/backend/manifests/kustomize/base/rbac.yaml @@ -0,0 +1,39 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: workspaces-backend +rules: +- apiGroups: + - kubeflow.org + resources: + - workspaces + - workspacekinds + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: workspaces-backend +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: workspaces-backend +subjects: +- kind: ServiceAccount + name: workspaces-backend + namespace: kubeflow-workspaces \ No newline at end of file diff --git a/workspaces/backend/manifests/kustomize/base/service.yaml b/workspaces/backend/manifests/kustomize/base/service.yaml new file mode 100644 index 00000000..8189c39d --- /dev/null +++ b/workspaces/backend/manifests/kustomize/base/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: workspaces-backend +spec: + selector: {} + ports: + - name: http-api + port: 4000 + targetPort: http-api + type: ClusterIP \ No newline at end of file diff --git a/workspaces/backend/manifests/kustomize/base/service_account.yaml b/workspaces/backend/manifests/kustomize/base/service_account.yaml new file mode 100644 index 00000000..5e211753 --- /dev/null +++ b/workspaces/backend/manifests/kustomize/base/service_account.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: workspaces-backend \ No newline at end of file diff --git a/workspaces/backend/manifests/kustomize/components/common/kustomization.yaml b/workspaces/backend/manifests/kustomize/components/common/kustomization.yaml new file mode 100644 index 00000000..2e2d28f4 --- /dev/null +++ b/workspaces/backend/manifests/kustomize/components/common/kustomization.yaml @@ -0,0 +1,9 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +labels: +- includeSelectors: true + pairs: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: workspaces-backend + app.kubernetes.io/part-of: kubeflow-workspaces \ No newline at end of file diff --git a/workspaces/backend/manifests/kustomize/components/istio/authorization-policy.yaml b/workspaces/backend/manifests/kustomize/components/istio/authorization-policy.yaml new file mode 100644 index 00000000..bcf14cc7 --- /dev/null +++ b/workspaces/backend/manifests/kustomize/components/istio/authorization-policy.yaml @@ -0,0 +1,17 @@ +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: workspaces-backend +spec: + action: ALLOW + selector: + matchLabels: + app.kubernetes.io/component: api + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: workspaces-backend + app.kubernetes.io/part-of: kubeflow-workspaces + rules: + - from: + - source: + principals: + - cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account \ No newline at end of file diff --git a/workspaces/backend/manifests/kustomize/components/istio/destination-rule.yaml b/workspaces/backend/manifests/kustomize/components/istio/destination-rule.yaml new file mode 100644 index 00000000..4549f6fd --- /dev/null +++ b/workspaces/backend/manifests/kustomize/components/istio/destination-rule.yaml @@ -0,0 +1,9 @@ +apiVersion: networking.istio.io/v1beta1 +kind: DestinationRule +metadata: + name: workspaces-backend +spec: + host: workspaces-backend.kubeflow-workspaces.svc.cluster.local + trafficPolicy: + tls: + mode: ISTIO_MUTUAL \ No newline at end of file diff --git a/workspaces/backend/manifests/kustomize/components/istio/kustomization.yaml b/workspaces/backend/manifests/kustomize/components/istio/kustomization.yaml new file mode 100644 index 00000000..b6c91830 --- /dev/null +++ b/workspaces/backend/manifests/kustomize/components/istio/kustomization.yaml @@ -0,0 +1,11 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +resources: +- destination-rule.yaml +- virtual-service.yaml +- authorization-policy.yaml + +labels: +- pairs: + app.kubernetes.io/component: api \ No newline at end of file diff --git a/workspaces/backend/manifests/kustomize/components/istio/virtual-service.yaml b/workspaces/backend/manifests/kustomize/components/istio/virtual-service.yaml new file mode 100644 index 00000000..a73f511a --- /dev/null +++ b/workspaces/backend/manifests/kustomize/components/istio/virtual-service.yaml @@ -0,0 +1,20 @@ +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: workspaces-backend +spec: + gateways: + - kubeflow/kubeflow-gateway + hosts: + - '*' + http: + - match: + - uri: + prefix: /workspaces/api/ + rewrite: + uri: /api/ + route: + - destination: + host: workspaces-backend.kubeflow-workspaces.svc.cluster.local + port: + number: 4000 \ No newline at end of file diff --git a/workspaces/backend/manifests/kustomize/overlays/istio/kustomization.yaml b/workspaces/backend/manifests/kustomize/overlays/istio/kustomization.yaml new file mode 100644 index 00000000..1b25c617 --- /dev/null +++ b/workspaces/backend/manifests/kustomize/overlays/istio/kustomization.yaml @@ -0,0 +1,104 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: kubeflow-workspaces + +resources: +- ../../base + +components: +- ../../components/istio +- ../../components/common + +patches: +- patch: |- + - op: remove + path: /metadata/labels/app.kubernetes.io~1component + - op: remove + path: /metadata/labels/app.kubernetes.io~1name + - op: add + path: /metadata/labels/istio-injection + value: enabled + target: + kind: Namespace + name: kubeflow-workspaces + +replacements: +- source: + fieldPath: metadata.namespace + kind: ServiceAccount + name: workspaces-backend + targets: + - fieldPaths: + - metadata.name + select: + kind: Namespace + name: kubeflow-workspaces + - fieldPaths: + - subjects.[kind=ServiceAccount].namespace + select: + kind: ClusterRoleBinding + name: workspaces-backend +- source: + fieldPath: metadata.name + kind: Service + name: workspaces-backend + version: v1 + targets: + - fieldPaths: + - spec.http.0.route.0.destination.host + options: + delimiter: . + select: + group: networking.istio.io + kind: VirtualService + name: workspaces-backend + version: v1beta1 + - fieldPaths: + - spec.host + options: + delimiter: . + select: + group: networking.istio.io + kind: DestinationRule + name: workspaces-backend + version: v1beta1 +- source: + fieldPath: metadata.namespace + kind: Service + name: workspaces-backend + version: v1 + targets: + - fieldPaths: + - spec.http.0.route.0.destination.host + options: + delimiter: . + index: 1 + select: + group: networking.istio.io + kind: VirtualService + name: workspaces-backend + version: v1beta1 + - fieldPaths: + - spec.host + options: + delimiter: . + index: 1 + select: + group: networking.istio.io + kind: DestinationRule + name: workspaces-backend + version: v1beta1 +- source: + fieldPath: spec.ports.[name=http-api].port + kind: Service + name: workspaces-backend + version: v1 + targets: + - fieldPaths: + - spec.http.0.route.0.destination.port.number + select: + group: networking.istio.io + kind: VirtualService + name: workspaces-backend + version: v1beta1 \ No newline at end of file From 42ffd9b0c56a4abf6bcce9bf9ba063c9e5b3d4cc Mon Sep 17 00:00:00 2001 From: Noa Limoy <84776878+Noa-limoy@users.noreply.github.com> Date: Thu, 21 Aug 2025 17:34:06 +0000 Subject: [PATCH 62/68] feat(ws): add manifests for frontend (#487) * feat(ws): Define k8s workload manifest for frontend component #404 Signed-off-by: Noa * fix: virtual-service tweaks from review Signed-off-by: Andy Stoneberg --------- Signed-off-by: Noa Signed-off-by: Andy Stoneberg Co-authored-by: Andy Stoneberg --- .../manifests/kustomize/base/deployment.yaml | 58 +++++++++++ .../kustomize/base/kustomization.yaml | 14 +++ .../manifests/kustomize/base/namespace.yaml | 4 + .../manifests/kustomize/base/service.yaml | 10 ++ .../components/common/kustomization.yaml | 9 ++ .../istio/authorization-policy.yaml | 17 ++++ .../components/istio/destination-rule.yaml | 9 ++ .../components/istio/kustomization.yaml | 11 +++ .../components/istio/virtual-service.yaml | 22 +++++ .../overlays/istio/kustomization.yaml | 99 +++++++++++++++++++ 10 files changed, 253 insertions(+) create mode 100644 workspaces/frontend/manifests/kustomize/base/deployment.yaml create mode 100644 workspaces/frontend/manifests/kustomize/base/kustomization.yaml create mode 100644 workspaces/frontend/manifests/kustomize/base/namespace.yaml create mode 100644 workspaces/frontend/manifests/kustomize/base/service.yaml create mode 100644 workspaces/frontend/manifests/kustomize/components/common/kustomization.yaml create mode 100644 workspaces/frontend/manifests/kustomize/components/istio/authorization-policy.yaml create mode 100644 workspaces/frontend/manifests/kustomize/components/istio/destination-rule.yaml create mode 100644 workspaces/frontend/manifests/kustomize/components/istio/kustomization.yaml create mode 100644 workspaces/frontend/manifests/kustomize/components/istio/virtual-service.yaml create mode 100644 workspaces/frontend/manifests/kustomize/overlays/istio/kustomization.yaml diff --git a/workspaces/frontend/manifests/kustomize/base/deployment.yaml b/workspaces/frontend/manifests/kustomize/base/deployment.yaml new file mode 100644 index 00000000..c5763985 --- /dev/null +++ b/workspaces/frontend/manifests/kustomize/base/deployment.yaml @@ -0,0 +1,58 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: workspaces-frontend +spec: + selector: + matchLabels: {} + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + template: + metadata: + labels: {} + spec: + terminationGracePeriodSeconds: 30 + containers: + - name: workspaces-frontend + image: workspaces-frontend + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - "ALL" + ports: + - name: http-ui + containerPort: 8080 + env: + - name: PORT + value: "8080" + resources: + limits: + cpu: "1" + memory: 512Mi + requests: + cpu: 100m + memory: 256Mi + livenessProbe: + httpGet: + path: / + port: http-ui + scheme: HTTP + initialDelaySeconds: 15 + periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: / + port: http-ui + scheme: HTTP + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 \ No newline at end of file diff --git a/workspaces/frontend/manifests/kustomize/base/kustomization.yaml b/workspaces/frontend/manifests/kustomize/base/kustomization.yaml new file mode 100644 index 00000000..8ff7a35f --- /dev/null +++ b/workspaces/frontend/manifests/kustomize/base/kustomization.yaml @@ -0,0 +1,14 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: kubeflow-workspaces + +resources: +- namespace.yaml +- deployment.yaml +- service.yaml + +labels: +- includeSelectors: true + pairs: + app.kubernetes.io/component: ui \ No newline at end of file diff --git a/workspaces/frontend/manifests/kustomize/base/namespace.yaml b/workspaces/frontend/manifests/kustomize/base/namespace.yaml new file mode 100644 index 00000000..0076fabf --- /dev/null +++ b/workspaces/frontend/manifests/kustomize/base/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: kubeflow-workspaces \ No newline at end of file diff --git a/workspaces/frontend/manifests/kustomize/base/service.yaml b/workspaces/frontend/manifests/kustomize/base/service.yaml new file mode 100644 index 00000000..34ba13dc --- /dev/null +++ b/workspaces/frontend/manifests/kustomize/base/service.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Service +metadata: + name: workspaces-frontend +spec: + ports: + - name: http-ui + port: 8080 + targetPort: http-ui + type: ClusterIP \ No newline at end of file diff --git a/workspaces/frontend/manifests/kustomize/components/common/kustomization.yaml b/workspaces/frontend/manifests/kustomize/components/common/kustomization.yaml new file mode 100644 index 00000000..a80030c5 --- /dev/null +++ b/workspaces/frontend/manifests/kustomize/components/common/kustomization.yaml @@ -0,0 +1,9 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +labels: +- includeSelectors: true + pairs: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: workspaces-frontend + app.kubernetes.io/part-of: kubeflow-workspaces \ No newline at end of file diff --git a/workspaces/frontend/manifests/kustomize/components/istio/authorization-policy.yaml b/workspaces/frontend/manifests/kustomize/components/istio/authorization-policy.yaml new file mode 100644 index 00000000..29ced65b --- /dev/null +++ b/workspaces/frontend/manifests/kustomize/components/istio/authorization-policy.yaml @@ -0,0 +1,17 @@ +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: workspaces-frontend +spec: + action: ALLOW + selector: + matchLabels: + app.kubernetes.io/component: ui + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: workspaces-frontend + app.kubernetes.io/part-of: kubeflow-workspaces + rules: + - from: + - source: + principals: + - cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account \ No newline at end of file diff --git a/workspaces/frontend/manifests/kustomize/components/istio/destination-rule.yaml b/workspaces/frontend/manifests/kustomize/components/istio/destination-rule.yaml new file mode 100644 index 00000000..d1ef05f1 --- /dev/null +++ b/workspaces/frontend/manifests/kustomize/components/istio/destination-rule.yaml @@ -0,0 +1,9 @@ +apiVersion: networking.istio.io/v1beta1 +kind: DestinationRule +metadata: + name: workspaces-frontend +spec: + host: workspaces-frontend.kubeflow-workspaces.svc.cluster.local + trafficPolicy: + tls: + mode: ISTIO_MUTUAL \ No newline at end of file diff --git a/workspaces/frontend/manifests/kustomize/components/istio/kustomization.yaml b/workspaces/frontend/manifests/kustomize/components/istio/kustomization.yaml new file mode 100644 index 00000000..7ac2400f --- /dev/null +++ b/workspaces/frontend/manifests/kustomize/components/istio/kustomization.yaml @@ -0,0 +1,11 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +resources: +- destination-rule.yaml +- virtual-service.yaml +- authorization-policy.yaml + +labels: +- pairs: + app.kubernetes.io/component: ui \ No newline at end of file diff --git a/workspaces/frontend/manifests/kustomize/components/istio/virtual-service.yaml b/workspaces/frontend/manifests/kustomize/components/istio/virtual-service.yaml new file mode 100644 index 00000000..edf17a55 --- /dev/null +++ b/workspaces/frontend/manifests/kustomize/components/istio/virtual-service.yaml @@ -0,0 +1,22 @@ +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: workspaces-frontend +spec: + gateways: + - kubeflow/kubeflow-gateway + hosts: + - '*' + http: + - match: + - uri: + prefix: /workspaces/ + - uri: + exact: /workspaces + rewrite: + uri: / + route: + - destination: + host: workspaces-frontend.kubeflow-workspaces.svc.cluster.local + port: + number: 8080 \ No newline at end of file diff --git a/workspaces/frontend/manifests/kustomize/overlays/istio/kustomization.yaml b/workspaces/frontend/manifests/kustomize/overlays/istio/kustomization.yaml new file mode 100644 index 00000000..84f7c959 --- /dev/null +++ b/workspaces/frontend/manifests/kustomize/overlays/istio/kustomization.yaml @@ -0,0 +1,99 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: kubeflow-workspaces + +resources: +- ../../base + +components: +- ../../components/istio +- ../../components/common + +patches: +- patch: |- + - op: remove + path: /metadata/labels/app.kubernetes.io~1component + - op: remove + path: /metadata/labels/app.kubernetes.io~1name + - op: add + path: /metadata/labels/istio-injection + value: enabled + target: + kind: Namespace + name: kubeflow-workspaces + +replacements: +- source: + fieldPath: metadata.namespace + kind: Deployment + name: workspaces-frontend + targets: + - fieldPaths: + - metadata.name + select: + kind: Namespace + name: kubeflow-workspaces +- source: + fieldPath: metadata.name + kind: Service + name: workspaces-frontend + version: v1 + targets: + - fieldPaths: + - spec.http.0.route.0.destination.host + options: + delimiter: . + select: + group: networking.istio.io + kind: VirtualService + name: workspaces-frontend + version: v1beta1 + - fieldPaths: + - spec.host + options: + delimiter: . + select: + group: networking.istio.io + kind: DestinationRule + name: workspaces-frontend + version: v1beta1 +- source: + fieldPath: metadata.namespace + kind: Service + name: workspaces-frontend + version: v1 + targets: + - fieldPaths: + - spec.http.0.route.0.destination.host + options: + delimiter: . + index: 1 + select: + group: networking.istio.io + kind: VirtualService + name: workspaces-frontend + version: v1beta1 + - fieldPaths: + - spec.host + options: + delimiter: . + index: 1 + select: + group: networking.istio.io + kind: DestinationRule + name: workspaces-frontend + version: v1beta1 +- source: + fieldPath: spec.ports.[name=http-ui].port + kind: Service + name: workspaces-frontend + version: v1 + targets: + - fieldPaths: + - spec.http.0.route.0.destination.port.number + select: + group: networking.istio.io + kind: VirtualService + name: workspaces-frontend + version: v1beta1 \ No newline at end of file From e666e2ebfb9dec3c73f6d307aedcb20abc8761a3 Mon Sep 17 00:00:00 2001 From: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> Date: Tue, 26 Aug 2025 11:37:20 -0300 Subject: [PATCH 63/68] feat: add environment configuration files for frontend (#536) Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> --- workspaces/frontend/.env | 4 + workspaces/frontend/.env.cypress.mock | 5 + workspaces/frontend/.env.development | 3 + workspaces/frontend/.env.production | 1 + workspaces/frontend/.gitignore | 3 +- workspaces/frontend/README.md | 6 +- workspaces/frontend/babel.config.js | 16 + .../frontend/config/cspell-ignore-words.txt | 4 +- workspaces/frontend/config/dotenv.js | 191 + workspaces/frontend/config/stylePaths.js | 1 + workspaces/frontend/config/webpack.common.js | 395 +- workspaces/frontend/config/webpack.dev.js | 187 +- workspaces/frontend/config/webpack.prod.js | 83 +- workspaces/frontend/package-lock.json | 9496 ++++++++++------- workspaces/frontend/package.json | 160 +- .../src/__tests__/cypress/cypress.config.ts | 15 +- .../cypress/tests/mocked/application.cy.ts | 2 +- .../workspaces/WorkspaceDetailsActivity.cy.ts | 2 +- .../cypress/cypress/webpack.config.ts | 115 + .../frontend/src/__tests__/unit/jest.setup.ts | 7 +- workspaces/frontend/src/app/App.tsx | 5 +- workspaces/frontend/src/app/AppRoutes.tsx | 5 +- workspaces/frontend/src/app/NavSidebar.tsx | 4 +- .../app/components/ThemeAwareSearchInput.tsx | 4 +- .../src/app/components/WorkspaceTable.tsx | 7 +- workspaces/frontend/src/app/const.ts | 16 - .../src/app/context/NotebookContext.tsx | 6 +- .../src/app/context/useNotebookAPIState.tsx | 3 +- .../WorkspaceKinds/Form/WorkspaceKindForm.tsx | 3 +- .../details/WorkspaceKindDetailsImages.tsx | 2 +- .../WorkspaceKindDetailsPodConfigs.tsx | 2 +- .../summary/WorkspaceKindSummary.tsx | 4 +- .../src/app/pages/Workspaces/Workspaces.tsx | 4 +- workspaces/frontend/src/app/routerHelper.ts | 2 +- .../images/{logo.svg => logo-light-theme.svg} | 0 workspaces/frontend/src/index.tsx | 4 +- workspaces/frontend/src/shared/typeHelpers.ts | 18 + .../frontend/src/shared/utilities/const.ts | 19 +- .../frontend/src/shared/utilities/types.ts | 14 + workspaces/frontend/tsconfig.json | 15 +- 40 files changed, 6328 insertions(+), 4505 deletions(-) create mode 100644 workspaces/frontend/.env create mode 100644 workspaces/frontend/.env.development create mode 100644 workspaces/frontend/.env.production create mode 100644 workspaces/frontend/babel.config.js create mode 100644 workspaces/frontend/config/dotenv.js create mode 100644 workspaces/frontend/src/__tests__/cypress/cypress/webpack.config.ts delete mode 100644 workspaces/frontend/src/app/const.ts rename workspaces/frontend/src/images/{logo.svg => logo-light-theme.svg} (100%) create mode 100644 workspaces/frontend/src/shared/utilities/types.ts diff --git a/workspaces/frontend/.env b/workspaces/frontend/.env new file mode 100644 index 00000000..112ddcad --- /dev/null +++ b/workspaces/frontend/.env @@ -0,0 +1,4 @@ +LOGO=logo-light-theme.svg +LOGO_DARK=logo-dark-theme.svg +FAVICON=favicon.ico +PRODUCT_NAME="Notebooks" diff --git a/workspaces/frontend/.env.cypress.mock b/workspaces/frontend/.env.cypress.mock index 60f8faee..b4c03de2 100644 --- a/workspaces/frontend/.env.cypress.mock +++ b/workspaces/frontend/.env.cypress.mock @@ -1,2 +1,7 @@ # Test against prod build hosted by lightweight http server BASE_URL=http://localhost:9001 +DEPLOYMENT_MODE=standalone +POLL_INTERVAL=9999999 +DIST_DIR=./dist +URL_PREFIX=/ +PUBLIC_PATH=/ diff --git a/workspaces/frontend/.env.development b/workspaces/frontend/.env.development new file mode 100644 index 00000000..9fc5aa7a --- /dev/null +++ b/workspaces/frontend/.env.development @@ -0,0 +1,3 @@ +APP_ENV=development +DEPLOYMENT_MODE=standalone +MOCK_API_ENABLED=true diff --git a/workspaces/frontend/.env.production b/workspaces/frontend/.env.production new file mode 100644 index 00000000..a904f4a9 --- /dev/null +++ b/workspaces/frontend/.env.production @@ -0,0 +1 @@ +APP_ENV=production diff --git a/workspaces/frontend/.gitignore b/workspaces/frontend/.gitignore index 27c704f8..574a1040 100644 --- a/workspaces/frontend/.gitignore +++ b/workspaces/frontend/.gitignore @@ -5,6 +5,5 @@ yarn.lock stats.json coverage .idea -.env .vscode/* -!.vscode/settings.json \ No newline at end of file +!.vscode/settings.json diff --git a/workspaces/frontend/README.md b/workspaces/frontend/README.md index c2477b62..da872f10 100644 --- a/workspaces/frontend/README.md +++ b/workspaces/frontend/README.md @@ -50,11 +50,7 @@ This is the default setup for running the UI locally. Make sure you build the pr npm run start:dev ``` -The command above requires the backend to be active in order to serve data. To run the UI independently, without establishing a connection to the backend, use the following command to start the application with a mocked API: - - ```bash - npm run start:dev:mock - ``` +The command above starts the UI with mocked data by default, so you can run the application without requiring a connection to the backend. This behavior can be customized in the `.env.development` file by setting the `MOCK_API_ENABLED` environment variable to `false`. ### Testing diff --git a/workspaces/frontend/babel.config.js b/workspaces/frontend/babel.config.js new file mode 100644 index 00000000..009c234e --- /dev/null +++ b/workspaces/frontend/babel.config.js @@ -0,0 +1,16 @@ +module.exports = { + presets: [ + [ + '@babel/preset-env', + { + targets: { + chrome: 110, + }, + useBuiltIns: 'usage', + corejs: '3', + }, + ], + '@babel/preset-react', + '@babel/preset-typescript', + ], +}; diff --git a/workspaces/frontend/config/cspell-ignore-words.txt b/workspaces/frontend/config/cspell-ignore-words.txt index 6b02547d..f4d8a0ad 100644 --- a/workspaces/frontend/config/cspell-ignore-words.txt +++ b/workspaces/frontend/config/cspell-ignore-words.txt @@ -5,4 +5,6 @@ jovyan millicores workspacekind workspacekinds -healthcheck \ No newline at end of file +healthcheck +pficon +svgs diff --git a/workspaces/frontend/config/dotenv.js b/workspaces/frontend/config/dotenv.js new file mode 100644 index 00000000..9f245dbb --- /dev/null +++ b/workspaces/frontend/config/dotenv.js @@ -0,0 +1,191 @@ +const fs = require('fs'); +const path = require('path'); +const dotenv = require('dotenv'); +const dotenvExpand = require('dotenv-expand'); +const Dotenv = require('dotenv-webpack'); + +/** + * Determine if the project is standalone or nested. + * + * @param {string} directory + * @returns {boolean} + */ +const getProjectIsRootDir = (directory) => { + const dotenvLocalFile = path.resolve(directory, '.env.local'); + const dotenvFile = path.resolve(directory, '.env'); + let localIsRoot; + let isRoot; + + if (fs.existsSync(dotenvLocalFile)) { + const { IS_PROJECT_ROOT_DIR: DOTENV_LOCAL_ROOT } = dotenv.parse( + fs.readFileSync(dotenvLocalFile), + ); + localIsRoot = DOTENV_LOCAL_ROOT; + } + + if (fs.existsSync(dotenvFile)) { + const { IS_PROJECT_ROOT_DIR: DOTENV_ROOT } = dotenv.parse(fs.readFileSync(dotenvFile)); + isRoot = DOTENV_ROOT; + } + + return localIsRoot !== undefined ? localIsRoot !== 'false' : isRoot !== 'false'; +}; + +/** + * Return tsconfig compilerOptions. + * + * @param {string} directory + * @returns {object} + */ +const getTsCompilerOptions = (directory) => { + const tsconfigFile = path.resolve(directory, './tsconfig.json'); + let tsCompilerOptions = {}; + + if (fs.existsSync(tsconfigFile)) { + const { compilerOptions = { outDir: './dist', baseUrl: './src' } } = require(tsconfigFile); + tsCompilerOptions = compilerOptions; + } + + return tsCompilerOptions; +}; + +/** + * Setup a webpack dotenv plugin config. + * + * @param {string} path + * @returns {*} + */ +const setupWebpackDotenvFile = (path) => { + const settings = { + systemvars: true, + silent: true, + }; + + if (path) { + settings.path = path; + } + + return new Dotenv(settings); +}; + +/** + * Setup multiple webpack dotenv file parameters. + * + * @param {string} directory + * @param {string} env + * @param {boolean} isRoot + * @returns {Array} + */ +const setupWebpackDotenvFilesForEnv = ({ directory, env, isRoot = true }) => { + const dotenvWebpackSettings = []; + + if (process.env.CY_MOCK) { + dotenvWebpackSettings.push( + setupWebpackDotenvFile(path.resolve(directory, `.env.cypress.mock`)), + ); + } + + if (env) { + dotenvWebpackSettings.push( + setupWebpackDotenvFile(path.resolve(directory, `.env.${env}.local`)), + ); + dotenvWebpackSettings.push(setupWebpackDotenvFile(path.resolve(directory, `.env.${env}`))); + } + + dotenvWebpackSettings.push(setupWebpackDotenvFile(path.resolve(directory, '.env.local'))); + dotenvWebpackSettings.push(setupWebpackDotenvFile(path.resolve(directory, '.env'))); + + if (!isRoot) { + if (env) { + dotenvWebpackSettings.push( + setupWebpackDotenvFile(path.resolve(directory, '..', `.env.${env}.local`)), + ); + dotenvWebpackSettings.push( + setupWebpackDotenvFile(path.resolve(directory, '..', `.env.${env}`)), + ); + } + + dotenvWebpackSettings.push(setupWebpackDotenvFile(path.resolve(directory, '..', '.env.local'))); + dotenvWebpackSettings.push(setupWebpackDotenvFile(path.resolve(directory, '..', '.env'))); + } + + return dotenvWebpackSettings; +}; + +/** + * Setup, and access, a dotenv file and the related set of parameters. + * + * @param {string} path + * @returns {*} + */ +const setupDotenvFile = (path) => { + const dotenvInitial = dotenv.config({ path }); + dotenvExpand(dotenvInitial); +}; + +/** + * Setup and access local and specific dotenv file parameters. + * + * @param {string} env + */ +const setupDotenvFilesForEnv = ({ env }) => { + const RELATIVE_DIRNAME = path.resolve(__dirname, '..'); + const IS_ROOT = getProjectIsRootDir(RELATIVE_DIRNAME); + const { baseUrl: TS_BASE_URL, outDir: TS_OUT_DIR } = getTsCompilerOptions(RELATIVE_DIRNAME); + + if (process.env.CY_MOCK) { + setupDotenvFile(path.resolve(RELATIVE_DIRNAME, `.env.cypress.mock`)); + } + + if (!IS_ROOT) { + if (env) { + setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '..', `.env.${env}.local`)); + setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '..', `.env.${env}`)); + } + + setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '..', '.env.local')); + setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '..', '.env')); + } + + if (env) { + setupDotenvFile(path.resolve(RELATIVE_DIRNAME, `.env.${env}.local`)); + setupDotenvFile(path.resolve(RELATIVE_DIRNAME, `.env.${env}`)); + } + + setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '.env.local')); + setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '.env')); + + const DEPLOYMENT_MODE = process.env.DEPLOYMENT_MODE || 'kubeflow'; + const AUTH_METHOD = process.env.AUTH_METHOD || 'internal'; + const IMAGES_DIRNAME = process.env.IMAGES_DIRNAME || 'images'; + const PUBLIC_PATH = process.env.PUBLIC_PATH || '/workspaces'; + const SRC_DIR = path.resolve(RELATIVE_DIRNAME, process.env.SRC_DIR || TS_BASE_URL || 'src'); + const COMMON_DIR = path.resolve(RELATIVE_DIRNAME, process.env.COMMON_DIR || '../common'); + const DIST_DIR = path.resolve(RELATIVE_DIRNAME, process.env.DIST_DIR || TS_OUT_DIR || 'dist'); + const HOST = process.env.HOST || DEPLOYMENT_MODE === 'kubeflow' ? '0.0.0.0' : 'localhost'; + const PORT = process.env.PORT || '9000'; + const PROXY_PROTOCOL = process.env.PROXY_PROTOCOL || 'http'; + const PROXY_HOST = process.env.PROXY_HOST || 'localhost'; + const PROXY_PORT = process.env.PROXY_PORT || process.env.PORT || 4000; + const DEV_MODE = process.env.DEV_MODE || undefined; + const OUTPUT_ONLY = process.env._OUTPUT_ONLY === 'true'; + + process.env._RELATIVE_DIRNAME = RELATIVE_DIRNAME; + process.env._IS_PROJECT_ROOT_DIR = IS_ROOT; + process.env._IMAGES_DIRNAME = IMAGES_DIRNAME; + process.env._PUBLIC_PATH = PUBLIC_PATH; + process.env._SRC_DIR = SRC_DIR; + process.env._COMMON_DIR = COMMON_DIR; + process.env._DIST_DIR = DIST_DIR; + process.env._HOST = HOST; + process.env._PORT = PORT; + process.env._PROXY_PROTOCOL = PROXY_PROTOCOL; + process.env._PROXY_HOST = PROXY_HOST; + process.env._PROXY_PORT = PROXY_PORT; + process.env._OUTPUT_ONLY = OUTPUT_ONLY; + process.env._DEV_MODE = DEV_MODE; + process.env._DEPLOYMENT_MODE = DEPLOYMENT_MODE; + process.env._AUTH_METHOD = AUTH_METHOD; +}; + +module.exports = { setupWebpackDotenvFilesForEnv, setupDotenvFilesForEnv }; diff --git a/workspaces/frontend/config/stylePaths.js b/workspaces/frontend/config/stylePaths.js index 1f8a6b3f..fb578e4f 100644 --- a/workspaces/frontend/config/stylePaths.js +++ b/workspaces/frontend/config/stylePaths.js @@ -23,5 +23,6 @@ module.exports = { relativeDir, 'node_modules/@patternfly/react-inline-edit-extension/node_modules/@patternfly/react-styles/css', ), + path.resolve(relativeDir, 'node_modules/@patternfly/react-catalog-view-extension/dist/css'), ], }; diff --git a/workspaces/frontend/config/webpack.common.js b/workspaces/frontend/config/webpack.common.js index bbbba485..f772c659 100644 --- a/workspaces/frontend/config/webpack.common.js +++ b/workspaces/frontend/config/webpack.common.js @@ -1,186 +1,245 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ - const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin'); -const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); -const Dotenv = require('dotenv-webpack'); -const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); -const { EnvironmentPlugin } = require('webpack'); -const APP_PREFIX = process.env.APP_PREFIX || '/workspaces'; -const IMAGES_DIRNAME = 'images'; -const relativeDir = path.resolve(__dirname, '..'); -module.exports = (env) => { - return { - module: { - rules: [ - { - test: /\.(tsx|ts|jsx)?$/, - use: [ - { - loader: 'ts-loader', - options: { - experimentalWatchApi: true, +const { setupWebpackDotenvFilesForEnv } = require('./dotenv'); +const { name } = require('../package.json'); + +const RELATIVE_DIRNAME = process.env._RELATIVE_DIRNAME; +const IS_PROJECT_ROOT_DIR = process.env._IS_PROJECT_ROOT_DIR; +const IMAGES_DIRNAME = process.env._IMAGES_DIRNAME; +const PUBLIC_PATH = process.env._PUBLIC_PATH; +const SRC_DIR = process.env._SRC_DIR; +const COMMON_DIR = process.env._COMMON_DIR; +const DIST_DIR = process.env._DIST_DIR; +const OUTPUT_ONLY = process.env._OUTPUT_ONLY; +const FAVICON = process.env.FAVICON; +const PRODUCT_NAME = process.env.PRODUCT_NAME; +const COVERAGE = process.env.COVERAGE; +const DEPLOYMENT_MODE = process.env._DEPLOYMENT_MODE; +const BASE_PATH = DEPLOYMENT_MODE === 'kubeflow' ? '/workspaces' : PUBLIC_PATH; + +if (OUTPUT_ONLY !== 'true') { + console.info( + `\nPrepping files...` + + `\n\tSRC DIR: ${SRC_DIR}` + + `\n\tOUTPUT DIR: ${DIST_DIR}` + + `\n\tPUBLIC PATH: ${PUBLIC_PATH}` + + `\n\tBASE_PATH: ${BASE_PATH}\n`, + ); + if (COVERAGE === 'true') { + console.info('\nAdding code coverage instrumentation.\n'); + } +} + +module.exports = (env) => ({ + entry: { + app: path.join(SRC_DIR, 'index.tsx'), + }, + module: { + rules: [ + { + test: /\.(tsx|ts|jsx|js)?$/, + exclude: [/node_modules/, /__tests__/, /__mocks__/], + include: [SRC_DIR, COMMON_DIR], + use: [ + COVERAGE === 'true' && '@jsdevtools/coverage-istanbul-loader', + env === 'development' + ? { loader: 'swc-loader' } + : { + loader: 'ts-loader', + options: { + transpileOnly: true, + }, }, - }, - ], + ], + }, + { + test: /\.(svg|ttf|eot|woff|woff2)$/, + // only process modules with this loader + // if they live under a 'fonts' or 'pficon' directory + include: [ + path.resolve(RELATIVE_DIRNAME, 'node_modules/patternfly/dist/fonts'), + path.resolve( + RELATIVE_DIRNAME, + 'node_modules/@patternfly/react-core/dist/styles/assets/fonts', + ), + path.resolve( + RELATIVE_DIRNAME, + 'node_modules/@patternfly/react-core/dist/styles/assets/pficon', + ), + path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly/patternfly/assets/fonts'), + path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly/patternfly/assets/pficon'), + ], + use: { + loader: 'file-loader', + options: { + // Limit at 50k. larger files emitted into separate files + limit: 5000, + outputPath: 'fonts', + name: '[name].[ext]', + }, }, - { - test: /\.(svg|ttf|eot|woff|woff2)$/, - type: 'asset/resource', - // only process modules with this loader - // if they live under a 'fonts' or 'pficon' directory - include: [ - path.resolve(relativeDir, 'node_modules/patternfly/dist/fonts'), - path.resolve( - relativeDir, - 'node_modules/@patternfly/react-core/dist/styles/assets/fonts', - ), - path.resolve( - relativeDir, - 'node_modules/@patternfly/react-core/dist/styles/assets/pficon', - ), - path.resolve(relativeDir, 'node_modules/@patternfly/patternfly/assets/fonts'), - path.resolve(relativeDir, 'node_modules/@patternfly/patternfly/assets/pficon'), - ], - use: { - loader: 'file-loader', + }, + { + test: /\.svg$/, + include: (input) => input.indexOf('background-filter.svg') > 1, + use: [ + { + loader: 'url-loader', options: { - // Limit at 50k. larger files emitted into separate files limit: 5000, - outputPath: 'fonts', + outputPath: 'svgs', name: '[name].[ext]', }, }, + ], + }, + { + test: /\.svg$/, + // only process SVG modules with this loader if they live under a 'bgimages' directory + // this is primarily useful when applying a CSS background using an SVG + include: (input) => input.indexOf(IMAGES_DIRNAME) > -1, + use: { + loader: 'svg-url-loader', + options: { + limit: 10000, + }, }, - { - test: /\.svg$/, - include: (input) => input.indexOf('background-filter.svg') > 1, - use: [ - { - loader: 'url-loader', - options: { - limit: 5000, - outputPath: 'svgs', - name: '[name].[ext]', - }, - }, - ], + }, + { + test: /\.svg$/, + // only process SVG modules with this loader when they don't live under a 'bgimages', + // 'fonts', or 'pficon' directory, those are handled with other loaders + include: (input) => + input.indexOf(IMAGES_DIRNAME) === -1 && + input.indexOf('fonts') === -1 && + input.indexOf('background-filter') === -1 && + input.indexOf('pficon') === -1, + use: { + loader: 'raw-loader', + options: {}, }, - { - test: /\.svg$/, - // only process SVG modules with this loader if they live under a 'bgimages' directory - // this is primarily useful when applying a CSS background using an SVG - include: (input) => input.indexOf(IMAGES_DIRNAME) > -1, - use: { - loader: 'svg-url-loader', + }, + { + test: /\.(jpg|jpeg|png|gif)$/i, + include: [ + SRC_DIR, + COMMON_DIR, + path.resolve(RELATIVE_DIRNAME, 'node_modules/patternfly'), + path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly/patternfly/assets/images'), + path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly/react-styles/css/assets/images'), + path.resolve( + RELATIVE_DIRNAME, + 'node_modules/@patternfly/react-core/dist/styles/assets/images', + ), + path.resolve( + RELATIVE_DIRNAME, + 'node_modules/@patternfly/react-core/node_modules/@patternfly/react-styles/css/assets/images', + ), + path.resolve( + RELATIVE_DIRNAME, + 'node_modules/@patternfly/react-table/node_modules/@patternfly/react-styles/css/assets/images', + ), + path.resolve( + RELATIVE_DIRNAME, + 'node_modules/@patternfly/react-inline-edit-extension/node_modules/@patternfly/react-styles/css/assets/images', + ), + ], + use: [ + { + loader: 'url-loader', options: { - limit: 10000, + limit: 5000, + outputPath: 'images', + name: '[name].[ext]', }, }, - }, - { - test: /\.svg$/, - // only process SVG modules with this loader when they don't live under a 'bgimages', - // 'fonts', or 'pficon' directory, those are handled with other loaders - include: (input) => - input.indexOf(IMAGES_DIRNAME) === -1 && - input.indexOf('fonts') === -1 && - input.indexOf('background-filter') === -1 && - input.indexOf('pficon') === -1, - use: { - loader: 'raw-loader', - options: {}, - }, - }, - { - test: /\.(jpg|jpeg|png|gif)$/i, - include: [ - path.resolve(relativeDir, 'src'), - path.resolve(relativeDir, 'node_modules/patternfly'), - path.resolve(relativeDir, 'node_modules/@patternfly/patternfly/assets/images'), - path.resolve(relativeDir, 'node_modules/@patternfly/react-styles/css/assets/images'), - path.resolve( - relativeDir, - 'node_modules/@patternfly/react-core/dist/styles/assets/images', - ), - path.resolve( - relativeDir, - 'node_modules/@patternfly/react-core/node_modules/@patternfly/react-styles/css/assets/images', - ), - path.resolve( - relativeDir, - 'node_modules/@patternfly/react-table/node_modules/@patternfly/react-styles/css/assets/images', - ), - path.resolve( - relativeDir, - 'node_modules/@patternfly/react-inline-edit-extension/node_modules/@patternfly/react-styles/css/assets/images', - ), - ], - type: 'asset/inline', - use: [ - { - options: { - limit: 5000, - outputPath: 'images', - name: '[name].[ext]', - }, - }, - ], - }, - { - test: /\.s[ac]ss$/i, - use: [ - // Creates `style` nodes from JS strings - 'style-loader', - // Translates CSS into CommonJS - 'css-loader', - // Compiles Sass to CSS - 'sass-loader', - ], - }, - { - test: /\.css$/i, - use: ['style-loader', 'css-loader'], - include: [ - path.resolve( - relativeDir, - 'node_modules/@patternfly/react-catalog-view-extension/dist/css/react-catalog-view-extension.css', - ), - ], - }, - ], - }, - output: { - filename: '[name].bundle.js', - path: path.resolve(relativeDir, 'dist'), - publicPath: APP_PREFIX, - }, - plugins: [ - new HtmlWebpackPlugin({ - template: path.resolve(relativeDir, 'src', 'index.html'), - }), - new Dotenv({ - systemvars: true, - silent: true, - }), - new CopyPlugin({ - patterns: [{ from: './src/images', to: 'images' }], - }), - new ForkTsCheckerWebpackPlugin(), - new EnvironmentPlugin({ - APP_PREFIX: process.env.APP_PREFIX || '/workspaces', - }), + ], + }, + { + test: /\.s[ac]ss$/i, + use: [ + // Creates `style` nodes from JS strings + 'style-loader', + // Translates CSS into CommonJS + 'css-loader', + // Compiles Sass to CSS + 'sass-loader', + ], + }, + { + test: /\.ya?ml$/, + use: 'js-yaml-loader', + }, ], - resolve: { - extensions: ['.js', '.ts', '.tsx', '.jsx'], - plugins: [ - new TsconfigPathsPlugin({ - configFile: path.resolve(relativeDir, './tsconfig.json'), - }), + }, + output: { + filename: '[name].bundle.js', + path: DIST_DIR, + publicPath: BASE_PATH, + uniqueName: name, + }, + plugins: [ + ...setupWebpackDotenvFilesForEnv({ + directory: RELATIVE_DIRNAME, + isRoot: IS_PROJECT_ROOT_DIR, + }), + new HtmlWebpackPlugin({ + template: path.join(SRC_DIR, 'index.html'), + title: PRODUCT_NAME, + favicon: path.join(SRC_DIR, 'images', FAVICON), + publicPath: BASE_PATH, + base: { + href: BASE_PATH, + }, + chunks: ['app'], + }), + new CopyPlugin({ + patterns: [ + { + from: path.join(SRC_DIR, 'locales'), + to: path.join(DIST_DIR, 'locales'), + noErrorOnMissing: true, + }, + { + from: path.join(SRC_DIR, 'favicons'), + to: path.join(DIST_DIR, 'favicons'), + noErrorOnMissing: true, + }, + { + from: path.join(SRC_DIR, 'images'), + to: path.join(DIST_DIR, 'images'), + noErrorOnMissing: true, + }, + { + from: path.join(SRC_DIR, 'favicon.ico'), + to: path.join(DIST_DIR), + noErrorOnMissing: true, + }, + { + from: path.join(SRC_DIR, 'favicon.png'), + to: path.join(DIST_DIR), + noErrorOnMissing: true, + }, + { + from: path.join(SRC_DIR, 'manifest.json'), + to: path.join(DIST_DIR), + noErrorOnMissing: true, + }, + { + from: path.join(SRC_DIR, 'robots.txt'), + to: path.join(DIST_DIR), + noErrorOnMissing: true, + }, ], - symlinks: false, - cacheWithContext: false, + }), + ], + resolve: { + extensions: ['.js', '.ts', '.tsx', '.jsx'], + alias: { + '~': path.resolve(SRC_DIR), }, - }; -}; + symlinks: false, + cacheWithContext: false, + }, +}); diff --git a/workspaces/frontend/config/webpack.dev.js b/workspaces/frontend/config/webpack.dev.js index 0fb6bdee..d3e00a9e 100644 --- a/workspaces/frontend/config/webpack.dev.js +++ b/workspaces/frontend/config/webpack.dev.js @@ -1,60 +1,141 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ - +const { execSync } = require('child_process'); const path = require('path'); -const { EnvironmentPlugin } = require('webpack'); const { merge } = require('webpack-merge'); -const common = require('./webpack.common.js'); -const { stylePaths } = require('./stylePaths'); -const HOST = process.env.HOST || 'localhost'; -const PORT = process.env.PORT || '9000'; -const PROXY_HOST = process.env.PROXY_HOST || 'localhost'; -const PROXY_PORT = process.env.PROXY_PORT || '4000'; -const PROXY_PROTOCOL = process.env.PROXY_PROTOCOL || 'http:'; -const MOCK_API_ENABLED = process.env.MOCK_API_ENABLED || 'false'; -const relativeDir = path.resolve(__dirname, '..'); -const APP_PREFIX = process.env.APP_PREFIX || '/workspaces'; +const { setupWebpackDotenvFilesForEnv, setupDotenvFilesForEnv } = require('./dotenv'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); +const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); +const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); -module.exports = merge(common('development'), { - mode: 'development', - devtool: 'eval-source-map', - devServer: { - host: HOST, - port: PORT, - historyApiFallback: { - index: APP_PREFIX + '/index.html', +const smp = new SpeedMeasurePlugin({ disable: !process.env.MEASURE }); + +setupDotenvFilesForEnv({ env: 'development' }); +const webpackCommon = require('./webpack.common.js'); + +const RELATIVE_DIRNAME = process.env._RELATIVE_DIRNAME; +const IS_PROJECT_ROOT_DIR = process.env._IS_PROJECT_ROOT_DIR; +const SRC_DIR = process.env._SRC_DIR; +const COMMON_DIR = process.env._COMMON_DIR; +const PUBLIC_PATH = process.env._PUBLIC_PATH; +const DIST_DIR = process.env._DIST_DIR; +const HOST = process.env._HOST; +const PORT = process.env._PORT; +const PROXY_PROTOCOL = process.env._PROXY_PROTOCOL; +const PROXY_HOST = process.env._PROXY_HOST; +const PROXY_PORT = process.env._PROXY_PORT; +const DEPLOYMENT_MODE = process.env._DEPLOYMENT_MODE; +const AUTH_METHOD = process.env._AUTH_METHOD; +const BASE_PATH = DEPLOYMENT_MODE === 'kubeflow' ? '/workspaces' : PUBLIC_PATH; + +// Function to generate headers based on deployment mode +const getProxyHeaders = () => { + if (AUTH_METHOD === 'internal') { + return { + 'kubeflow-userid': 'user@example.com', + }; + } + if (AUTH_METHOD === 'user_token') { + try { + const token = execSync( + "kubectl config view --raw --minify --flatten -o jsonpath='{.users[].user.token}'", + ) + .toString() + .trim(); + const username = execSync("kubectl auth whoami -o jsonpath='{.status.userInfo.username}'") + .toString() + .trim(); + // eslint-disable-next-line no-console + console.info('Logged in as user:', username); + return { + Authorization: `Bearer ${token}`, + 'x-forwarded-access-token': token, + }; + } catch (error) { + // eslint-disable-next-line no-console + console.error('Failed to get Kubernetes token:', error.message); + return {}; + } + } + return {}; +}; + +module.exports = smp.wrap( + merge( + { + plugins: [ + ...setupWebpackDotenvFilesForEnv({ + directory: RELATIVE_DIRNAME, + env: 'development', + isRoot: IS_PROJECT_ROOT_DIR, + }), + ], }, - open: [APP_PREFIX], - static: { - directory: path.resolve(relativeDir, 'dist'), - publicPath: APP_PREFIX, - }, - client: { - overlay: true, - }, - proxy: [ - { - context: ['/api'], - target: { - host: PROXY_HOST, - protocol: PROXY_PROTOCOL, - port: PROXY_PORT, + webpackCommon('development'), + { + mode: 'development', + devtool: 'eval-source-map', + optimization: { + runtimeChunk: 'single', + removeEmptyChunks: true, + }, + devServer: { + host: HOST, + port: PORT, + compress: true, + historyApiFallback: { + index: `${BASE_PATH}/index.html`, + }, + hot: true, + open: [BASE_PATH], + proxy: [ + { + context: ['/api', '/workspaces/api'], + target: { + host: PROXY_HOST, + protocol: PROXY_PROTOCOL, + port: PROXY_PORT, + }, + changeOrigin: true, + headers: getProxyHeaders(), + }, + ], + devMiddleware: { + stats: 'errors-only', + }, + client: { + overlay: false, + }, + static: { + directory: DIST_DIR, + publicPath: BASE_PATH, + }, + onListening: (devServer) => { + if (devServer) { + // eslint-disable-next-line no-console + console.log( + `\x1b[32m✓ Dashboard available at: \x1b[4mhttp://localhost:${ + devServer.server.address().port + }\x1b[0m`, + ); + } }, - changeOrigin: true, }, - ], - }, - module: { - rules: [ - { - test: /\.css$/, - include: [...stylePaths], - use: ['style-loader', 'css-loader'], + module: { + rules: [ + { + test: /\.css$/, + include: [ + SRC_DIR, + COMMON_DIR, + path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly'), + ], + use: ['style-loader', 'css-loader'], + }, + ], }, - ], - }, - plugins: [ - new EnvironmentPlugin({ - WEBPACK_REPLACE__mockApiEnabled: MOCK_API_ENABLED, - }), - ], -}); + plugins: [ + new ForkTsCheckerWebpackPlugin(), + new ReactRefreshWebpackPlugin({ overlay: false }), + ], + }, + ), +); diff --git a/workspaces/frontend/config/webpack.prod.js b/workspaces/frontend/config/webpack.prod.js index b0c839ea..a14ef40f 100644 --- a/workspaces/frontend/config/webpack.prod.js +++ b/workspaces/frontend/config/webpack.prod.js @@ -1,44 +1,61 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ - +const path = require('path'); const { merge } = require('webpack-merge'); -const common = require('./webpack.common.js'); -const { EnvironmentPlugin } = require('webpack'); -const { stylePaths } = require('./stylePaths'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const TerserJSPlugin = require('terser-webpack-plugin'); +const { setupWebpackDotenvFilesForEnv, setupDotenvFilesForEnv } = require('./dotenv'); -const PRODUCTION = process.env.PRODUCTION || 'false'; +setupDotenvFilesForEnv({ env: 'production' }); +const webpackCommon = require('./webpack.common.js'); -module.exports = merge(common('production'), { - mode: 'production', - devtool: 'source-map', - optimization: { - minimizer: [ - new TerserJSPlugin({}), - new CssMinimizerPlugin({ - minimizerOptions: { - preset: ['default', { mergeLonghand: false }], - }, +const RELATIVE_DIRNAME = process.env._RELATIVE_DIRNAME; +const IS_PROJECT_ROOT_DIR = process.env._IS_PROJECT_ROOT_DIR; +const SRC_DIR = process.env._SRC_DIR; +const COMMON_DIR = process.env._COMMON_DIR; +const DIST_DIR = process.env._DIST_DIR; +const OUTPUT_ONLY = process.env._OUTPUT_ONLY; + +if (OUTPUT_ONLY !== 'true') { + console.info(`Cleaning OUTPUT DIR...\n ${DIST_DIR}\n`); +} + +module.exports = merge( + { + plugins: [ + ...setupWebpackDotenvFilesForEnv({ + directory: RELATIVE_DIRNAME, + env: 'production', + isRoot: IS_PROJECT_ROOT_DIR, }), ], }, - plugins: [ - new MiniCssExtractPlugin({ - filename: '[name].css', - chunkFilename: '[name].bundle.css', - }), - new EnvironmentPlugin({ - PRODUCTION, - }), - ], - module: { - rules: [ - { - test: /\.css$/, - include: [...stylePaths], - use: [MiniCssExtractPlugin.loader, 'css-loader'], - }, + webpackCommon('production'), + { + mode: 'production', + devtool: 'source-map', + optimization: { + minimize: true, + minimizer: [new TerserJSPlugin(), new CssMinimizerPlugin()], + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: '[name].css', + chunkFilename: '[name].bundle.css', + ignoreOrder: true, + }), ], + module: { + rules: [ + { + test: /\.css$/, + include: [ + SRC_DIR, + COMMON_DIR, + path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly'), + ], + use: [MiniCssExtractPlugin.loader, 'css-loader'], + }, + ], + }, }, -}); +); diff --git a/workspaces/frontend/package-lock.json b/workspaces/frontend/package-lock.json index 1b6c4ad9..9ad96a67 100644 --- a/workspaces/frontend/package-lock.json +++ b/workspaces/frontend/package-lock.json @@ -9,6 +9,8 @@ "version": "0.0.1", "license": "Apache-2.0", "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", "@patternfly/patternfly": "^6.3.1", "@patternfly/react-catalog-view-extension": "^6.2.0", "@patternfly/react-code-editor": "^6.3.1", @@ -16,102 +18,120 @@ "@patternfly/react-icons": "^6.3.1", "@patternfly/react-styles": "^6.3.1", "@patternfly/react-table": "^6.3.1", + "@patternfly/react-templates": "^6.3.1", "@patternfly/react-tokens": "^6.3.1", "@types/js-yaml": "^4.0.9", "axios": "^1.10.0", + "classnames": "^2.2.6", "date-fns": "^4.1.0", - "eslint-plugin-local-rules": "^3.0.2", + "dompurify": "^3.2.4", "js-yaml": "^4.1.0", - "npm-run-all": "^4.1.5", + "lodash-es": "^4.17.15", "react": "^18", "react-dom": "^18", - "react-router": "^6.26.2", - "sirv-cli": "^2.0.2" + "react-router": "^7.5.2", + "react-router-dom": "^7.6.1", + "sass": "^1.83.0", + "showdown": "^2.1.0" }, "devDependencies": { - "@cspell/eslint-plugin": "^9.1.2", - "@cypress/code-coverage": "^3.13.5", - "@mui/icons-material": "^6.3.1", + "@mui/icons-material": "^6.4.8", "@mui/material": "^6.3.1", "@mui/types": "^7.2.21", - "@testing-library/cypress": "^10.0.1", - "@testing-library/dom": "^10.4.0", - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "14.4.3", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", + "@swc/core": "^1.9.1", "@types/chai-subset": "^1.3.5", - "@types/jest": "^29.5.3", - "@types/react-router-dom": "^5.3.3", - "@types/victory": "^33.1.5", + "@types/classnames": "^2.3.1", + "@types/dompurify": "^3.2.0", + "@types/jest": "^29.5.13", + "@types/js-yaml": "^4.0.9", + "@types/lodash-es": "^4.17.8", + "@types/react-dom": "^18.3.1", + "@types/showdown": "^2.0.3", + "@typescript-eslint/eslint-plugin": "^8.35.0", "chai-subset": "^1.6.0", "concurrently": "^9.1.0", - "copy-webpack-plugin": "^11.0.0", - "core-js": "^3.39.0", - "cross-env": "^7.0.3", - "css-loader": "^6.11.0", - "css-minimizer-webpack-plugin": "^5.0.1", - "cypress": "^13.16.1", - "cypress-axe": "^1.5.0", - "cypress-high-resolution": "^1.0.0", - "cypress-mochawesome-reporter": "^3.8.2", - "cypress-multi-reporters": "^2.0.4", - "dotenv": "^16.4.5", - "dotenv-webpack": "^8.1.0", - "expect": "^29.7.0", - "fork-ts-checker-webpack-plugin": "^9.0.3", - "html-webpack-plugin": "^5.6.0", + "copy-webpack-plugin": "^13.0.0", + "core-js": "^3.40.0", + "css-loader": "^7.1.2", + "css-minimizer-webpack-plugin": "^7.0.0", + "dotenv": "^16.5.0", + "dotenv-expand": "^5.1.0", + "dotenv-webpack": "^6.0.0", + "expect": "^30.0.2", + "file-loader": "^6.2.0", + "fork-ts-checker-webpack-plugin": "^9.0.2", + "html-webpack-plugin": "^5.6.3", "husky": "^9.1.7", - "imagemin": "^8.0.1", + "imagemin": "^9.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "junit-report-merger": "^7.0.0", + "junit-report-merger": "^7.0.1", "mini-css-extract-plugin": "^2.9.0", - "postcss": "^8.4.38", - "prettier": "^3.3.0", + "postcss": "^8.4.49", + "prettier": "^3.3.3", "prop-types": "^15.8.1", "raw-loader": "^4.0.2", - "react-router-dom": "^6.26.1", - "regenerator-runtime": "^0.13.11", + "react-refresh": "^0.14.2", + "regenerator-runtime": "^0.14.1", "rimraf": "^6.0.1", - "sass": "^1.83.4", - "sass-loader": "^16.0.4", - "serve": "^14.2.1", - "style-loader": "^3.3.4", + "sass": "^1.87.0", + "sass-loader": "^16.0.0", + "speed-measure-webpack-plugin": "^1.5.0", + "style-loader": "^4.0.0", "svg-url-loader": "^8.0.0", - "terser-webpack-plugin": "^5.3.10", - "ts-jest": "^29.1.4", - "ts-loader": "^9.5.1", + "swagger-typescript-api": "13.2.7", + "swc-loader": "^0.2.6", + "terser-webpack-plugin": "^5.3.11", + "ts-loader": "^9.5.2", "tsconfig-paths-webpack-plugin": "^4.1.0", - "tslib": "^2.6.0", - "typescript": "^5.4.5", + "tslib": "^2.7.0", + "typescript": "^5.8.2", "url-loader": "^4.1.1", - "webpack": "^5.91.0", + "webpack": "^5.97.1", "webpack-bundle-analyzer": "^4.10.2", - "webpack-cli": "^5.1.4", - "webpack-dev-server": "^5.2.2", - "webpack-merge": "^5.10.0" + "webpack-cli": "^6.0.1", + "webpack-dev-server": "^5.2.0", + "webpack-merge": "^6.0.1" }, "engines": { "node": ">=20.0.0" }, "optionalDependencies": { - "@emotion/react": "^11.14.0", - "@emotion/styled": "^11.14.0", - "@typescript-eslint/eslint-plugin": "^8.8.1", - "@typescript-eslint/parser": "^8.12.2", + "@babel/preset-env": "^7.26.9", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.5", + "@cspell/eslint-plugin": "^9.1.2", + "@cypress/code-coverage": "^3.14.1", + "@cypress/webpack-preprocessor": "^6.0.4", + "@testing-library/cypress": "^10.0.3", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.2", + "@testing-library/react": "^16.2.0", + "@testing-library/user-event": "14.6.1", + "@typescript-eslint/eslint-plugin": "^8.31.1", + "@typescript-eslint/parser": "^8.31.1", + "cypress": "^14.4.1", + "cypress-axe": "^1.6.0", + "cypress-high-resolution": "^1.0.0", + "cypress-mochawesome-reporter": "^3.8.2", + "cypress-multi-reporters": "^2.0.5", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-node": "^0.3.7", - "eslint-import-resolver-typescript": "^3.5.3", + "eslint-import-resolver-typescript": "^3.8.3", "eslint-plugin-cypress": "^3.3.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-local-rules": "^3.0.2", "eslint-plugin-no-only-tests": "^3.1.0", - "eslint-plugin-no-relative-import-paths": "^1.5.2", - "eslint-plugin-prettier": "^5.0.0", - "eslint-plugin-react": "^7.37.2", - "eslint-plugin-react-hooks": "^5.0.0", - "swagger-typescript-api": "^13.2.7" + "eslint-plugin-no-relative-import-paths": "^1.6.1", + "eslint-plugin-prettier": "^5.4.0", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", + "npm-run-all": "^4.1.5", + "serve": "^14.2.4", + "ts-jest": "^29.4.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -124,16 +144,16 @@ } }, "node_modules/@adobe/css-tools": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", - "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", - "dev": true + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "optional": true }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/gen-mapping": "^0.1.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -143,46 +163,43 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "devOptional": true, - "license": "MIT", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", - "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", - "dev": true, - "license": "MIT", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "devOptional": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.6.tgz", - "integrity": "sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ==", - "dev": true, + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", + "devOptional": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.6", - "@babel/generator": "^7.24.6", - "@babel/helper-compilation-targets": "^7.24.6", - "@babel/helper-module-transforms": "^7.24.6", - "@babel/helpers": "^7.24.6", - "@babel/parser": "^7.24.6", - "@babel/template": "^7.24.6", - "@babel/traverse": "^7.24.6", - "@babel/types": "^7.24.6", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -201,19 +218,17 @@ "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 + "devOptional": true }, "node_modules/@babel/generator": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", - "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", - "devOptional": true, - "license": "MIT", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", "dependencies": { - "@babel/parser": "^7.26.2", - "@babel/types": "^7.26.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -221,42 +236,34 @@ } }, "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "devOptional": true, + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, - "license": "MIT", - "peer": true, + "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==", + "optional": true, "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", - "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", - "dev": true, - "license": "MIT", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "devOptional": true, "dependencies": { - "@babel/compat-data": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -269,7 +276,7 @@ "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, + "devOptional": true, "dependencies": { "yallist": "^3.0.2" } @@ -278,22 +285,20 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "devOptional": true }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", - "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", + "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", + "optional": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.25.9", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.3", "semver": "^6.3.1" }, "engines": { @@ -304,14 +309,12 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", - "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "optional": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-annotate-as-pure": "^7.27.1", "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, @@ -323,81 +326,100 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", - "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "optional": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "license": "MIT", - "peer": true, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "optional": true, "dependencies": { - "is-core-module": "^2.13.0", + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/helper-define-polyfill-provider/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==", + "optional": true, + "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/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", - "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", - "dev": true, - "license": "MIT", - "peer": true, + "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==", + "optional": true, "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", - "devOptional": true, - "license": "MIT", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", - "dev": true, - "license": "MIT", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "devOptional": true, "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -407,40 +429,35 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", - "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", - "dev": true, - "license": "MIT", - "peer": true, + "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==", + "optional": true, "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", - "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", - "dev": true, - "license": "MIT", + "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==", + "devOptional": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", - "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "optional": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-wrap-function": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -450,16 +467,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz", - "integrity": "sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "optional": true, "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -469,88 +484,76 @@ } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", - "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", - "dev": true, - "license": "MIT", - "peer": true, + "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==", + "optional": true, "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "devOptional": true, - "license": "MIT", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "devOptional": true, - "license": "MIT", + "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==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", - "dev": true, - "license": "MIT", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "devOptional": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", - "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", + "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", + "optional": true, "dependencies": { - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz", - "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==", - "dev": true, - "license": "MIT", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", + "devOptional": true, "dependencies": { - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.10" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz", - "integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==", - "devOptional": true, - "license": "MIT", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", "dependencies": { - "@babel/types": "^7.26.10" + "@babel/types": "^7.28.2" }, "bin": { "parser": "bin/babel-parser.js" @@ -560,15 +563,13 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", - "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -578,14 +579,12 @@ } }, "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", - "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -595,14 +594,12 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", - "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -612,16 +609,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -631,15 +626,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", - "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", + "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -652,9 +645,8 @@ "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, "license": "MIT", - "peer": true, + "optional": true, "engines": { "node": ">=6.9.0" }, @@ -666,7 +658,7 @@ "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -678,7 +670,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -690,7 +682,7 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -699,14 +691,12 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", - "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -716,14 +706,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", - "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -736,7 +724,7 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -748,7 +736,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -757,12 +745,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.6.tgz", - "integrity": "sha512-lWfvAIFNWMlCsU0DRUun2GpFwZdGTukLaHJqRh1JRb80NdAP5Sb1HDHB5X9P9OtgZHQl089UzQkpYlBq2VTPRw==", - "dev": true, + "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==", + "devOptional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -775,7 +763,7 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -787,7 +775,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -799,7 +787,7 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -811,7 +799,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -823,7 +811,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -835,7 +823,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -847,7 +835,7 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -859,12 +847,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.6.tgz", - "integrity": "sha512-TzCtxGgVTEJWWwcYwQhCIQ6WaKlo80/B+Onsk4RRCcYqpYGFcG9etPW94VToGte5AAcxRrhjPUFvUS3Y2qKi4A==", - "dev": true, + "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==", + "devOptional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -877,9 +865,8 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, "license": "MIT", - "peer": true, + "optional": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -892,14 +879,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", - "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -909,16 +894,14 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz", - "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", + "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-remap-async-to-generator": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -928,16 +911,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", - "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "optional": true, "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-remap-async-to-generator": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -947,14 +928,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz", - "integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -964,14 +943,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", - "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz", + "integrity": "sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -981,15 +958,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", - "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "optional": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -999,15 +974,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", - "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", + "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "optional": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1017,19 +990,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", - "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.3.tgz", + "integrity": "sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg==", + "optional": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/traverse": "^7.25.9", - "globals": "^11.1.0" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -1039,15 +1010,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", - "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/template": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1057,14 +1026,13 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", - "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", + "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -1074,15 +1042,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", - "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "optional": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1092,14 +1058,12 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", - "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1109,15 +1073,13 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "optional": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1127,14 +1089,28 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", - "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", + "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "optional": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -1144,14 +1120,12 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", - "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1161,14 +1135,12 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", - "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1178,15 +1150,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", - "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1196,16 +1166,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", - "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "optional": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1215,14 +1183,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", - "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1232,14 +1198,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", - "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1249,14 +1213,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", - "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1266,14 +1228,12 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", - "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1283,15 +1243,13 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", - "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "optional": true, "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1301,15 +1259,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", - "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", - "dev": true, - "license": "MIT", - "peer": true, + "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==", + "optional": true, "dependencies": { - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1319,17 +1275,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", - "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "optional": true, "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1339,15 +1293,13 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", - "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "optional": true, "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1357,15 +1309,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "optional": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1375,14 +1325,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", - "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1392,14 +1340,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz", - "integrity": "sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1409,14 +1355,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", - "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1426,16 +1370,16 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", - "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz", + "integrity": "sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==", + "optional": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9" + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -1445,15 +1389,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", - "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1463,14 +1405,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", - "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1480,15 +1420,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1498,14 +1436,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", - "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1515,15 +1451,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", - "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "optional": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1533,16 +1467,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", - "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "optional": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1552,14 +1484,77 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", - "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", + "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "optional": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", + "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", + "optional": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "optional": true, + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", + "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "optional": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1569,15 +1564,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", - "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.3.tgz", + "integrity": "sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "regenerator-transform": "^0.15.2" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1587,15 +1579,13 @@ } }, "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", - "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "optional": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1605,14 +1595,12 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", - "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1622,14 +1610,12 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", - "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1639,15 +1625,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", - "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1657,14 +1641,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", - "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1674,14 +1656,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", - "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1691,14 +1671,31 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", - "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", + "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", + "optional": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1708,14 +1705,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", - "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1725,15 +1720,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", - "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "optional": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1743,15 +1736,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", - "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "optional": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1761,15 +1752,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", - "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "optional": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1779,81 +1768,80 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz", - "integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", + "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", + "optional": true, "dependencies": { - "@babel/compat-data": "^7.26.0", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", + "@babel/compat-data": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.26.0", - "@babel/plugin-syntax-import-attributes": "^7.26.0", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.25.9", - "@babel/plugin-transform-async-generator-functions": "^7.25.9", - "@babel/plugin-transform-async-to-generator": "^7.25.9", - "@babel/plugin-transform-block-scoped-functions": "^7.25.9", - "@babel/plugin-transform-block-scoping": "^7.25.9", - "@babel/plugin-transform-class-properties": "^7.25.9", - "@babel/plugin-transform-class-static-block": "^7.26.0", - "@babel/plugin-transform-classes": "^7.25.9", - "@babel/plugin-transform-computed-properties": "^7.25.9", - "@babel/plugin-transform-destructuring": "^7.25.9", - "@babel/plugin-transform-dotall-regex": "^7.25.9", - "@babel/plugin-transform-duplicate-keys": "^7.25.9", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-dynamic-import": "^7.25.9", - "@babel/plugin-transform-exponentiation-operator": "^7.25.9", - "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-for-of": "^7.25.9", - "@babel/plugin-transform-function-name": "^7.25.9", - "@babel/plugin-transform-json-strings": "^7.25.9", - "@babel/plugin-transform-literals": "^7.25.9", - "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", - "@babel/plugin-transform-member-expression-literals": "^7.25.9", - "@babel/plugin-transform-modules-amd": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.25.9", - "@babel/plugin-transform-modules-systemjs": "^7.25.9", - "@babel/plugin-transform-modules-umd": "^7.25.9", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-new-target": "^7.25.9", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9", - "@babel/plugin-transform-numeric-separator": "^7.25.9", - "@babel/plugin-transform-object-rest-spread": "^7.25.9", - "@babel/plugin-transform-object-super": "^7.25.9", - "@babel/plugin-transform-optional-catch-binding": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9", - "@babel/plugin-transform-private-methods": "^7.25.9", - "@babel/plugin-transform-private-property-in-object": "^7.25.9", - "@babel/plugin-transform-property-literals": "^7.25.9", - "@babel/plugin-transform-regenerator": "^7.25.9", - "@babel/plugin-transform-regexp-modifiers": "^7.26.0", - "@babel/plugin-transform-reserved-words": "^7.25.9", - "@babel/plugin-transform-shorthand-properties": "^7.25.9", - "@babel/plugin-transform-spread": "^7.25.9", - "@babel/plugin-transform-sticky-regex": "^7.25.9", - "@babel/plugin-transform-template-literals": "^7.25.9", - "@babel/plugin-transform-typeof-symbol": "^7.25.9", - "@babel/plugin-transform-unicode-escapes": "^7.25.9", - "@babel/plugin-transform-unicode-property-regex": "^7.25.9", - "@babel/plugin-transform-unicode-regex": "^7.25.9", - "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.0", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.0", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.3", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.28.0", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.3", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.6", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.38.1", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", "semver": "^6.3.1" }, "engines": { @@ -1867,9 +1855,8 @@ "version": "0.1.6-no-external-plugins", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, "license": "MIT", - "peer": true, + "optional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", @@ -1879,11 +1866,49 @@ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/preset-react": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz", + "integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==", + "optional": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.27.1", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "optional": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { "version": "7.26.10", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", - "devOptional": true, "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -1892,55 +1917,43 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/runtime/node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "devOptional": true - }, "node_modules/@babel/template": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", - "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", - "devOptional": true, - "license": "MIT", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", - "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", - "devOptional": true, - "license": "MIT", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz", - "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==", - "devOptional": true, - "license": "MIT", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1950,13 +1963,13 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "devOptional": true }, "node_modules/@biomejs/js-api": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@biomejs/js-api/-/js-api-1.0.0.tgz", "integrity": "sha512-69OfQ7+09AtiCIg+k+aU3rEsGit5o/SJWCS3BeBH/2nJYdJGi0cIx+ybka8i1EK69aNcZxYO1y1iAAEmYMq1HA==", - "optional": true, + "dev": true, "peerDependencies": { "@biomejs/wasm-bundler": "^2.0.0", "@biomejs/wasm-nodejs": "^2.0.0", @@ -1978,25 +1991,24 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@biomejs/wasm-nodejs/-/wasm-nodejs-2.0.5.tgz", "integrity": "sha512-pihpBMylewgDdGFZHRkgmc3OajuGIJPXhvfYuKCNK/CWyJMrYEFmPKs8Iq1kY0sYMmGlTbD4K2udV03KYa+r0Q==", - "optional": true + "dev": true }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "node_modules/@borewit/text-codec": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", + "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==", "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" } }, "node_modules/@cspell/cspell-bundled-dicts": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-9.1.2.tgz", "integrity": "sha512-mdhxj7j1zqXYKO/KPx2MgN3RPAvqoWvncxz2dOMFBcuUteZPt58NenUoi0VZXEhV/FM2V80NvhHZZafaIcxVjQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "@cspell/dict-ada": "^4.1.0", "@cspell/dict-al": "^1.1.0", @@ -2065,8 +2077,8 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-9.1.2.tgz", "integrity": "sha512-/pIhsf4SI4Q/kvehq9GsGKLgbQsRhiDgthQIgO6YOrEa761wOI2hVdRyc0Tgc1iAGiJEedDaFsAhabVRJBeo2g==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=20" } @@ -2075,8 +2087,8 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-9.1.2.tgz", "integrity": "sha512-dNDx7yMl2h1Ousk08lizTou+BUvce4RPSnPXrQPB7B7CscgZloSyuP3Yyj1Zt81pHNpggrym4Ezx6tMdyPjESw==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "global-directory": "^4.0.1" }, @@ -2088,8 +2100,8 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-9.1.2.tgz", "integrity": "sha512-YOsUctzCMzEJbKdzNyvPkyMen/i7sGO3Xgcczn848GJPlRsJc50QwsoU67SY7zEARz6y2WS0tv5F5RMrRO4idw==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=20" } @@ -2098,8 +2110,8 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-9.1.2.tgz", "integrity": "sha512-bSDDjoQi4pbh1BULEA596XCo1PMShTpTb4J2lj8jVYqYgXYQNjSmQFA1fj4NHesC84JpK1um4ybzXBcqtniC7Q==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=20" } @@ -2108,29 +2120,29 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@cspell/dict-ada/-/dict-ada-4.1.0.tgz", "integrity": "sha512-7SvmhmX170gyPd+uHXrfmqJBY5qLcCX8kTGURPVeGxmt8XNXT75uu9rnZO+jwrfuU2EimNoArdVy5GZRGljGNg==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-al": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@cspell/dict-al/-/dict-al-1.1.0.tgz", "integrity": "sha512-PtNI1KLmYkELYltbzuoztBxfi11jcE9HXBHCpID2lou/J4VMYKJPNqe4ZjVzSI9NYbMnMnyG3gkbhIdx66VSXg==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-aws": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-4.0.10.tgz", "integrity": "sha512-0qW4sI0GX8haELdhfakQNuw7a2pnWXz3VYQA2MpydH2xT2e6EN9DWFpKAi8DfcChm8MgDAogKkoHtIo075iYng==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-bash": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@cspell/dict-bash/-/dict-bash-4.2.0.tgz", "integrity": "sha512-HOyOS+4AbCArZHs/wMxX/apRkjxg6NDWdt0jF9i9XkvJQUltMwEhyA2TWYjQ0kssBsnof+9amax2lhiZnh3kCg==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "@cspell/dict-shell": "1.1.0" } @@ -2139,246 +2151,246 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-3.2.1.tgz", "integrity": "sha512-ryaeJ1KhTTKL4mtinMtKn8wxk6/tqD4vX5tFP+Hg89SiIXmbMk5vZZwVf+eyGUWJOyw5A1CVj9EIWecgoi+jYQ==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-cpp": { "version": "6.0.8", "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-6.0.8.tgz", "integrity": "sha512-BzurRZilWqaJt32Gif6/yCCPi+FtrchjmnehVEIFzbWyeBd/VOUw77IwrEzehZsu5cRU91yPWuWp5fUsKfDAXA==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-cryptocurrencies": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@cspell/dict-cryptocurrencies/-/dict-cryptocurrencies-5.0.4.tgz", "integrity": "sha512-6iFu7Abu+4Mgqq08YhTKHfH59mpMpGTwdzDB2Y8bbgiwnGFCeoiSkVkgLn1Kel2++hYcZ8vsAW/MJS9oXxuMag==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-csharp": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-4.0.6.tgz", "integrity": "sha512-w/+YsqOknjQXmIlWDRmkW+BHBPJZ/XDrfJhZRQnp0wzpPOGml7W0q1iae65P2AFRtTdPKYmvSz7AL5ZRkCnSIw==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-css": { "version": "4.0.17", "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.17.tgz", "integrity": "sha512-2EisRLHk6X/PdicybwlajLGKF5aJf4xnX2uuG5lexuYKt05xV/J/OiBADmi8q9obhxf1nesrMQbqAt+6CsHo/w==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-dart": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@cspell/dict-dart/-/dict-dart-2.3.0.tgz", "integrity": "sha512-1aY90lAicek8vYczGPDKr70pQSTQHwMFLbmWKTAI6iavmb1fisJBS1oTmMOKE4ximDf86MvVN6Ucwx3u/8HqLg==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-data-science": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/@cspell/dict-data-science/-/dict-data-science-2.0.8.tgz", "integrity": "sha512-uyAtT+32PfM29wRBeAkUSbkytqI8bNszNfAz2sGPtZBRmsZTYugKMEO9eDjAIE/pnT9CmbjNuoiXhk+Ss4fCOg==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-django": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-4.1.4.tgz", "integrity": "sha512-fX38eUoPvytZ/2GA+g4bbdUtCMGNFSLbdJJPKX2vbewIQGfgSFJKY56vvcHJKAvw7FopjvgyS/98Ta9WN1gckg==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-docker": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/@cspell/dict-docker/-/dict-docker-1.1.14.tgz", "integrity": "sha512-p6Qz5mokvcosTpDlgSUREdSbZ10mBL3ndgCdEKMqjCSZJFdfxRdNdjrGER3lQ6LMq5jGr1r7nGXA0gvUJK80nw==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-dotnet": { "version": "5.0.9", "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-5.0.9.tgz", "integrity": "sha512-JGD6RJW5sHtO5lfiJl11a5DpPN6eKSz5M1YBa1I76j4dDOIqgZB6rQexlDlK1DH9B06X4GdDQwdBfnpAB0r2uQ==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-elixir": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/@cspell/dict-elixir/-/dict-elixir-4.0.7.tgz", "integrity": "sha512-MAUqlMw73mgtSdxvbAvyRlvc3bYnrDqXQrx5K9SwW8F7fRYf9V4vWYFULh+UWwwkqkhX9w03ZqFYRTdkFku6uA==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-en_us": { "version": "4.4.13", "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.4.13.tgz", "integrity": "sha512-6TEHCJKmRqq7fQI7090p+ju12vhuGcNkc6YfxHrcjO816m53VPVaS6IfG6+6OqelQiOMjr0ZD8IHcDIkwThSFw==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-en-common-misspellings": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.1.2.tgz", "integrity": "sha512-r74AObInM1XOUxd3lASnNZNDOIA9Bka7mBDTkvkOeCGoLQhn+Cr7h1889u4K07KHbecKMHP6zw5zQhkdocNzCw==", - "dev": true, - "license": "CC BY-SA 4.0" + "license": "CC BY-SA 4.0", + "optional": true }, "node_modules/@cspell/dict-en-gb-mit": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb-mit/-/dict-en-gb-mit-3.1.3.tgz", "integrity": "sha512-4aY8ySQxSNSRILtf9lJIfSR+su86u8VL6z41gOIhvLIvYnHMFiohV7ebM91GbtdZXBazL7zmGFcpm2EnBzewug==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-filetypes": { "version": "3.0.12", "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.12.tgz", "integrity": "sha512-+ds5wgNdlUxuJvhg8A1TjuSpalDFGCh7SkANCWvIplg6QZPXL4j83lqxP7PgjHpx7PsBUS7vw0aiHPjZy9BItw==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-flutter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@cspell/dict-flutter/-/dict-flutter-1.1.0.tgz", "integrity": "sha512-3zDeS7zc2p8tr9YH9tfbOEYfopKY/srNsAa+kE3rfBTtQERAZeOhe5yxrnTPoufctXLyuUtcGMUTpxr3dO0iaA==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-fonts": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-4.0.4.tgz", "integrity": "sha512-cHFho4hjojBcHl6qxidl9CvUb492IuSk7xIf2G2wJzcHwGaCFa2o3gRcxmIg1j62guetAeDDFELizDaJlVRIOg==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-fsharp": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@cspell/dict-fsharp/-/dict-fsharp-1.1.0.tgz", "integrity": "sha512-oguWmHhGzgbgbEIBKtgKPrFSVAFtvGHaQS0oj+vacZqMObwkapcTGu7iwf4V3Bc2T3caf0QE6f6rQfIJFIAVsw==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-fullstack": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-3.2.6.tgz", "integrity": "sha512-cSaq9rz5RIU9j+0jcF2vnKPTQjxGXclntmoNp4XB7yFX2621PxJcekGjwf/lN5heJwVxGLL9toR0CBlGKwQBgA==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-gaming-terms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@cspell/dict-gaming-terms/-/dict-gaming-terms-1.1.1.tgz", "integrity": "sha512-tb8GFxjTLDQstkJcJ90lDqF4rKKlMUKs5/ewePN9P+PYRSehqDpLI5S5meOfPit8LGszeOrjUdBQ4zXo7NpMyQ==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-git": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-3.0.6.tgz", "integrity": "sha512-nazfOqyxlBOQGgcur9ssEOEQCEZkH8vXfQe8SDEx8sCN/g0SFm8ktabgLVmBOXjy3RzjVNLlM2nBfRQ7e6+5hQ==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-golang": { "version": "6.0.22", "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.22.tgz", "integrity": "sha512-FvV0m3Y0nUFxw36uDCD8UtfOPv4wsZnnlabNwB3xNZ2IBn0gBURuMUZywScb9sd2wXM8VFBRoU//tc6NQsOVOg==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-google": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@cspell/dict-google/-/dict-google-1.0.8.tgz", "integrity": "sha512-BnMHgcEeaLyloPmBs8phCqprI+4r2Jb8rni011A8hE+7FNk7FmLE3kiwxLFrcZnnb7eqM0agW4zUaNoB0P+z8A==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-haskell": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@cspell/dict-haskell/-/dict-haskell-4.0.5.tgz", "integrity": "sha512-s4BG/4tlj2pPM9Ha7IZYMhUujXDnI0Eq1+38UTTCpatYLbQqDwRFf2KNPLRqkroU+a44yTUAe0rkkKbwy4yRtQ==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-html": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.11.tgz", "integrity": "sha512-QR3b/PB972SRQ2xICR1Nw/M44IJ6rjypwzA4jn+GH8ydjAX9acFNfc+hLZVyNe0FqsE90Gw3evLCOIF0vy1vQw==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-html-symbol-entities": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.3.tgz", "integrity": "sha512-aABXX7dMLNFdSE8aY844X4+hvfK7977sOWgZXo4MTGAmOzR8524fjbJPswIBK7GaD3+SgFZ2yP2o0CFvXDGF+A==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-java": { "version": "5.0.11", "resolved": "https://registry.npmjs.org/@cspell/dict-java/-/dict-java-5.0.11.tgz", "integrity": "sha512-T4t/1JqeH33Raa/QK/eQe26FE17eUCtWu+JsYcTLkQTci2dk1DfcIKo8YVHvZXBnuM43ATns9Xs0s+AlqDeH7w==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-julia": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@cspell/dict-julia/-/dict-julia-1.1.0.tgz", "integrity": "sha512-CPUiesiXwy3HRoBR3joUseTZ9giFPCydSKu2rkh6I2nVjXnl5vFHzOMLXpbF4HQ1tH2CNfnDbUndxD+I+7eL9w==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-k8s": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@cspell/dict-k8s/-/dict-k8s-1.0.11.tgz", "integrity": "sha512-8ojNwB5j4PfZ1Gq9n5c/HKJCtZD3h6+wFy+zpALpDWFFQ2qT22Be30+3PVd+G5gng8or0LeK8VgKKd0l1uKPTA==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-kotlin": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@cspell/dict-kotlin/-/dict-kotlin-1.1.0.tgz", "integrity": "sha512-vySaVw6atY7LdwvstQowSbdxjXG6jDhjkWVWSjg1XsUckyzH1JRHXe9VahZz1i7dpoFEUOWQrhIe5B9482UyJQ==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-latex": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@cspell/dict-latex/-/dict-latex-4.0.3.tgz", "integrity": "sha512-2KXBt9fSpymYHxHfvhUpjUFyzrmN4c4P8mwIzweLyvqntBT3k0YGZJSriOdjfUjwSygrfEwiuPI1EMrvgrOMJw==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-lorem-ipsum": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@cspell/dict-lorem-ipsum/-/dict-lorem-ipsum-4.0.4.tgz", "integrity": "sha512-+4f7vtY4dp2b9N5fn0za/UR0kwFq2zDtA62JCbWHbpjvO9wukkbl4rZg4YudHbBgkl73HRnXFgCiwNhdIA1JPw==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-lua": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/@cspell/dict-lua/-/dict-lua-4.0.7.tgz", "integrity": "sha512-Wbr7YSQw+cLHhTYTKV6cAljgMgcY+EUAxVIZW3ljKswEe4OLxnVJ7lPqZF5JKjlXdgCjbPSimsHqyAbC5pQN/Q==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-makefile": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@cspell/dict-makefile/-/dict-makefile-1.0.4.tgz", "integrity": "sha512-E4hG/c0ekPqUBvlkrVvzSoAA+SsDA9bLi4xSV3AXHTVru7Y2bVVGMPtpfF+fI3zTkww/jwinprcU1LSohI3ylw==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-markdown": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/@cspell/dict-markdown/-/dict-markdown-2.0.11.tgz", "integrity": "sha512-stZieFKJyMQbzKTVoalSx2QqCpB0j8nPJF/5x+sBnDIWgMC65jp8Wil+jccWh9/vnUVukP3Ejewven5NC7SWuQ==", - "dev": true, "license": "MIT", + "optional": true, "peerDependencies": { "@cspell/dict-css": "^4.0.17", "@cspell/dict-html": "^4.0.11", @@ -2390,50 +2402,50 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.0.10.tgz", "integrity": "sha512-7RTGyKsTIIVqzbvOtAu6Z/lwwxjGRtY5RkKPlXKHEoEAgIXwfDxb5EkVwzGQwQr8hF/D3HrdYbRT8MFBfsueZw==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-node": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-5.0.7.tgz", "integrity": "sha512-ZaPpBsHGQCqUyFPKLyCNUH2qzolDRm1/901IO8e7btk7bEDF56DN82VD43gPvD4HWz3yLs/WkcLa01KYAJpnOw==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-npm": { "version": "5.2.9", "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.2.9.tgz", "integrity": "sha512-1uxRQ0LGPweRX8U9EEoU/tk5GGtTLAJT0BMmeHbe2AfzxX3nYSZtK/q52h9yg/wZLgvnFYzha2DL70uuT8oZuA==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-php": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.0.14.tgz", "integrity": "sha512-7zur8pyncYZglxNmqsRycOZ6inpDoVd4yFfz1pQRe5xaRWMiK3Km4n0/X/1YMWhh3e3Sl/fQg5Axb2hlN68t1g==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-powershell": { "version": "5.0.14", "resolved": "https://registry.npmjs.org/@cspell/dict-powershell/-/dict-powershell-5.0.14.tgz", "integrity": "sha512-ktjjvtkIUIYmj/SoGBYbr3/+CsRGNXGpvVANrY0wlm/IoGlGywhoTUDYN0IsGwI2b8Vktx3DZmQkfb3Wo38jBA==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-public-licenses": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.13.tgz", "integrity": "sha512-1Wdp/XH1ieim7CadXYE7YLnUlW0pULEjVl9WEeziZw3EKCAw8ZI8Ih44m4bEa5VNBLnuP5TfqC4iDautAleQzQ==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-python": { "version": "4.2.18", "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-4.2.18.tgz", "integrity": "sha512-hYczHVqZBsck7DzO5LumBLJM119a3F17aj8a7lApnPIS7cmEwnPc2eACNscAHDk7qAo2127oI7axUoFMe9/g1g==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "@cspell/dict-data-science": "^2.0.8" } @@ -2442,92 +2454,92 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@cspell/dict-r/-/dict-r-2.1.0.tgz", "integrity": "sha512-k2512wgGG0lTpTYH9w5Wwco+lAMf3Vz7mhqV8+OnalIE7muA0RSuD9tWBjiqLcX8zPvEJr4LdgxVju8Gk3OKyA==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-ruby": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-5.0.8.tgz", "integrity": "sha512-ixuTneU0aH1cPQRbWJvtvOntMFfeQR2KxT8LuAv5jBKqQWIHSxzGlp+zX3SVyoeR0kOWiu64/O5Yn836A5yMcQ==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-rust": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-4.0.11.tgz", "integrity": "sha512-OGWDEEzm8HlkSmtD8fV3pEcO2XBpzG2XYjgMCJCRwb2gRKvR+XIm6Dlhs04N/K2kU+iH8bvrqNpM8fS/BFl0uw==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-scala": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-5.0.7.tgz", "integrity": "sha512-yatpSDW/GwulzO3t7hB5peoWwzo+Y3qTc0pO24Jf6f88jsEeKmDeKkfgPbYuCgbE4jisGR4vs4+jfQZDIYmXPA==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-shell": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@cspell/dict-shell/-/dict-shell-1.1.0.tgz", "integrity": "sha512-D/xHXX7T37BJxNRf5JJHsvziFDvh23IF/KvkZXNSh8VqcRdod3BAz9VGHZf6VDqcZXr1VRqIYR3mQ8DSvs3AVQ==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-software-terms": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-5.1.2.tgz", "integrity": "sha512-MssT9yyInezB6mFqHTDNOIVjbMakORllIt7IJ91LrgiQOcDLzidR0gN9pE340s655TJ8U5MJNAfRfH0oRU14KQ==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-sql": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@cspell/dict-sql/-/dict-sql-2.2.0.tgz", "integrity": "sha512-MUop+d1AHSzXpBvQgQkCiok8Ejzb+nrzyG16E8TvKL2MQeDwnIvMe3bv90eukP6E1HWb+V/MA/4pnq0pcJWKqQ==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-svelte": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@cspell/dict-svelte/-/dict-svelte-1.0.6.tgz", "integrity": "sha512-8LAJHSBdwHCoKCSy72PXXzz7ulGROD0rP1CQ0StOqXOOlTUeSFaJJlxNYjlONgd2c62XBQiN2wgLhtPN+1Zv7Q==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-swift": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@cspell/dict-swift/-/dict-swift-2.0.5.tgz", "integrity": "sha512-3lGzDCwUmnrfckv3Q4eVSW3sK3cHqqHlPprFJZD4nAqt23ot7fic5ALR7J4joHpvDz36nHX34TgcbZNNZOC/JA==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-terraform": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@cspell/dict-terraform/-/dict-terraform-1.1.2.tgz", "integrity": "sha512-RB9dnhxKIiWpwQB+b3JuFa8X4m+6Ny92Y4Z5QARR7jEtapg8iF2ODZX1yLtozp4kFVoRsUKEP6vj3MLv87VTdg==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-typescript": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.2.2.tgz", "integrity": "sha512-H9Y+uUHsTIDFO/jdfUAcqmcd5osT+2DB5b0aRCHfLWN/twUbGn/1qq3b7YwEvttxKlYzWHU3uNFf+KfA93VY7w==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dict-vue": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@cspell/dict-vue/-/dict-vue-3.0.4.tgz", "integrity": "sha512-0dPtI0lwHcAgSiQFx8CzvqjdoXROcH+1LyqgROCpBgppommWpVhbQ0eubnKotFEXgpUCONVkeZJ6Ql8NbTEu+w==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@cspell/dynamic-import": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-9.1.2.tgz", "integrity": "sha512-Kg22HCx5m0znVPLea2jRrvMnzHZAAzqcDr5g6Dbd4Pizs5b3SPQuRpFmYaDvKo26JNZnfRqA9eweiuE5aQAf2A==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "@cspell/url": "9.1.2", "import-meta-resolve": "^4.1.0" @@ -2540,8 +2552,8 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/@cspell/eslint-plugin/-/eslint-plugin-9.1.2.tgz", "integrity": "sha512-UUCCBAyv3gTL1P19fX9C+cknkwCXHvnHUAaFBz25dX6PhJSPyYPmVdA8jm/2H6+GQYKBnHvWgfjkkiZgtqoQRA==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "@cspell/cspell-types": "9.1.2", "@cspell/url": "9.1.2", @@ -2555,41 +2567,12 @@ "eslint": "^7 || ^8 || ^9" } }, - "node_modules/@cspell/eslint-plugin/node_modules/@pkgr/core": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", - "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@cspell/eslint-plugin/node_modules/synckit": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", - "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.4" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, "node_modules/@cspell/filetypes": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-9.1.2.tgz", "integrity": "sha512-j+6kDz3GbeYwwtlzVosqVaSiFGMhf0u3y8eAP3IV2bTelhP2ZiOLD+yNbAyYGao7p10/Sqv+Ri0yT7IsGLniww==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=20" } @@ -2598,8 +2581,8 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-9.1.2.tgz", "integrity": "sha512-6X9oXnklvdt1pd0x0Mh6qXaaIRxjt0G50Xz5ZGm3wpAagv0MFvTThdmYVFfBuZ91x7fDT3u77y3d1uqdGQW1CA==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=20" } @@ -2608,8 +2591,8 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/@cspell/url/-/url-9.1.2.tgz", "integrity": "sha512-PMJBuLYQIdFnEfPHQXaVE5hHUkbbOxOIRmHyZwWEc9+79tIaIkiwLpjZvbm8p6f9WXAaESqXs/uK2tUC/bjwmw==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=20" } @@ -2618,8 +2601,8 @@ "version": "3.14.5", "resolved": "https://registry.npmjs.org/@cypress/code-coverage/-/code-coverage-3.14.5.tgz", "integrity": "sha512-sSyCSiYpChgKIaO7Bglxp1Pjf1l6EQDejq6yIc4REcGXCVxrtjP5G5j2TsjH/zcceDvyShXH5DyLD21M9ryaeg==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "@cypress/webpack-preprocessor": "^6.0.0", "chalk": "4.1.2", @@ -2643,8 +2626,8 @@ "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", + "optional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -2659,8 +2642,8 @@ "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", + "optional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2676,8 +2659,8 @@ "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", + "optional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -2689,15 +2672,15 @@ "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" + "license": "MIT", + "optional": true }, "node_modules/@cypress/code-coverage/node_modules/execa": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", @@ -2720,8 +2703,8 @@ "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", + "optional": true, "dependencies": { "pump": "^3.0.0" }, @@ -2736,8 +2719,8 @@ "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", + "optional": true, "engines": { "node": ">=8" } @@ -2746,8 +2729,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, "license": "Apache-2.0", + "optional": true, "engines": { "node": ">=8.12.0" } @@ -2756,8 +2739,8 @@ "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", + "optional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -2766,11 +2749,10 @@ } }, "node_modules/@cypress/request": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.6.tgz", - "integrity": "sha512-fi0eVdCOtKu5Ed6+E8mYxUF6ZTFJDZvHogCBelM0xVXmrDEkyM22gRArQzq1YcHPm1V47Vf/iAD+WgVdUlJCGg==", - "dev": true, - "license": "Apache-2.0", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", + "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", + "optional": true, "dependencies": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -2778,14 +2760,14 @@ "combined-stream": "~1.0.6", "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~4.0.0", + "form-data": "~4.0.4", "http-signature": "~1.4.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "6.13.0", + "qs": "6.14.0", "safe-buffer": "^5.1.2", "tough-cookie": "^5.0.0", "tunnel-agent": "^0.6.0", @@ -2795,12 +2777,26 @@ "node": ">= 6" } }, + "node_modules/@cypress/request/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "optional": true, + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/@cypress/request/node_modules/tough-cookie": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", - "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", - "dev": true, - "license": "BSD-3-Clause", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "optional": true, "dependencies": { "tldts": "^6.1.32" }, @@ -2809,29 +2805,41 @@ } }, "node_modules/@cypress/webpack-preprocessor": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@cypress/webpack-preprocessor/-/webpack-preprocessor-6.0.2.tgz", - "integrity": "sha512-0+1+4iy4W9PE6R5ywBNKAZoFp8Sf//w3UJ+CKTqkcAjA29b+dtsD0iFT70DsYE0BMqUM1PO7HXFGbXllQ+bRAA==", - "dev": true, - "license": "MIT", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@cypress/webpack-preprocessor/-/webpack-preprocessor-6.0.4.tgz", + "integrity": "sha512-ly+EcabWWbhrSPr2J/njQX7Y3da+QqOmFg8Og/MVmLxhDLKIzr2WhTdgzDYviPTLx/IKsdb41cc2RLYp6mSBRA==", + "optional": true, "dependencies": { "bluebird": "3.7.1", "debug": "^4.3.4", - "lodash": "^4.17.20" + "lodash": "^4.17.20", + "semver": "^7.3.2" }, "peerDependencies": { - "@babel/core": "^7.0.1", - "@babel/preset-env": "^7.0.0", - "babel-loader": "^8.3 || ^9", + "@babel/core": "^7.25.2", + "@babel/preset-env": "^7.25.3", + "babel-loader": "^8.3 || ^9 || ^10", "webpack": "^4 || ^5" } }, + "node_modules/@cypress/webpack-preprocessor/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==", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@cypress/xvfb": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "debug": "^3.1.0", "lodash.once": "^4.1.1" @@ -2841,8 +2849,8 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "ms": "^2.1.1" } @@ -2856,11 +2864,38 @@ "node": ">=10.0.0" } }, + "node_modules/@emnapi/core": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", + "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", + "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", + "optional": true, + "dependencies": { + "tslib": "^2.4.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==", - "optional": true, "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", @@ -2879,7 +2914,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "optional": true, "engines": { "node": ">=10" }, @@ -2891,7 +2925,6 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "optional": true, "engines": { "node": ">=0.10.0" } @@ -2900,7 +2933,6 @@ "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==", - "devOptional": true, "dependencies": { "@emotion/memoize": "^0.9.0", "@emotion/sheet": "^1.4.0", @@ -2912,14 +2944,12 @@ "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==", - "devOptional": true + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" }, "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==", - "optional": true, "dependencies": { "@emotion/memoize": "^0.9.0" } @@ -2927,14 +2957,12 @@ "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==", - "devOptional": true + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" }, "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==", - "optional": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -2958,7 +2986,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", - "devOptional": true, "dependencies": { "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", @@ -2970,14 +2997,12 @@ "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==", - "devOptional": true + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" }, "node_modules/@emotion/styled": { "version": "11.14.0", "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", - "optional": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -2999,14 +3024,12 @@ "node_modules/@emotion/unitless": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", - "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", - "devOptional": true + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" }, "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==", - "optional": true, "peerDependencies": { "react": ">=16.8.0" } @@ -3014,40 +3037,29 @@ "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==", - "devOptional": true + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" }, "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==", - "devOptional": true + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "devOptional": true, "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "devOptional": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, "funding": { "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/regexpp": { @@ -3122,7 +3134,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==", - "optional": true + "dev": true }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", @@ -3264,7 +3276,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, + "devOptional": true, "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -3280,7 +3292,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, + "devOptional": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -3289,7 +3301,7 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, + "devOptional": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -3302,7 +3314,7 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -3311,7 +3323,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -3328,7 +3340,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -3343,7 +3355,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3359,7 +3371,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -3371,13 +3383,13 @@ "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 + "devOptional": true }, "node_modules/@jest/console/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -3386,7 +3398,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -3398,7 +3410,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", @@ -3445,7 +3457,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -3460,7 +3472,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3476,7 +3488,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -3488,13 +3500,13 @@ "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 + "devOptional": true }, "node_modules/@jest/core/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -3503,7 +3515,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -3511,11 +3523,20 @@ "node": ">=8" } }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "dev": true, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/environment": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", @@ -3530,7 +3551,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, + "devOptional": true, "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" @@ -3543,7 +3564,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, + "devOptional": true, "dependencies": { "jest-get-type": "^29.6.3" }, @@ -3551,11 +3572,27 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/expect/node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "devOptional": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jest/fake-timers": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", @@ -3568,11 +3605,20 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/get-type": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz", + "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==", + "dev": true, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/globals": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -3583,11 +3629,33 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/reporters": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, + "devOptional": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", @@ -3630,7 +3698,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -3645,7 +3713,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3661,7 +3729,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -3673,13 +3741,13 @@ "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 + "devOptional": true }, "node_modules/@jest/reporters/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -3688,7 +3756,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", @@ -3704,7 +3772,7 @@ "version": "7.6.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "dev": true, + "devOptional": true, "bin": { "semver": "bin/semver.js" }, @@ -3716,7 +3784,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -3728,7 +3796,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, + "devOptional": true, "dependencies": { "@sinclair/typebox": "^0.27.8" }, @@ -3740,7 +3808,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", @@ -3754,7 +3822,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", @@ -3769,7 +3837,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", @@ -3784,7 +3852,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", @@ -3810,7 +3878,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -3825,7 +3893,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3841,7 +3909,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -3853,19 +3921,19 @@ "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 + "devOptional": true }, "node_modules/@jest/transform/node_modules/convert-source-map": { "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 + "devOptional": true }, "node_modules/@jest/transform/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -3874,7 +3942,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -3886,7 +3954,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -3903,7 +3971,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -3918,7 +3986,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3934,7 +4002,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -3946,13 +4014,13 @@ "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 + "devOptional": true }, "node_modules/@jest/types/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -3961,7 +4029,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -3973,7 +4041,7 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/set-array": "^1.0.0", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -3986,7 +4054,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "devOptional": true, "engines": { "node": ">=6.0.0" } @@ -4004,7 +4071,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -4014,7 +4081,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -4025,16 +4092,14 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "devOptional": true + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" }, "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==", - "devOptional": true, + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -4131,9 +4196,9 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.3.1.tgz", - "integrity": "sha512-2OmnEyoHpj5//dJJpMuxOeLItCCHdf99pjMFfUFdBteCunAK9jW+PwEo4mtdGcLs7P+IgZ+85ypd52eY4AigoQ==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.5.0.tgz", + "integrity": "sha512-LGb8t8i6M2ZtS3Drn3GbTI1DVhDY6FJ9crEey2lZ0aN2EMZo8IZBZj9wRf4vqbZHaWjsYgtbOnJw5V8UWbmK2Q==", "dev": true, "funding": { "type": "opencollective", @@ -4141,9 +4206,9 @@ } }, "node_modules/@mui/icons-material": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.3.1.tgz", - "integrity": "sha512-nJmWj1PBlwS3t1PnoqcixIsftE+7xrW3Su7f0yrjPw4tVjYrgkhU0hrRp+OlURfZ3ptdSkoBkalee9Bhf1Erfw==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.5.0.tgz", + "integrity": "sha512-VPuPqXqbBPlcVSA0BmnoE4knW4/xG6Thazo8vCLWkOKusko6DtwFV6B665MMWJ9j0KFohTIf3yx2zYtYacvG1g==", "dev": true, "dependencies": { "@babel/runtime": "^7.26.0" @@ -4156,7 +4221,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^6.3.1", + "@mui/material": "^6.5.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -4167,16 +4232,16 @@ } }, "node_modules/@mui/material": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.3.1.tgz", - "integrity": "sha512-ynG9ayhxgCsHJ/dtDcT1v78/r2GwQyP3E0hPz3GdPRl0uFJz/uUTtI5KFYwadXmbC+Uv3bfB8laZ6+Cpzh03gA==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.5.0.tgz", + "integrity": "sha512-yjvtXoFcrPLGtgKRxFaH6OQPtcLPhkloC0BML6rBG5UeldR0nPULR/2E2BfXdo5JNV7j7lOzrrLX2Qf/iSidow==", "dev": true, "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.3.1", - "@mui/system": "^6.3.1", - "@mui/types": "^7.2.21", - "@mui/utils": "^6.3.1", + "@mui/core-downloads-tracker": "^6.5.0", + "@mui/system": "^6.5.0", + "@mui/types": "~7.2.24", + "@mui/utils": "^6.4.9", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", @@ -4195,7 +4260,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.3.1", + "@mui/material-pigment-css": "^6.5.0", "@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" @@ -4222,13 +4287,13 @@ "dev": true }, "node_modules/@mui/private-theming": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.3.1.tgz", - "integrity": "sha512-g0u7hIUkmXmmrmmf5gdDYv9zdAig0KoxhIQn1JN8IVqApzf/AyRhH3uDGx5mSvs8+a1zb4+0W6LC260SyTTtdQ==", + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.9.tgz", + "integrity": "sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==", "dev": true, "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.3.1", + "@mui/utils": "^6.4.9", "prop-types": "^15.8.1" }, "engines": { @@ -4249,9 +4314,9 @@ } }, "node_modules/@mui/styled-engine": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.3.1.tgz", - "integrity": "sha512-/7CC0d2fIeiUxN5kCCwYu4AWUDd9cCTxWCyo0v/Rnv6s8uk6hWgJC3VLZBoDENBHf/KjqDZuYJ2CR+7hD6QYww==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.5.0.tgz", + "integrity": "sha512-8woC2zAqF4qUDSPIBZ8v3sakj+WgweolpyM/FXf8jAx6FMls+IE4Y8VDZc+zS805J7PRz31vz73n2SovKGaYgw==", "dev": true, "dependencies": { "@babel/runtime": "^7.26.0", @@ -4283,16 +4348,16 @@ } }, "node_modules/@mui/system": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.3.1.tgz", - "integrity": "sha512-AwqQ3EAIT2np85ki+N15fF0lFXX1iFPqenCzVOSl3QXKy2eifZeGd9dGtt7pGMoFw5dzW4dRGGzRpLAq9rkl7A==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.5.0.tgz", + "integrity": "sha512-XcbBYxDS+h/lgsoGe78ExXFZXtuIlSBpn/KsZq8PtZcIkUNJInkuDqcLd2rVBQrDC1u+rvVovdaWPf2FHKJf3w==", "dev": true, "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/private-theming": "^6.3.1", - "@mui/styled-engine": "^6.3.1", - "@mui/types": "^7.2.21", - "@mui/utils": "^6.3.1", + "@mui/private-theming": "^6.4.9", + "@mui/styled-engine": "^6.5.0", + "@mui/types": "~7.2.24", + "@mui/utils": "^6.4.9", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -4323,9 +4388,9 @@ } }, "node_modules/@mui/types": { - "version": "7.2.21", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz", - "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==", + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", "dev": true, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -4337,13 +4402,13 @@ } }, "node_modules/@mui/utils": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.3.1.tgz", - "integrity": "sha512-sjGjXAngoio6lniQZKJ5zGfjm+LD2wvLwco7FbKe1fu8A7VIFmz2SwkLb+MDPLNX1lE7IscvNNyh1pobtZg2tw==", + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.9.tgz", + "integrity": "sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==", "dev": true, "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/types": "^7.2.21", + "@mui/types": "~7.2.24", "@types/prop-types": "^15.7.14", "clsx": "^2.1.1", "prop-types": "^15.8.1", @@ -4367,11 +4432,22 @@ } }, "node_modules/@mui/utils/node_modules/react-is": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", - "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz", + "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==", "dev": true }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4848,6 +4924,22 @@ "react-dom": "^17 || ^18 || ^19" } }, + "node_modules/@patternfly/react-templates": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-templates/-/react-templates-6.3.1.tgz", + "integrity": "sha512-Rf23iWVq7cME/OE/T2F7Tbjhsjol6IdSW9CNEV2fDJQxcjfPG8UzJbLXii45ox0aLcjxTIWWXSr8vpaiDPzHrw==", + "dependencies": { + "@patternfly/react-core": "^6.3.1", + "@patternfly/react-icons": "^6.3.1", + "@patternfly/react-styles": "^6.3.1", + "@patternfly/react-tokens": "^6.3.1", + "tslib": "^2.8.1" + }, + "peerDependencies": { + "react": "^17 || ^18", + "react-dom": "^17 || ^18" + } + }, "node_modules/@patternfly/react-tokens": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-6.3.1.tgz", @@ -4857,7 +4949,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, "peer": true, @@ -4866,21 +4957,79 @@ } }, "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "optional": true, "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/unts" + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.17.tgz", + "integrity": "sha512-tXDyE1/jzFsHXjhRZQ3hMl0IVhYe5qula43LDWIhVfjp9G/nT5OQY5AORVOrkEGAUltBJOfOWeETbmhm6kHhuQ==", + "dev": true, + "dependencies": { + "ansi-html": "^0.0.9", + "core-js-pure": "^3.23.3", + "error-stack-parser": "^2.0.6", + "html-entities": "^2.1.0", + "loader-utils": "^2.0.4", + "schema-utils": "^4.2.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "@types/webpack": "4.x || 5.x", + "react-refresh": ">=0.10.0 <1.0.0", + "sockjs-client": "^1.4.0", + "type-fest": ">=0.17.0 <5.0.0", + "webpack": ">=4.43.0 <6.0.0", + "webpack-dev-server": "3.x || 4.x || 5.x", + "webpack-hot-middleware": "2.x", + "webpack-plugin-serve": "0.x || 1.x" + }, + "peerDependenciesMeta": { + "@types/webpack": { + "optional": true + }, + "sockjs-client": { + "optional": true + }, + "type-fest": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + }, + "webpack-hot-middleware": { + "optional": true + }, + "webpack-plugin-serve": { + "optional": true + } + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "engines": { + "node": ">= 12" } }, "node_modules/@polka/url": { "version": "1.0.0-next.25", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", - "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==" + "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", + "dev": true }, "node_modules/@popperjs/core": { "version": "2.11.8", @@ -4892,14 +5041,6 @@ "url": "https://opencollective.com/popperjs" } }, - "node_modules/@remix-run/router": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz", - "integrity": "sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -4907,17 +5048,47 @@ "license": "MIT", "optional": true }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true + "devOptional": true + }, + "node_modules/@sindresorhus/is": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-6.3.1.tgz", + "integrity": "sha512-FX4MfcifwJyFOI2lPoX7PQxCqx8BG1HCho7WdiXwpEQx1Ycij0JxkfYtGK7yqNScrZGSlt6RE6sw8QYoH7eKnQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, + "devOptional": true, "dependencies": { "type-detect": "4.0.8" } @@ -4926,17 +5097,229 @@ "version": "10.3.0", "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, + "devOptional": true, "dependencies": { "@sinonjs/commons": "^3.0.0" } }, - "node_modules/@testing-library/cypress": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@testing-library/cypress/-/cypress-10.0.2.tgz", - "integrity": "sha512-dKv95Bre5fDmNb9tOIuWedhGUryxGu1GWYWtXDqUsDPcr9Ekld0fiTb+pcBvSsFpYXAZSpmyEjhoXzLbhh06yQ==", + "node_modules/@swc/core": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.3.tgz", + "integrity": "sha512-ZaDETVWnm6FE0fc+c2UE8MHYVS3Fe91o5vkmGfgwGXFbxYvAjKSqxM/j4cRc9T7VZNSJjriXq58XkfCp3Y6f+w==", "dev": true, - "license": "MIT", + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.23" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.13.3", + "@swc/core-darwin-x64": "1.13.3", + "@swc/core-linux-arm-gnueabihf": "1.13.3", + "@swc/core-linux-arm64-gnu": "1.13.3", + "@swc/core-linux-arm64-musl": "1.13.3", + "@swc/core-linux-x64-gnu": "1.13.3", + "@swc/core-linux-x64-musl": "1.13.3", + "@swc/core-win32-arm64-msvc": "1.13.3", + "@swc/core-win32-ia32-msvc": "1.13.3", + "@swc/core-win32-x64-msvc": "1.13.3" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.3.tgz", + "integrity": "sha512-ux0Ws4pSpBTqbDS9GlVP354MekB1DwYlbxXU3VhnDr4GBcCOimpocx62x7cFJkSpEBF8bmX8+/TTCGKh4PbyXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.3.tgz", + "integrity": "sha512-p0X6yhxmNUOMZrbeZ3ZNsPige8lSlSe1llllXvpCLkKKxN/k5vZt1sULoq6Nj4eQ7KeHQVm81/+AwKZyf/e0TA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.3.tgz", + "integrity": "sha512-OmDoiexL2fVWvQTCtoh0xHMyEkZweQAlh4dRyvl8ugqIPEVARSYtaj55TBMUJIP44mSUOJ5tytjzhn2KFxFcBA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.3.tgz", + "integrity": "sha512-STfKku3QfnuUj6k3g9ld4vwhtgCGYIFQmsGPPgT9MK/dI3Lwnpe5Gs5t1inoUIoGNP8sIOLlBB4HV4MmBjQuhw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.3.tgz", + "integrity": "sha512-bc+CXYlFc1t8pv9yZJGus372ldzOVscBl7encUBlU1m/Sig0+NDJLz6cXXRcFyl6ABNOApWeR4Yl7iUWx6C8og==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.3.tgz", + "integrity": "sha512-dFXoa0TEhohrKcxn/54YKs1iwNeW6tUkHJgXW33H381SvjKFUV53WR231jh1sWVJETjA3vsAwxKwR23s7UCmUA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.3.tgz", + "integrity": "sha512-ieyjisLB+ldexiE/yD8uomaZuZIbTc8tjquYln9Quh5ykOBY7LpJJYBWvWtm1g3pHv6AXlBI8Jay7Fffb6aLfA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.3.tgz", + "integrity": "sha512-elTQpnaX5vESSbhCEgcwXjpMsnUbqqHfEpB7ewpkAsLzKEXZaK67ihSRYAuAx6ewRQTo7DS5iTT6X5aQD3MzMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.3.tgz", + "integrity": "sha512-nvehQVEOdI1BleJpuUgPLrclJ0TzbEMc+MarXDmmiRFwEUGqj+pnfkTSb7RZyS1puU74IXdK/YhTirHurtbI9w==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.3.tgz", + "integrity": "sha512-A+JSKGkRbPLVV2Kwx8TaDAV0yXIXm/gc8m98hSkVDGlPBBmydgzNdWy3X7HTUBM7IDk7YlWE7w2+RUGjdgpTmg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true + }, + "node_modules/@swc/types": { + "version": "0.1.24", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.24.tgz", + "integrity": "sha512-tjTMh3V4vAORHtdTprLlfoMptu1WfTZG9Rsca6yOKyNYsRr+MUXutKmliB17orgSZk5DpnDxs8GUdd/qwYxOng==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@testing-library/cypress": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@testing-library/cypress/-/cypress-10.0.3.tgz", + "integrity": "sha512-TeZJMCNtiS59cPWalra7LgADuufO5FtbqQBYxuAgdX6ZFAR2D9CtQwAG8VbgvFcchW3K414va/+7P4OkQ80UVg==", + "optional": true, "dependencies": { "@babel/runtime": "^7.14.6", "@testing-library/dom": "^10.1.0" @@ -4946,15 +5329,15 @@ "npm": ">=6" }, "peerDependencies": { - "cypress": "^12.0.0 || ^13.0.0" + "cypress": "^12.0.0 || ^13.0.0 || ^14.0.0" } }, "node_modules/@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -4973,7 +5356,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "optional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4988,7 +5371,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "optional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -5004,7 +5387,7 @@ "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, + "optional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -5016,13 +5399,13 @@ "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 + "optional": true }, "node_modules/@testing-library/dom/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, + "optional": true, "engines": { "node": ">=8" } @@ -5031,7 +5414,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, + "optional": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -5045,7 +5428,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "optional": true, "engines": { "node": ">=10" }, @@ -5057,7 +5440,7 @@ "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, + "optional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -5066,251 +5449,62 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz", - "integrity": "sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==", - "dev": true, + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.7.0.tgz", + "integrity": "sha512-RI2e97YZ7MRa+vxP4UUnMuMFL2buSsf0ollxUbTgrbPLKhMn8KVTx7raS6DYjC7v1NDVrioOvaShxsguLNISCA==", + "optional": true, "dependencies": { - "@adobe/css-tools": "^4.0.1", - "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", + "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", - "chalk": "^3.0.0", "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.5.6", - "lodash": "^4.17.15", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", "redent": "^3.0.0" }, "engines": { - "node": ">=8", + "node": ">=14", "npm": ">=6", "yarn": ">=1" } }, - "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@testing-library/jest-dom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "optional": true }, "node_modules/@testing-library/react": { - "version": "14.3.1", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.1.tgz", - "integrity": "sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==", - "dev": true, + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", + "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", + "optional": true, "dependencies": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^9.0.0", - "@types/react-dom": "^18.0.0" + "@babel/runtime": "^7.12.5" }, "engines": { - "node": ">=14" + "node": ">=18" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@testing-library/react/node_modules/@testing-library/dom": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", - "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.1.3", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@testing-library/react/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/@testing-library/react/node_modules/aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "deep-equal": "^2.0.5" - } - }, - "node_modules/@testing-library/react/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/@testing-library/react/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/@testing-library/react/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/@testing-library/react/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/@testing-library/react/node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@testing-library/react/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@testing-library/react/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" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, "node_modules/@testing-library/user-event": { - "version": "14.4.3", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.4.3.tgz", - "integrity": "sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==", - "dev": true, + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "optional": true, "engines": { "node": ">=12", "npm": ">=6" @@ -5334,26 +5528,26 @@ "node": ">= 10" } }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "dev": true, - "engines": { - "node": ">=10.13.0" + "node_modules/@tybys/wasm-util": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", + "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@types/aria-query": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==", - "dev": true + "optional": true }, "node_modules/@types/babel__core": { "version": "7.20.1", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -5366,7 +5560,7 @@ "version": "7.6.4", "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/types": "^7.0.0" } @@ -5375,7 +5569,7 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" @@ -5385,7 +5579,7 @@ "version": "7.20.1", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/types": "^7.20.7" } @@ -5430,6 +5624,16 @@ "@types/chai": "*" } }, + "node_modules/@types/classnames": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.3.4.tgz", + "integrity": "sha512-dwmfrMMQb9ujX1uYGvB5ERDlOzBNywnZAZBtOe107/hORWP05ESgU4QyaanZMWYYfd2BzrG78y13/Bju8IQcMQ==", + "deprecated": "This is a stub types definition. classnames provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "classnames": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -5457,6 +5661,42 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/dompurify": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.2.0.tgz", + "integrity": "sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg==", + "deprecated": "This is a stub types definition. dompurify provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "dompurify": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "devOptional": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "devOptional": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "devOptional": true + }, "node_modules/@types/express": { "version": "4.17.22", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz", @@ -5487,17 +5727,11 @@ "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, + "devOptional": true, "dependencies": { "@types/node": "*" } }, - "node_modules/@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", - "dev": true - }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -5515,43 +5749,60 @@ } }, "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "devOptional": true }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, + "devOptional": true, "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "devOptional": true, "dependencies": { "@types/istanbul-lib-report": "*" } }, "node_modules/@types/jest": { - "version": "29.5.3", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.3.tgz", - "integrity": "sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA==", + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", "dev": true, "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" } }, + "node_modules/@types/jest/node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@types/js-yaml": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, "license": "MIT" }, "node_modules/@types/jsdom": { @@ -5593,7 +5844,7 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "devOptional": true }, "node_modules/@types/json5": { "version": "0.0.29", @@ -5602,6 +5853,21 @@ "license": "MIT", "optional": true }, + "node_modules/@types/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "dev": true + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -5613,7 +5879,7 @@ "version": "18.19.34", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.34.tgz", "integrity": "sha512-eXF4pfBNV5DAMKGbI02NnDtWrQ40hAN558/2vvS4gMpMIxaf6JmD7YjnZbq0Q9TDSSkKBamime8ewRoomHdt4g==", - "dev": true, + "devOptional": true, "dependencies": { "undici-types": "~5.26.4" } @@ -5631,14 +5897,13 @@ "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "optional": true + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, "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==", - "dev": true + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true }, "node_modules/@types/qs": { "version": "6.9.5", @@ -5653,42 +5918,23 @@ "dev": true }, "node_modules/@types/react": { - "version": "19.0.6", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.6.tgz", - "integrity": "sha512-gIlMztcTeDgXCUj0vCBOqEuSEhX//63fW9SZtCJ+agxoQTOklwDfiEMlTWn4mR/C/UK5VHlpwsCsOyf7/hc4lw==", - "dev": true, + "version": "18.3.23", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", + "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", + "devOptional": true, + "peer": true, "dependencies": { + "@types/prop-types": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.6.tgz", - "integrity": "sha512-2et4PDvg6PVCyS7fuTc4gPoksV58bW0RwSxWKcPRcHZf0PRUGq03TKcD/rUHe3azfV6/5/biUBJw+HhCQjaP0A==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-router": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.11.tgz", - "integrity": "sha512-ofHbZMlp0Y2baOHgsWBQ4K3AttxY61bDMkwTiBOkPg7U6C/3UwwB5WaIx28JmSVi/eX3uFEMRo61BV22fDQIvg==", - "dev": true, - "dependencies": { - "@types/history": "*", - "@types/react": "*" - } - }, - "node_modules/@types/react-router-dom": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", - "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", - "dev": true, - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "*" + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "devOptional": true, + "peerDependencies": { + "@types/react": "^18.0.0" } }, "node_modules/@types/react-transition-group": { @@ -5740,19 +5986,25 @@ "@types/send": "*" } }, + "node_modules/@types/showdown": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.6.tgz", + "integrity": "sha512-pTvD/0CIeqe4x23+YJWlX2gArHa8G0J0Oh6GKaVXV7TAeickpkkZiNOgFcFcmLQ5lB/K0qBJL1FtRYltBfbGCQ==", + "dev": true + }, "node_modules/@types/sinonjs__fake-timers": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@types/sizzle": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz", "integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@types/sockjs": { "version": "0.3.36", @@ -5765,25 +6017,16 @@ } }, "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "devOptional": true }, "node_modules/@types/swagger-schema-official": { "version": "2.0.25", "resolved": "https://registry.npmjs.org/@types/swagger-schema-official/-/swagger-schema-official-2.0.25.tgz", "integrity": "sha512-T92Xav+Gf/Ik1uPW581nA+JftmjWPgskw/WBf4TJzxRG/SJ+DfNnNE+WuZ4mrXuzflQMqMkm1LSYjzYW7MB1Cg==", - "optional": true - }, - "node_modules/@types/testing-library__jest-dom": { - "version": "5.14.8", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.8.tgz", - "integrity": "sha512-NRfJE9Cgpmu4fx716q9SYmU4jxxhYRU1BQo239Txt/9N3EC745XZX1Yl7h/SBIDlo1ANVOCRB4YDXjaQdoKCHQ==", - "dev": true, - "dependencies": { - "@types/jest": "*" - } + "dev": true }, "node_modules/@types/tough-cookie": { "version": "4.0.2", @@ -5791,14 +6034,11 @@ "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", "dev": true }, - "node_modules/@types/victory": { - "version": "33.1.5", - "resolved": "https://registry.npmjs.org/@types/victory/-/victory-33.1.5.tgz", - "integrity": "sha512-Lpi1kAlZ4+gY7oH3tRcmJs4YhTIJTTJxkQkyYTK7CtTHl+S6Xf7E7e207eq6D/Dn9UQAB7PCrNrzTGtO5+9GGQ==", - "dev": true, - "dependencies": { - "@types/react": "*" - } + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "optional": true }, "node_modules/@types/ws": { "version": "8.18.1", @@ -5811,10 +6051,10 @@ } }, "node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dev": true, + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "devOptional": true, "dependencies": { "@types/yargs-parser": "*" } @@ -5823,13 +6063,12 @@ "version": "21.0.0", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true + "devOptional": true }, "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": { @@ -5837,21 +6076,20 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz", - "integrity": "sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==", - "license": "MIT", - "optional": true, + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.40.0.tgz", + "integrity": "sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==", + "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/type-utils": "8.17.0", - "@typescript-eslint/utils": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", + "@typescript-eslint/scope-manager": "8.40.0", + "@typescript-eslint/type-utils": "8.40.0", + "@typescript-eslint/utils": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0", "graphemer": "^1.4.0", - "ignore": "^5.3.1", + "ignore": "^7.0.0", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5861,26 +6099,30 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.40.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "engines": { + "node": ">= 4" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz", - "integrity": "sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==", - "license": "BSD-2-Clause", - "optional": true, + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.40.0.tgz", + "integrity": "sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==", + "devOptional": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/typescript-estree": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", + "@typescript-eslint/scope-manager": "8.40.0", + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/typescript-estree": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0", "debug": "^4.3.4" }, "engines": { @@ -5891,43 +6133,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", - "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", - "license": "MIT", - "optional": true, + "node_modules/@typescript-eslint/project-service": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.40.0.tgz", + "integrity": "sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==", + "devOptional": true, "dependencies": { - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz", - "integrity": "sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "8.17.0", - "@typescript-eslint/utils": "8.17.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "@typescript-eslint/tsconfig-utils": "^8.40.0", + "@typescript-eslint/types": "^8.40.0", + "debug": "^4.3.4" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5937,20 +6155,71 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.40.0.tgz", + "integrity": "sha512-y9ObStCcdCiZKzwqsE8CcpyuVMwRouJbbSrNuThDpv16dFAj429IkM6LNb1dZ2m7hK5fHyzNcErZf7CEeKXR4w==", + "devOptional": true, + "dependencies": { + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.40.0.tgz", + "integrity": "sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==", + "devOptional": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.40.0.tgz", + "integrity": "sha512-eE60cK4KzAc6ZrzlJnflXdrMqOBaugeukWICO2rB0KNvwdIMaEaYiywwHMzA1qFpTxrLhN9Lp4E/00EgWcD3Ow==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/typescript-estree": "8.40.0", + "@typescript-eslint/utils": "8.40.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", - "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", - "license": "MIT", - "optional": true, + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.40.0.tgz", + "integrity": "sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==", + "devOptional": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -5960,20 +6229,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", - "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", - "license": "BSD-2-Clause", - "optional": true, + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.40.0.tgz", + "integrity": "sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==", + "devOptional": true, "dependencies": { - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", + "@typescript-eslint/project-service": "8.40.0", + "@typescript-eslint/tsconfig-utils": "8.40.0", + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5982,18 +6252,15 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/typescript-estree/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==", - "license": "MIT", - "optional": true, + "devOptional": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -6002,8 +6269,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", - "optional": true, + "devOptional": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -6015,11 +6281,10 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "license": "ISC", - "optional": true, + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "devOptional": true, "bin": { "semver": "bin/semver.js" }, @@ -6028,16 +6293,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz", - "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==", - "license": "MIT", - "optional": true, + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.40.0.tgz", + "integrity": "sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==", + "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/typescript-estree": "8.17.0" + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.40.0", + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/typescript-estree": "8.40.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6047,23 +6311,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", - "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", - "license": "MIT", - "optional": true, + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.40.0.tgz", + "integrity": "sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==", + "devOptional": true, "dependencies": { - "@typescript-eslint/types": "8.17.0", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.40.0", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6073,72 +6332,438 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "devOptional": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "devOptional": true }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", - "dev": true, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "optional": true, "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "devOptional": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "devOptional": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "devOptional": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "devOptional": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "devOptional": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, - "node_modules/@webassemblyjs/helper-numbers/node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "devOptional": true }, - "node_modules/@webassemblyjs/helper-numbers/node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "devOptional": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "devOptional": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "devOptional": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "devOptional": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "devOptional": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "devOptional": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "devOptional": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "devOptional": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "devOptional": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } }, "node_modules/@webpack-cli/configtest": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", + "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", "dev": true, "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" }, "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" } }, "node_modules/@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", + "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", "dev": true, "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" }, "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" } }, "node_modules/@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", + "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", "dev": true, "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" }, "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" }, "peerDependenciesMeta": { "webpack-dev-server": { @@ -6150,20 +6775,20 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true + "devOptional": true }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true + "devOptional": true }, "node_modules/@zeit/schemas": { "version": "2.36.0", "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/abab": { "version": "2.0.6", @@ -6175,7 +6800,7 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, + "devOptional": true, "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -6185,11 +6810,10 @@ } }, "node_modules/acorn": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", - "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "devOptional": true, - "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -6216,14 +6840,16 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "dev": true, - "license": "MIT", + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "devOptional": true, + "engines": { + "node": ">=10.13.0" + }, "peerDependencies": { - "acorn": "^8" + "acorn": "^8.14.0" } }, "node_modules/acorn-jsx": { @@ -6251,8 +6877,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -6281,7 +6907,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, + "devOptional": true, "dependencies": { "ajv": "^8.0.0" }, @@ -6298,7 +6924,7 @@ "version": "8.11.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, + "devOptional": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -6314,7 +6940,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "devOptional": true }, "node_modules/ajv-keywords": { "version": "3.5.2", @@ -6329,8 +6955,8 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, "license": "ISC", + "optional": true, "dependencies": { "string-width": "^4.1.0" } @@ -6339,8 +6965,8 @@ "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", + "optional": true, "engines": { "node": ">=6" } @@ -6349,7 +6975,7 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, + "devOptional": true, "dependencies": { "type-fest": "^0.21.3" }, @@ -6364,7 +6990,7 @@ "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -6372,6 +6998,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ansi-html": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.9.tgz", + "integrity": "sha512-ozbS3LuenHVxNRh/wdnN16QapUHzauqSomAl1jwwJRRsGwFwtj644lIhxfWu0Fy0acCij2+AEgHvjscq3dlVXg==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, "node_modules/ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -6397,6 +7035,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "optional": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -6408,7 +7047,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, + "devOptional": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -6421,8 +7060,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "default-require-extensions": "^3.0.0" }, @@ -6434,7 +7073,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", - "dev": true, "funding": [ { "type": "github", @@ -6449,21 +7087,22 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/arg": { "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" + "license": "MIT", + "optional": true }, "node_modules/argparse": { "version": "2.0.1", @@ -6474,18 +7113,19 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, + "optional": true, "dependencies": { "dequal": "^2.0.3" } }, "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "optional": true, "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" }, "engines": { "node": ">= 0.4" @@ -6524,14 +7164,14 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, + "optional": true, "engines": { "node": ">=8" } @@ -6597,15 +7237,15 @@ } }, "node_modules/array.prototype.flatmap": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "optional": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6631,18 +7271,18 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "optional": true, "dependencies": { "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" }, "engines": { "node": ">= 0.4" @@ -6655,8 +7295,7 @@ "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "license": "MIT", + "optional": true, "dependencies": { "safer-buffer": "~2.1.0" } @@ -6665,8 +7304,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "license": "MIT", + "optional": true, "engines": { "node": ">=0.8" } @@ -6682,8 +7320,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=8" } @@ -6692,8 +7330,17 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "optional": true, + "engines": { + "node": ">= 0.4" + } }, "node_modules/asynckit": { "version": "0.4.0", @@ -6704,8 +7351,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, "license": "ISC", + "optional": true, "engines": { "node": ">= 4.0.0" } @@ -6723,6 +7370,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "optional": true, "dependencies": { "possible-typed-array-names": "^1.0.0" }, @@ -6737,8 +7385,7 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "license": "Apache-2.0", + "optional": true, "engines": { "node": "*" } @@ -6747,15 +7394,14 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", - "dev": true, - "license": "MIT" + "optional": true }, "node_modules/axe-core": { "version": "4.10.2", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz", "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==", - "devOptional": true, "license": "MPL-2.0", + "optional": true, "engines": { "node": ">=4" } @@ -6789,7 +7435,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", @@ -6810,7 +7456,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -6825,7 +7471,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -6841,7 +7487,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -6853,13 +7499,13 @@ "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 + "devOptional": true }, "node_modules/babel-jest/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -6868,7 +7514,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -6880,8 +7526,8 @@ "version": "9.2.1", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", - "dev": true, "license": "MIT", + "optional": true, "peer": true, "dependencies": { "find-cache-dir": "^4.0.0", @@ -6899,7 +7545,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -6915,7 +7561,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", @@ -6930,7 +7576,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "optional": true, "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -6945,7 +7590,6 @@ "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "optional": true, "dependencies": { "is-core-module": "^2.11.0", "path-parse": "^1.0.7", @@ -6959,15 +7603,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.12", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz", - "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "optional": true, "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.3", + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { @@ -6975,29 +7617,25 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", - "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "optional": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2", - "core-js-compat": "^3.38.0" + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", - "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "optional": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.3" + "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -7007,7 +7645,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", @@ -7030,7 +7668,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, + "devOptional": true, "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" @@ -7045,13 +7683,13 @@ "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "devOptional": true }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -7066,7 +7704,8 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/batch": { "version": "0.6.1", @@ -7078,8 +7717,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "license": "BSD-3-Clause", + "optional": true, "dependencies": { "tweetnacl": "^0.14.3" } @@ -7097,7 +7735,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -7106,15 +7744,15 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", - "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "optional": true }, "node_modules/bluebird": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/body-parser": { "version": "1.20.3", @@ -7189,8 +7827,8 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^7.0.0", @@ -7212,8 +7850,8 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=12" }, @@ -7225,8 +7863,8 @@ "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", + "optional": true, "engines": { "node": ">=12" }, @@ -7238,8 +7876,8 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=14.16" }, @@ -7251,8 +7889,8 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -7264,15 +7902,15 @@ "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" + "license": "MIT", + "optional": true }, "node_modules/boxen/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", + "optional": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -7289,8 +7927,8 @@ "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", + "optional": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -7305,8 +7943,8 @@ "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, "license": "(MIT OR CC0-1.0)", + "optional": true, "engines": { "node": ">=12.20" }, @@ -7318,8 +7956,8 @@ "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", + "optional": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -7336,6 +7974,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -7346,15 +7985,15 @@ "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", + "optional": true, "peer": true }, "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", - "dev": true, + "version": "4.25.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz", + "integrity": "sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==", + "devOptional": true, "funding": [ { "type": "opencollective", @@ -7369,12 +8008,11 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.1" + "caniuse-lite": "^1.0.30001735", + "electron-to-chromium": "^1.5.204", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -7387,7 +8025,7 @@ "version": "0.2.6", "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, + "optional": true, "dependencies": { "fast-json-stable-stringify": "2.x" }, @@ -7399,7 +8037,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, + "devOptional": true, "dependencies": { "node-int64": "^0.4.0" } @@ -7408,7 +8046,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "funding": [ { "type": "github", @@ -7424,6 +8061,7 @@ } ], "license": "MIT", + "optional": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -7433,8 +8071,8 @@ "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", + "optional": true, "engines": { "node": "*" } @@ -7443,7 +8081,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "devOptional": true }, "node_modules/bundle-name": { "version": "4.1.0", @@ -7472,22 +8110,22 @@ } }, "node_modules/c12": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/c12/-/c12-3.0.4.tgz", - "integrity": "sha512-t5FaZTYbbCtvxuZq9xxIruYydrAGsJ+8UdP0pZzMiK2xl/gNiSOy0OxhLzHUEEb0m1QXYqfzfvyIFEmz/g9lqg==", - "optional": true, + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.2.0.tgz", + "integrity": "sha512-ixkEtbYafL56E6HiFuonMm1ZjoKtIo7TH68/uiEq4DAwv9NcUX2nJ95F8TrbMeNjqIkZpruo3ojXQJ+MGG5gcQ==", + "dev": true, "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", - "dotenv": "^16.5.0", - "exsolve": "^1.0.5", + "dotenv": "^17.2.1", + "exsolve": "^1.0.7", "giget": "^2.0.0", - "jiti": "^2.4.2", + "jiti": "^2.5.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", - "pkg-types": "^2.1.0", + "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { @@ -7503,7 +8141,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "optional": true, + "dev": true, "dependencies": { "readdirp": "^4.0.1" }, @@ -7514,11 +8152,23 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/c12/node_modules/dotenv": { + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", + "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/c12/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==", - "optional": true, + "dev": true, "engines": { "node": ">= 14.18.0" }, @@ -7531,8 +8181,8 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=6" } @@ -7541,8 +8191,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "hasha": "^5.0.0", "make-dir": "^3.0.0", @@ -7557,8 +8207,8 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, "license": "ISC", + "optional": true, "dependencies": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", @@ -7567,15 +8217,15 @@ } }, "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "optional": true, "dependencies": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" }, "engines": { "node": ">= 0.4" @@ -7585,10 +8235,9 @@ } }, "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", - "license": "MIT", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -7598,13 +8247,13 @@ } }, "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", - "license": "MIT", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "devOptional": true, "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -7617,13 +8266,12 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", - "optional": true + "dev": true }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "devOptional": true, "engines": { "node": ">=6" } @@ -7642,7 +8290,7 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -7660,10 +8308,10 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001677", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz", - "integrity": "sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==", - "dev": true, + "version": "1.0.30001735", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz", + "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==", + "devOptional": true, "funding": [ { "type": "opencollective", @@ -7677,15 +8325,13 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "license": "CC-BY-4.0" + ] }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true, - "license": "Apache-2.0" + "optional": true }, "node_modules/chai-subset": { "version": "1.6.0", @@ -7701,6 +8347,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "optional": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -7714,8 +8361,8 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "chalk": "^4.1.2" }, @@ -7730,8 +8377,8 @@ "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", + "optional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -7746,8 +8393,8 @@ "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", + "optional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -7763,8 +8410,8 @@ "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", + "optional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -7776,15 +8423,15 @@ "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" + "license": "MIT", + "optional": true }, "node_modules/chalk-template/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", + "optional": true, "engines": { "node": ">=8" } @@ -7793,8 +8440,8 @@ "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", + "optional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -7802,11 +8449,23 @@ "node": ">=8" } }, + "node_modules/change-file-extension": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/change-file-extension/-/change-file-extension-0.1.1.tgz", + "integrity": "sha512-lB0j9teu8JtDPDHRfU8pNH33w4wMu5bOaKoT4PxH+AKugBrIfpiJMTTKIm0TErNeJPkeQEgvH31YpccTwOKPRg==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" } @@ -7815,8 +8474,8 @@ "version": "2.24.0", "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">= 0.8.0" } @@ -7825,7 +8484,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -7850,7 +8509,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, + "devOptional": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -7862,7 +8521,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, + "devOptional": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -7874,7 +8533,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, + "devOptional": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -7886,7 +8545,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.12.0" } @@ -7895,7 +8554,7 @@ "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, + "devOptional": true, "dependencies": { "is-number": "^7.0.0" }, @@ -7907,7 +8566,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", - "dev": true, + "devOptional": true, "dependencies": { "tslib": "^1.9.0" }, @@ -7919,13 +8578,13 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "devOptional": true }, "node_modules/ci-info": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -7940,7 +8599,7 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", - "optional": true, + "dev": true, "dependencies": { "consola": "^3.2.3" } @@ -7949,14 +8608,19 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", - "dev": true + "devOptional": true + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=6" } @@ -7965,8 +8629,8 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/clear-module/-/clear-module-4.1.2.tgz", "integrity": "sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "parent-module": "^2.0.0", "resolve-from": "^5.0.0" @@ -7982,8 +8646,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz", "integrity": "sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "callsites": "^3.1.0" }, @@ -7995,8 +8659,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=10" }, @@ -8008,8 +8672,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "restore-cursor": "^3.1.0" }, @@ -8018,11 +8682,10 @@ } }, "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "dev": true, - "license": "MIT", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz", + "integrity": "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==", + "optional": true, "dependencies": { "string-width": "^4.2.0" }, @@ -8030,15 +8693,15 @@ "node": "10.* || >= 12.*" }, "optionalDependencies": { - "@colors/colors": "1.5.0" + "colors": "1.4.0" } }, "node_modules/cli-truncate": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "slice-ansi": "^3.0.0", "string-width": "^4.2.0" @@ -8054,8 +8717,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "arch": "^2.2.0", "execa": "^5.1.1", @@ -8109,7 +8772,7 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, + "devOptional": true, "engines": { "iojs": ">= 1.0.0", "node": ">= 0.12.0" @@ -8119,12 +8782,13 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true + "devOptional": true }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "optional": true, "dependencies": { "color-name": "1.1.3" } @@ -8132,7 +8796,8 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "optional": true }, "node_modules/colord": { "version": "2.9.3", @@ -8144,7 +8809,16 @@ "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true + "devOptional": true + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "optional": true, + "engines": { + "node": ">=0.1.90" + } }, "node_modules/combined-stream": { "version": "1.0.8", @@ -8158,17 +8832,20 @@ } }, "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "engines": { + "node": ">=18" + } }, "node_modules/comment-json": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.5.tgz", "integrity": "sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "array-timsort": "^1.0.3", "core-util-is": "^1.0.3", @@ -8184,23 +8861,23 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/common-path-prefix": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "dev": true, "license": "ISC", + "optional": true, "peer": true }, "node_modules/common-tags": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=4.0.0" } @@ -8209,14 +8886,14 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, + "devOptional": true, "dependencies": { "mime-db": ">= 1.43.0 < 2" }, @@ -8228,7 +8905,7 @@ "version": "1.7.4", "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dev": true, + "devOptional": true, "dependencies": { "accepts": "~1.3.5", "bytes": "3.0.0", @@ -8246,7 +8923,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.8" } @@ -8255,7 +8932,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, + "devOptional": true, "dependencies": { "ms": "2.0.0" } @@ -8264,12 +8941,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "devOptional": true }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "devOptional": true }, "node_modules/concurrently": { "version": "9.1.0", @@ -8393,7 +9071,7 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", - "optional": true + "dev": true }, "node_modules/connect-history-api-fallback": { "version": "2.0.0", @@ -8408,19 +9086,11 @@ "version": "3.4.2", "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "optional": true, + "dev": true, "engines": { "node": "^14.18.0 || >=16.10.0" } }, - "node_modules/console-clear": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/console-clear/-/console-clear-1.1.1.tgz", - "integrity": "sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ==", - "engines": { - "node": ">=4" - } - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -8463,11 +9133,22 @@ "node": ">= 0.6" } }, + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "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==", - "devOptional": true, "license": "MIT" }, "node_modules/cookie": { @@ -8487,20 +9168,19 @@ "dev": true }, "node_modules/copy-webpack-plugin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", - "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.1.tgz", + "integrity": "sha512-J+YV3WfhY6W/Xf9h+J1znYuqTye2xkBUIGyTPWuBAT27qajBa5mR4f8WBmfDY3YjRftT2kqZZiLi1qf0H+UOFw==", "dev": true, "dependencies": { - "fast-glob": "^3.2.11", "glob-parent": "^6.0.1", - "globby": "^13.1.1", "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2", + "tinyglobby": "^0.2.12" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", @@ -8522,75 +9202,51 @@ "node": ">=10.13.0" } }, - "node_modules/copy-webpack-plugin/node_modules/globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "dev": true, - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/copy-webpack-plugin/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/core-js": { - "version": "3.39.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz", - "integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==", + "version": "3.45.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.0.tgz", + "integrity": "sha512-c2KZL9lP4DjkN3hk/an4pWn5b5ZefhRJnAc42n6LJ19kSnbeRbdQZE5dSeE2LBol1OwJD3X1BQvFTAsa8ReeDA==", "dev": true, "hasInstallScript": true, - "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" } }, "node_modules/core-js-compat": { - "version": "3.39.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz", - "integrity": "sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==", - "dev": true, - "license": "MIT", - "peer": true, + "version": "3.45.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.0.tgz", + "integrity": "sha512-gRoVMBawZg0OnxaVv3zpqLLxaHmsubEGyTnqdpI/CEBvX4JadI1dMSHxagThprYRtSVbuQxvi6iUatdPxohHpA==", + "optional": true, "dependencies": { - "browserslist": "^4.24.2" + "browserslist": "^4.25.1" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" } }, + "node_modules/core-js-pure": { + "version": "3.45.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.45.0.tgz", + "integrity": "sha512-OtwjqcDpY2X/eIIg1ol/n0y/X8A9foliaNt1dSK0gV3J2/zw+89FcNG3mPK+N8YWts4ZFUPxnrAzsxs/lf8yDA==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "devOptional": true }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "optional": true, "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -8606,7 +9262,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -8627,7 +9283,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -8642,7 +9298,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -8658,7 +9314,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -8670,13 +9326,13 @@ "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 + "devOptional": true }, "node_modules/create-jest/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -8685,7 +9341,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -8693,24 +9349,6 @@ "node": ">=8" } }, - "node_modules/cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.1" - }, - "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" - }, - "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -8730,8 +9368,8 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-9.1.2.tgz", "integrity": "sha512-QvHHGUuMI5h3ymU6O/Qz8zfhMhvPTuopT1FgebYRBB1cyggl4KnEJKU9m7wy/SQ1IGSlFDtQp6rCy70ujTfavQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "@cspell/cspell-types": "9.1.2", "comment-json": "^4.2.5", @@ -8745,8 +9383,8 @@ "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, "bin": { "yaml": "bin.mjs" }, @@ -8758,8 +9396,8 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-9.1.2.tgz", "integrity": "sha512-Osn5f9ugkX/zA3PVtSmYKRer3gZX3YqVB0UH0wVNzi8Ryl/1RUuYLIcvd0SDEhiVW56WKxFLfZ5sggTz/l9cDA==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "@cspell/cspell-pipe": "9.1.2", "@cspell/cspell-types": "9.1.2", @@ -8774,8 +9412,8 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-9.1.2.tgz", "integrity": "sha512-l7Mqirn5h2tilTXgRamRIqqnzeA7R5iJEtJkY/zHDMEBeLWTR/5ai7dBp2+ooe8gIebpDtvv4938IXa5/75E6g==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "@cspell/url": "9.1.2", "picomatch": "^4.0.2" @@ -8788,8 +9426,8 @@ "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", + "optional": true, "engines": { "node": ">=12" }, @@ -8801,8 +9439,8 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-9.1.2.tgz", "integrity": "sha512-vUcnlUqJKK0yhwYHfGC71zjGyEn918l64U/NWb1ijn1VXrL6gsh3w8Acwdo++zbpOASd9HTAuuZelveDJKLLgA==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "@cspell/cspell-pipe": "9.1.2", "@cspell/cspell-types": "9.1.2" @@ -8818,8 +9456,8 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-9.1.2.tgz", "integrity": "sha512-oLPxbteI+uFV9ZPcJjII7Lr/C/gVXpdmDLlAMwR8/7LHGnEfxXR0lqYu5GZVEvZ7riX9whCUOsQWQQqr2u2Fzw==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "@cspell/cspell-service-bus": "9.1.2", "@cspell/url": "9.1.2" @@ -8832,8 +9470,8 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-9.1.2.tgz", "integrity": "sha512-OFCssgfp6Z2gd1K8j2FsYr9YGoA/C6xXlcUwgU75Ut/XMZ/S44chdA9fUupGd4dUOw+CZl0qKzSP21J6kYObIw==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "@cspell/cspell-bundled-dicts": "9.1.2", "@cspell/cspell-pipe": "9.1.2", @@ -8868,8 +9506,8 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-9.1.2.tgz", "integrity": "sha512-TkIQaknRRusUznqy+HwpqKCETCAznrzPJJHRHi8m6Zo3tAMsnIpaBQPRN8xem6w8/r/yJqFhLrsLSma0swyviQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "@cspell/cspell-pipe": "9.1.2", "@cspell/cspell-types": "9.1.2", @@ -8880,21 +9518,21 @@ } }, "node_modules/css-declaration-sorter": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", - "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", + "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", "dev": true, "engines": { - "node": "^10 || ^12 || >=14" + "node": "^14 || ^16 || >=18" }, "peerDependencies": { "postcss": "^8.0.9" } }, "node_modules/css-loader": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", - "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", @@ -8907,7 +9545,7 @@ "semver": "^7.5.4" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", @@ -8915,7 +9553,7 @@ }, "peerDependencies": { "@rspack/core": "0.x || 1.x", - "webpack": "^5.0.0" + "webpack": "^5.27.0" }, "peerDependenciesMeta": { "@rspack/core": { @@ -8942,20 +9580,20 @@ } }, "node_modules/css-minimizer-webpack-plugin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", - "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-7.0.2.tgz", + "integrity": "sha512-nBRWZtI77PBZQgcXMNqiIXVshiQOVLGSf2qX/WZfG8IQfMbeHUMXaBWQmiiSTmPJUflQxHjZjzAmuyO7tpL2Jg==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "cssnano": "^6.0.1", - "jest-worker": "^29.4.3", - "postcss": "^8.4.24", - "schema-utils": "^4.0.1", - "serialize-javascript": "^6.0.1" + "@jridgewell/trace-mapping": "^0.3.25", + "cssnano": "^7.0.4", + "jest-worker": "^29.7.0", + "postcss": "^8.4.40", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", @@ -8986,9 +9624,9 @@ } }, "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "dev": true, "dependencies": { "boolbase": "^1.0.0", @@ -9031,9 +9669,9 @@ } }, "node_modules/css-select/node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "dev": true, "dependencies": { "dom-serializer": "^2.0.0", @@ -9057,12 +9695,12 @@ } }, "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", "dev": true, "dependencies": { - "mdn-data": "2.0.30", + "mdn-data": "2.12.2", "source-map-js": "^1.0.1" }, "engines": { @@ -9085,7 +9723,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true + "optional": true }, "node_modules/cssesc": { "version": "3.0.0", @@ -9100,78 +9738,79 @@ } }, "node_modules/cssnano": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.1.tgz", - "integrity": "sha512-fVO1JdJ0LSdIGJq68eIxOqFpIJrZqXUsBt8fkrBcztCQqAjQD51OhZp7tc0ImcbwXD4k7ny84QTV90nZhmqbkg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.0.tgz", + "integrity": "sha512-Pu3rlKkd0ZtlCUzBrKL1Z4YmhKppjC1H9jo7u1o4qaKqyhvixFgu5qLyNIAOjSTg9DjVPtUqdROq2EfpVMEe+w==", "dev": true, "dependencies": { - "cssnano-preset-default": "^6.0.1", - "lilconfig": "^2.1.0" + "cssnano-preset-default": "^7.0.8", + "lilconfig": "^3.1.3" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/cssnano" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/cssnano-preset-default": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.1.tgz", - "integrity": "sha512-7VzyFZ5zEB1+l1nToKyrRkuaJIx0zi/1npjvZfbBwbtNTzhLtlvYraK/7/uqmX2Wb2aQtd983uuGw79jAjLSuQ==", + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.8.tgz", + "integrity": "sha512-d+3R2qwrUV3g4LEMOjnndognKirBZISylDZAF/TPeCWVjEwlXS2e4eN4ICkoobRe7pD3H6lltinKVyS1AJhdjQ==", "dev": true, "dependencies": { - "css-declaration-sorter": "^6.3.1", - "cssnano-utils": "^4.0.0", - "postcss-calc": "^9.0.0", - "postcss-colormin": "^6.0.0", - "postcss-convert-values": "^6.0.0", - "postcss-discard-comments": "^6.0.0", - "postcss-discard-duplicates": "^6.0.0", - "postcss-discard-empty": "^6.0.0", - "postcss-discard-overridden": "^6.0.0", - "postcss-merge-longhand": "^6.0.0", - "postcss-merge-rules": "^6.0.1", - "postcss-minify-font-values": "^6.0.0", - "postcss-minify-gradients": "^6.0.0", - "postcss-minify-params": "^6.0.0", - "postcss-minify-selectors": "^6.0.0", - "postcss-normalize-charset": "^6.0.0", - "postcss-normalize-display-values": "^6.0.0", - "postcss-normalize-positions": "^6.0.0", - "postcss-normalize-repeat-style": "^6.0.0", - "postcss-normalize-string": "^6.0.0", - "postcss-normalize-timing-functions": "^6.0.0", - "postcss-normalize-unicode": "^6.0.0", - "postcss-normalize-url": "^6.0.0", - "postcss-normalize-whitespace": "^6.0.0", - "postcss-ordered-values": "^6.0.0", - "postcss-reduce-initial": "^6.0.0", - "postcss-reduce-transforms": "^6.0.0", - "postcss-svgo": "^6.0.0", - "postcss-unique-selectors": "^6.0.0" + "browserslist": "^4.25.1", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^5.0.1", + "postcss-calc": "^10.1.1", + "postcss-colormin": "^7.0.4", + "postcss-convert-values": "^7.0.6", + "postcss-discard-comments": "^7.0.4", + "postcss-discard-duplicates": "^7.0.2", + "postcss-discard-empty": "^7.0.1", + "postcss-discard-overridden": "^7.0.1", + "postcss-merge-longhand": "^7.0.5", + "postcss-merge-rules": "^7.0.6", + "postcss-minify-font-values": "^7.0.1", + "postcss-minify-gradients": "^7.0.1", + "postcss-minify-params": "^7.0.4", + "postcss-minify-selectors": "^7.0.5", + "postcss-normalize-charset": "^7.0.1", + "postcss-normalize-display-values": "^7.0.1", + "postcss-normalize-positions": "^7.0.1", + "postcss-normalize-repeat-style": "^7.0.1", + "postcss-normalize-string": "^7.0.1", + "postcss-normalize-timing-functions": "^7.0.1", + "postcss-normalize-unicode": "^7.0.4", + "postcss-normalize-url": "^7.0.1", + "postcss-normalize-whitespace": "^7.0.1", + "postcss-ordered-values": "^7.0.2", + "postcss-reduce-initial": "^7.0.4", + "postcss-reduce-transforms": "^7.0.1", + "postcss-svgo": "^7.1.0", + "postcss-unique-selectors": "^7.0.4" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/cssnano-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.0.tgz", - "integrity": "sha512-Z39TLP+1E0KUcd7LGyF4qMfu8ZufI0rDzhdyAMsa/8UyNUU8wpS0fhdBxbQbv32r64ea00h4878gommRVg2BHw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.1.tgz", + "integrity": "sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==", "dev": true, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/csso": { @@ -9234,18 +9873,16 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/cypress": { - "version": "13.16.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.16.1.tgz", - "integrity": "sha512-17FtCaz0cx7ssWYKXzGB0Vub8xHwpVPr+iPt2fHhLMDhVAPVrplD+rTQsZUsfb19LVBn5iwkEUFjQ1yVVJXsLA==", - "dev": true, + "version": "14.5.4", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-14.5.4.tgz", + "integrity": "sha512-0Dhm4qc9VatOcI1GiFGVt8osgpPdqJLHzRwcAB5MSD/CAAts3oybvPUPawHyvJZUd8osADqZe/xzMsZ8sDTjXw==", "hasInstallScript": true, - "license": "MIT", + "optional": true, "dependencies": { - "@cypress/request": "^3.0.6", + "@cypress/request": "^3.0.9", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", @@ -9256,9 +9893,9 @@ "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", - "ci-info": "^4.0.0", + "ci-info": "^4.1.0", "cli-cursor": "^3.1.0", - "cli-table3": "~0.6.1", + "cli-table3": "0.6.1", "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", @@ -9271,6 +9908,7 @@ "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", + "hasha": "5.2.2", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", @@ -9282,7 +9920,7 @@ "process": "^0.11.10", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", - "semver": "^7.5.3", + "semver": "^7.7.1", "supports-color": "^8.1.1", "tmp": "~0.2.3", "tree-kill": "1.2.2", @@ -9293,29 +9931,28 @@ "cypress": "bin/cypress" }, "engines": { - "node": "^16.0.0 || ^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" } }, "node_modules/cypress-axe": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/cypress-axe/-/cypress-axe-1.5.0.tgz", - "integrity": "sha512-Hy/owCjfj+25KMsecvDgo4fC/781ccL+e8p+UUYoadGVM2ogZF9XIKbiM6KI8Y3cEaSreymdD6ZzccbI2bY0lQ==", - "dev": true, - "license": "MIT", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/cypress-axe/-/cypress-axe-1.6.0.tgz", + "integrity": "sha512-C/ij50G8eebBrl/WsGT7E+T/SFyIsRZ3Epx9cRTLrPL9Y1GcxlQGFoAVdtSFWRrHSCWXq9HC6iJQMaI89O9yvQ==", + "optional": true, "engines": { "node": ">=10" }, "peerDependencies": { "axe-core": "^3 || ^4", - "cypress": "^10 || ^11 || ^12 || ^13" + "cypress": "^10 || ^11 || ^12 || ^13 || ^14" } }, "node_modules/cypress-high-resolution": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/cypress-high-resolution/-/cypress-high-resolution-1.0.0.tgz", "integrity": "sha512-uZRmUVBYbh7Hdid6dwzFp2/iCf9FWlK6qPTQZ7twzqkmpRtfTGOXlUttBSts8EHBu9wye3HgwVY/Lr2Pzipckw==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "debug": "^4.3.2" } @@ -9324,8 +9961,8 @@ "version": "3.8.2", "resolved": "https://registry.npmjs.org/cypress-mochawesome-reporter/-/cypress-mochawesome-reporter-3.8.2.tgz", "integrity": "sha512-oJZkNzhNmN9ZD+LmZyFuPb8aWaIijyHyqYh52YOBvR6B6ckfJNCHP3A98a+/nG0H4t46CKTNwo+wNpMa4d2kjA==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "commander": "^10.0.1", "fs-extra": "^10.0.1", @@ -9350,8 +9987,8 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=14" } @@ -9360,8 +9997,8 @@ "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", + "optional": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -9372,14 +10009,14 @@ } }, "node_modules/cypress-multi-reporters": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/cypress-multi-reporters/-/cypress-multi-reporters-2.0.4.tgz", - "integrity": "sha512-TZKzSfo8ReU2Fuj1n90gi4Ocw1a/nh6utiq9g0wy27muq1/IjZXdR97WXkV0to2vd8NRldXt+tuKEmxQrp8LDg==", - "dev": true, - "license": "MIT", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cypress-multi-reporters/-/cypress-multi-reporters-2.0.5.tgz", + "integrity": "sha512-5ReXlNE7C/9/rpDI3z0tAJbPXsTHK7P3ogvUtBntQlmctRQ+sSMts7dIQY5MTb0XfBSge3CuwvNvaoqtw90KSQ==", + "optional": true, "dependencies": { - "debug": "^4.3.7", - "lodash": "^4.17.21" + "debug": "^4.4.0", + "lodash": "^4.17.21", + "semver": "^7.6.3" }, "engines": { "node": ">=6.0.0" @@ -9388,12 +10025,24 @@ "mocha": ">=3.1.2" } }, + "node_modules/cypress-multi-reporters/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==", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/cypress/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", + "optional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -9408,15 +10057,15 @@ "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/cypress/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", + "optional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -9432,8 +10081,8 @@ "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", + "optional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -9442,17 +10091,16 @@ } }, "node_modules/cypress/node_modules/ci-info": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz", - "integrity": "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==", - "dev": true, + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/sibiraj-s" } ], - "license": "MIT", + "optional": true, "engines": { "node": ">=8" } @@ -9461,8 +10109,8 @@ "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", + "optional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -9474,15 +10122,15 @@ "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" + "license": "MIT", + "optional": true }, "node_modules/cypress/node_modules/commander": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">= 6" } @@ -9491,8 +10139,8 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", @@ -9515,8 +10163,8 @@ "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", + "optional": true, "dependencies": { "pump": "^3.0.0" }, @@ -9531,8 +10179,8 @@ "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", + "optional": true, "engines": { "node": ">=8" } @@ -9541,18 +10189,17 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, "license": "Apache-2.0", + "optional": true, "engines": { "node": ">=8.12.0" } }, "node_modules/cypress/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "optional": true, "bin": { "semver": "bin/semver.js" }, @@ -9564,8 +10211,8 @@ "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", + "optional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -9587,8 +10234,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "license": "MIT", + "optional": true, "dependencies": { "assert-plus": "^1.0.0" }, @@ -9645,13 +10291,14 @@ } }, "node_modules/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "optional": true, "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -9661,27 +10308,29 @@ } }, "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "optional": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/inspect-js" } }, "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "optional": true, "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" }, @@ -9705,8 +10354,8 @@ "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": "*" } @@ -9715,8 +10364,8 @@ "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/debounce": { "version": "1.2.1", @@ -9728,7 +10377,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "devOptional": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -9746,8 +10394,8 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } @@ -9762,7 +10410,7 @@ "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", - "dev": true, + "devOptional": true, "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, @@ -9772,45 +10420,12 @@ } } }, - "node_modules/deep-equal": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", - "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.5", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.2", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=4.0.0" } @@ -9825,7 +10440,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -9864,8 +10479,8 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "strip-bom": "^4.0.0" }, @@ -9880,6 +10495,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "optional": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -9909,6 +10525,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "optional": true, "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -9925,7 +10542,7 @@ "version": "6.1.4", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "optional": true + "dev": true }, "node_modules/delayed-stream": { "version": "1.0.0", @@ -9948,7 +10565,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, + "optional": true, "engines": { "node": ">=6" } @@ -9957,7 +10574,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", - "optional": true + "dev": true }, "node_modules/destroy": { "version": "1.2.0", @@ -9987,7 +10604,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -10002,8 +10619,8 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, "license": "BSD-3-Clause", + "optional": true, "engines": { "node": ">=0.3.1" } @@ -10012,7 +10629,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, + "devOptional": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -10021,7 +10638,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, + "optional": true, "dependencies": { "path-type": "^4.0.0" }, @@ -10058,7 +10675,7 @@ "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true + "optional": true }, "node_modules/dom-converter": { "version": "0.2.0", @@ -10141,6 +10758,14 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", @@ -10165,11 +10790,38 @@ "tslib": "^2.0.3" } }, + "node_modules/dot-prop": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-8.0.2.tgz", + "integrity": "sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ==", + "dev": true, + "dependencies": { + "type-fest": "^3.8.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dot-prop/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dotenv": { "version": "16.5.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", - "devOptional": true, + "dev": true, "engines": { "node": ">=12" }, @@ -10177,20 +10829,22 @@ "url": "https://dotenvx.com" } }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true + }, "node_modules/dotenv-webpack": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-8.1.0.tgz", - "integrity": "sha512-owK1JcsPkIobeqjVrk6h7jPED/W6ZpdFsMPR+5ursB7/SdgDyO+VzAU+szK8C8u3qUhtENyYnj8eyXMR5kkGag==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-6.0.4.tgz", + "integrity": "sha512-WiTPNLanDNJ1O8AvgkBpsbarw78a4PMYG2EfJcQoxTHFWy+ji213HR+3f4PhWB1RBumiD9cbiuC3SNxJXbBp9g==", "dev": true, - "license": "MIT", "dependencies": { - "dotenv-defaults": "^2.0.2" - }, - "engines": { - "node": ">=10" + "dotenv-defaults": "^2.0.1" }, "peerDependencies": { - "webpack": "^4 || ^5" + "webpack": "^1 || ^2 || ^3 || ^4 || ^5" } }, "node_modules/dotenv-webpack/node_modules/dotenv": { @@ -10242,8 +10896,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "license": "MIT", + "optional": true, "dependencies": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -10257,17 +10910,16 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.52", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.52.tgz", - "integrity": "sha512-xtoijJTZ+qeucLBDNztDOuQBE1ksqjvNjvqFoST3nGC7fSpqJ+X6BdTBaY5BHG+IhWWmpc6b/KfpeuEDupEPOQ==", - "dev": true, - "license": "ISC" + "version": "1.5.207", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.207.tgz", + "integrity": "sha512-mryFrrL/GXDTmAtIVMVf+eIXM09BBPlO5IQ7lUyKmK8d+A4VpRGG+M3ofoVef6qyF8s60rJei8ymlJxjUA8Faw==", + "devOptional": true }, "node_modules/emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=12" }, @@ -10304,18 +10956,17 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "once": "^1.4.0" } }, "node_modules/enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", "devOptional": true, - "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -10328,8 +10979,8 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" @@ -10351,8 +11002,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -10361,9 +11012,9 @@ } }, "node_modules/envinfo": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", - "integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", + "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", "dev": true, "bin": { "envinfo": "dist/cli.js" @@ -10372,6 +11023,18 @@ "node": ">=4" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -10380,58 +11043,75 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/es-abstract": { - "version": "1.23.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.5.tgz", - "integrity": "sha512-vlmniQ0WNPwXqA0BnmwV3Ng7HxiGlh6r5U6JcTMNx8OilcAGqVJBHJcPjqOMaczU9fRuRK5Px2BdVyPRnKMMVQ==", - "license": "MIT", + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", + "stackframe": "^1.3.4" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "optional": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", - "gopd": "^1.0.1", + "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", + "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.3", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" }, "engines": { "node": ">= 0.4" @@ -10457,49 +11137,28 @@ "node": ">= 0.4" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-iterator-helpers": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.0.tgz", - "integrity": "sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==", - "license": "MIT", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", "optional": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", + "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.0.3", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", + "get-intrinsic": "^1.2.6", "globalthis": "^1.0.4", - "gopd": "^1.0.1", + "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.7", - "iterator.prototype": "^1.1.3", - "safe-array-concat": "^1.1.2" + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" }, "engines": { "node": ">= 0.4" @@ -10509,12 +11168,12 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", - "dev": true + "devOptional": true }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dependencies": { "es-errors": "^1.3.0" }, @@ -10547,13 +11206,14 @@ } }, "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "optional": true, "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { "node": ">= 0.4" @@ -10566,14 +11226,14 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/es6-promise": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", - "optional": true + "dev": true }, "node_modules/escalade": { "version": "3.2.0", @@ -10589,12 +11249,13 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true + "devOptional": true }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "optional": true, "engines": { "node": ">=0.8.0" } @@ -10738,26 +11399,24 @@ } }, "node_modules/eslint-import-resolver-typescript": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.7.0.tgz", - "integrity": "sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow==", - "license": "ISC", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", "optional": true, "dependencies": { "@nolyfill/is-core-module": "1.0.39", - "debug": "^4.3.7", - "enhanced-resolve": "^5.15.0", - "fast-glob": "^3.3.2", - "get-tsconfig": "^4.7.5", - "is-bun-module": "^1.0.2", - "is-glob": "^4.0.3", - "stable-hash": "^0.0.4" + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + "url": "https://opencollective.com/eslint-import-resolver-typescript" }, "peerDependencies": { "eslint": "*", @@ -10986,7 +11645,8 @@ "node_modules/eslint-plugin-local-rules": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/eslint-plugin-local-rules/-/eslint-plugin-local-rules-3.0.2.tgz", - "integrity": "sha512-IWME7GIYHXogTkFsToLdBCQVJ0U4kbSuVyDT+nKoR4UgtnVrrVeNWuAZkdEu1nxkvi9nsPccGehEEF6dgA28IQ==" + "integrity": "sha512-IWME7GIYHXogTkFsToLdBCQVJ0U4kbSuVyDT+nKoR4UgtnVrrVeNWuAZkdEu1nxkvi9nsPccGehEEF6dgA28IQ==", + "optional": true }, "node_modules/eslint-plugin-no-only-tests": { "version": "3.3.0", @@ -10999,20 +11659,19 @@ } }, "node_modules/eslint-plugin-no-relative-import-paths": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-no-relative-import-paths/-/eslint-plugin-no-relative-import-paths-1.5.5.tgz", - "integrity": "sha512-UjudFFdBbv93v0CsVdEKcMLbBzRIjeK2PubTctX57tgnHxZcMj1Jm8lDBWoETnPxk0S5g5QLSltEM+511yL4+w==", - "license": "ISC", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-relative-import-paths/-/eslint-plugin-no-relative-import-paths-1.6.1.tgz", + "integrity": "sha512-YZNeOnsOrJcwhFw0X29MXjIzu2P/f5X2BZDPWw1R3VUYBRFxNIh77lyoL/XrMU9ewZNQPcEvAgL/cBOT1P330A==", "optional": true }, "node_modules/eslint-plugin-prettier": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", - "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", + "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", "optional": true, "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.6" + "synckit": "^0.11.7" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -11023,7 +11682,7 @@ "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", - "eslint-config-prettier": "*", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "peerDependenciesMeta": { @@ -11036,29 +11695,28 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.37.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.2.tgz", - "integrity": "sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==", - "license": "MIT", + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "optional": true, "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.2", + "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.1.0", + "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.8", + "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", - "object.values": "^1.2.0", + "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.11", + "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "engines": { @@ -11069,10 +11727,9 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0.tgz", - "integrity": "sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==", - "license": "MIT", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", "optional": true, "engines": { "node": ">=10" @@ -11103,13 +11760,12 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "license": "Apache-2.0", - "optional": true, + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "devOptional": true, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -11192,18 +11848,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "devOptional": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/eslint/node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", @@ -11351,23 +11995,11 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "devOptional": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "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, + "devOptional": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -11422,7 +12054,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=4.0" } @@ -11440,7 +12072,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/eta/-/eta-2.2.0.tgz", "integrity": "sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==", - "optional": true, + "dev": true, "engines": { "node": ">=6.0.0" }, @@ -11462,8 +12094,8 @@ "version": "6.4.7", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/eventemitter3": { "version": "4.0.7", @@ -11475,7 +12107,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.8.x" } @@ -11484,7 +12116,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, + "devOptional": true, "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -11507,8 +12139,8 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "pify": "^2.2.0" }, @@ -11520,8 +12152,8 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } @@ -11530,25 +12162,284 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.8.0" } }, "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.5.tgz", + "integrity": "sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ==", "dev": true, "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" + "@jest/expect-utils": "30.0.5", + "@jest/get-type": "30.0.1", + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/expect/node_modules/@jest/expect-utils": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.5.tgz", + "integrity": "sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==", + "dev": true, + "dependencies": { + "@jest/get-type": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/expect/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/expect/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dev": true, + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/expect/node_modules/@sinclair/typebox": { + "version": "0.34.40", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.40.tgz", + "integrity": "sha512-gwBNIP8ZAYev/ORDWW0QvxdwPXwxBtLsdsJgSc7eDIRt8ubP+rxUBzPsrwnu16fgEF8Bx4lh/+mvQvJzcTM6Kw==", + "dev": true + }, + "node_modules/expect/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/expect/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/expect/node_modules/ci-info": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/expect/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/expect/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/expect/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/expect/node_modules/jest-diff": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.5.tgz", + "integrity": "sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==", + "dev": true, + "dependencies": { + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.0.1", + "chalk": "^4.1.2", + "pretty-format": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/expect/node_modules/jest-matcher-utils": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz", + "integrity": "sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ==", + "dev": true, + "dependencies": { + "@jest/get-type": "30.0.1", + "chalk": "^4.1.2", + "jest-diff": "30.0.5", + "pretty-format": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/expect/node_modules/jest-message-util": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.5.tgz", + "integrity": "sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.0.5", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.0.5", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/expect/node_modules/jest-mock": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", + "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", + "dev": true, + "dependencies": { + "@jest/types": "30.0.5", + "@types/node": "*", + "jest-util": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/expect/node_modules/jest-util": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", + "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", + "dev": true, + "dependencies": { + "@jest/types": "30.0.5", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/expect/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/expect/node_modules/pretty-format": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", + "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", + "dev": true, + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/expect/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/expect/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/expect/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/express": { @@ -11652,24 +12543,23 @@ } }, "node_modules/exsolve": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.5.tgz", - "integrity": "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==", - "optional": true + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "dev": true }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "license": "MIT" + "optional": true }, "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", + "optional": true, "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", @@ -11689,8 +12579,8 @@ "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", + "optional": true, "dependencies": { "pump": "^3.0.0" }, @@ -11705,11 +12595,10 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, "engines": [ "node >=0.6.0" ], - "license": "MIT" + "optional": true }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -11727,24 +12616,23 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "devOptional": true, - "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -11778,7 +12666,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "optional": true + "dev": true }, "node_modules/fastest-levenshtein": { "version": "1.0.12", @@ -11811,7 +12699,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, + "devOptional": true, "dependencies": { "bser": "2.1.1" } @@ -11820,8 +12708,8 @@ "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", + "optional": true, "dependencies": { "pend": "~1.2.0" } @@ -11830,8 +12718,8 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -11874,20 +12762,6 @@ "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/file-loader/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, "node_modules/file-loader/node_modules/schema-utils": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", @@ -11919,22 +12793,51 @@ } }, "node_modules/file-type": { - "version": "16.5.4", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", - "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-19.6.0.tgz", + "integrity": "sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==", "dev": true, "dependencies": { - "readable-web-to-node-stream": "^3.0.0", - "strtok3": "^6.2.4", - "token-types": "^4.1.1" + "get-stream": "^9.0.1", + "strtok3": "^9.0.1", + "token-types": "^6.0.0", + "uint8array-extras": "^1.3.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sindresorhus/file-type?sponsor=1" } }, + "node_modules/file-type/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-type/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -11985,8 +12888,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", - "dev": true, "license": "MIT", + "optional": true, "peer": true, "dependencies": { "common-path-prefix": "^3.0.0", @@ -12003,8 +12906,8 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "dev": true, "license": "MIT", + "optional": true, "peer": true, "dependencies": { "locate-path": "^7.1.0", @@ -12021,8 +12924,8 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "dev": true, "license": "MIT", + "optional": true, "peer": true, "dependencies": { "p-locate": "^6.0.0" @@ -12038,8 +12941,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, "license": "MIT", + "optional": true, "peer": true, "dependencies": { "yocto-queue": "^1.0.0" @@ -12055,8 +12958,8 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dev": true, "license": "MIT", + "optional": true, "peer": true, "dependencies": { "p-limit": "^4.0.0" @@ -12072,8 +12975,8 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true, "license": "MIT", + "optional": true, "peer": true, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -12083,8 +12986,8 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", - "dev": true, "license": "MIT", + "optional": true, "peer": true, "dependencies": { "find-up": "^6.3.0" @@ -12100,8 +13003,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", - "dev": true, "license": "MIT", + "optional": true, "peer": true, "engines": { "node": ">=12.20" @@ -12113,14 +13016,13 @@ "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==", - "optional": true + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, + "devOptional": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -12133,7 +13035,7 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, + "devOptional": true, "bin": { "flat": "cli.js" } @@ -12185,11 +13087,18 @@ } }, "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "optional": true, "dependencies": { - "is-callable": "^1.1.3" + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/foreground-child": { @@ -12226,8 +13135,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "license": "Apache-2.0", + "optional": true, "engines": { "node": "*" } @@ -12449,7 +13357,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "dev": true, "funding": [ { "type": "github", @@ -12464,14 +13371,15 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -12492,13 +13400,12 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "devOptional": true }, "node_modules/fsevents": { "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, @@ -12513,8 +13420,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/fsu/-/fsu-1.1.1.tgz", "integrity": "sha512-xQVsnjJ/5pQtcKh+KjUoZGzVWn4uNkchxTF6Lwjr4Gf7nQr8fmUfhKJ62zE77+xQg9xnxi5KUps7XGs+VC986A==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/function-bind": { "version": "1.1.2", @@ -12524,15 +13431,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function-timeout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", + "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "optional": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" }, "engines": { "node": ">= 0.4" @@ -12545,6 +13467,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "optional": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -12553,8 +13476,8 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/gensequence/-/gensequence-7.0.0.tgz", "integrity": "sha512-47Frx13aZh01afHJTB3zTtKIlFI6vWY+MYCN9Qpew6i52rfKjnhCF/l1YlC8UmEMvvntZZ6z4PiCcmyuedR2aQ==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=18" } @@ -12563,7 +13486,7 @@ "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, + "devOptional": true, "engines": { "node": ">=6.9.0" } @@ -12578,21 +13501,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", - "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", - "license": "MIT", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "dunder-proto": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", + "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "math-intrinsics": "^1.0.0" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -12605,24 +13527,28 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8.0.0" } }, - "node_modules/get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": ">=4" + "node": ">= 0.4" } }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -12631,13 +13557,14 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "optional": true, "dependencies": { - "call-bind": "^1.0.5", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -12647,10 +13574,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", - "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", - "license": "MIT", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", "optional": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -12663,8 +13589,8 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "async": "^3.2.0" } @@ -12673,8 +13599,7 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "license": "MIT", + "optional": true, "dependencies": { "assert-plus": "^1.0.0" } @@ -12683,7 +13608,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", - "optional": true, + "dev": true, "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", @@ -12700,7 +13625,7 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, + "devOptional": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -12720,14 +13645,14 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true + "devOptional": true }, "node_modules/global-directory": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "ini": "4.1.1" }, @@ -12742,8 +13667,8 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", - "dev": true, "license": "ISC", + "optional": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -12752,8 +13677,8 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "ini": "2.0.0" }, @@ -12764,19 +13689,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "devOptional": true, - "engines": { - "node": ">=4" - } - }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "optional": true, "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" @@ -12792,7 +13709,7 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, + "optional": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -12823,7 +13740,8 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "devOptional": true }, "node_modules/graphemer": { "version": "1.4.0", @@ -12837,10 +13755,35 @@ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "optional": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "optional": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -12849,6 +13792,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "optional": true, "engines": { "node": ">=4" } @@ -12857,8 +13801,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=8" } @@ -12867,6 +13811,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "optional": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -12875,9 +13820,13 @@ } }, "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "optional": true, + "dependencies": { + "dunder-proto": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -12915,8 +13864,8 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "is-stream": "^2.0.0", "type-fest": "^0.8.0" @@ -12928,6 +13877,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -12943,7 +13901,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, + "devOptional": true, "bin": { "he": "bin/he" } @@ -12952,7 +13910,6 @@ "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==", - "optional": true, "dependencies": { "react-is": "^16.7.0" } @@ -12960,14 +13917,14 @@ "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==", - "optional": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/hpack.js": { "version": "2.1.6", @@ -12993,16 +13950,32 @@ "node": ">=12" } }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "devOptional": true }, "node_modules/html-webpack-plugin": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", - "integrity": "sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==", + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.4.tgz", + "integrity": "sha512-V/PZeWsqhfpE27nKeX9EO2sbR+D17A+tLf6qU+ht66jdUsN0QLKJN27Z+1+gHrVMKgndBahes0PU6rRihDgHTw==", "dev": true, "dependencies": { "@types/html-minifier-terser": "^6.0.0", @@ -13118,30 +14091,6 @@ "strip-ansi": "^6.0.1" } }, - "node_modules/html-webpack-plugin/node_modules/terser": { - "version": "5.26.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.26.0.tgz", - "integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/html-webpack-plugin/node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "node_modules/htmlparser2": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", @@ -13267,8 +14216,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", - "dev": true, - "license": "MIT", + "optional": true, "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^2.0.2", @@ -13282,7 +14230,7 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", - "optional": true + "dev": true }, "node_modules/https-proxy-agent": { "version": "5.0.1", @@ -13301,7 +14249,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10.17.0" } @@ -13356,11 +14304,26 @@ "postcss": "^8.1.0" } }, + "node_modules/identifier-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/identifier-regex/-/identifier-regex-1.0.0.tgz", + "integrity": "sha512-Rcy5cjBOM9iTR+Vwy0Llyip9u0cA99T1yiWOhDW/+PDaTQhyski0tMovsipQ/FRNDkudjLWusJ/IMVIlG5WZnQ==", + "dev": true, + "dependencies": { + "reserved-identifiers": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -13386,63 +14349,93 @@ "node": ">= 4" } }, - "node_modules/imagemin": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-8.0.1.tgz", - "integrity": "sha512-Q/QaPi+5HuwbZNtQRqUVk6hKacI6z9iWiCSQBisAv7uBynZwO7t1svkryKl7+iSQbkU/6t9DWnHz04cFs2WY7w==", + "node_modules/image-dimensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/image-dimensions/-/image-dimensions-2.3.0.tgz", + "integrity": "sha512-8Ar3lsO6+/JLfnUeHnR8Jp/IyQR85Jut5t4Swy1yiXNwj/xM9h5V53v5KE/m/ZSMG4qGRopnSy37uPzKyQCv0A==", "dev": true, - "dependencies": { - "file-type": "^16.5.3", - "globby": "^12.0.0", - "graceful-fs": "^4.2.8", - "junk": "^3.1.0", - "p-pipe": "^4.0.0", - "replace-ext": "^2.0.0", - "slash": "^3.0.0" + "bin": { + "image-dimensions": "cli.js" }, "engines": { - "node": ">=12" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/imagemin/node_modules/array-union": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", - "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", + "node_modules/imagemin": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-9.0.1.tgz", + "integrity": "sha512-UoHOfynN8QeqRoUGunn6ilMnLpJ+utbmleP2ufcFqaGal8mY/PeOpV43N31uqtb+CBMFqQ7hxgKzIaAAnmcrdA==", "dev": true, + "dependencies": { + "change-file-extension": "^0.1.1", + "environment": "^1.0.0", + "file-type": "^19.0.0", + "globby": "^14.0.1", + "image-dimensions": "^2.3.0", + "junk": "^4.0.1", + "ow": "^2.0.0", + "p-pipe": "^4.0.0", + "slash": "^5.1.0", + "uint8array-extras": "^1.1.0" + }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/imagemin/node_modules/globby": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", - "integrity": "sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", "dev": true, "dependencies": { - "array-union": "^3.0.1", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.7", - "ignore": "^5.1.9", - "merge2": "^1.4.1", - "slash": "^4.0.0" + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/imagemin/node_modules/globby/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "node_modules/imagemin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "engines": { - "node": ">=12" + "node": ">= 4" + } + }, + "node_modules/imagemin/node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "engines": { + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -13458,7 +14451,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "devOptional": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -13475,7 +14467,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "devOptional": true, "engines": { "node": ">=4" } @@ -13484,7 +14475,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, + "devOptional": true, "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" @@ -13500,8 +14491,8 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", - "dev": true, "license": "MIT", + "optional": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -13520,7 +14511,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, + "optional": true, "engines": { "node": ">=8" } @@ -13529,7 +14520,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, + "devOptional": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -13539,14 +14530,14 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "devOptional": true }, "node_modules/ini": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true, "license": "ISC", + "optional": true, "engines": { "node": ">=10" } @@ -13556,6 +14547,7 @@ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "license": "MIT", + "optional": true, "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", @@ -13584,30 +14576,15 @@ "node": ">= 10" } }, - "node_modules/is-arguments": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "optional": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -13622,13 +14599,16 @@ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, "node_modules/is-async-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", - "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", - "license": "MIT", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "optional": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -13638,11 +14618,15 @@ } }, "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "optional": true, "dependencies": { - "has-bigints": "^1.0.1" + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -13652,7 +14636,7 @@ "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, + "devOptional": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -13661,12 +14645,13 @@ } }, "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "optional": true, "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -13676,20 +14661,18 @@ } }, "node_modules/is-bun-module": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.3.0.tgz", - "integrity": "sha512-DgXeu5UWI0IsMQundYb5UAOzm6G2eVnarJ0byP6Tm55iZNKceD59LNPA2L4VvsScTtHcw0yEkVwSf7PC+QoLSA==", - "license": "MIT", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", "optional": true, "dependencies": { - "semver": "^7.6.3" + "semver": "^7.7.1" } }, "node_modules/is-bun-module/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "license": "ISC", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "optional": true, "bin": { "semver": "bin/semver.js" @@ -13702,6 +14685,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "optional": true, "engines": { "node": ">= 0.4" }, @@ -13710,10 +14694,9 @@ } }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", - "license": "MIT", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dependencies": { "hasown": "^2.0.2" }, @@ -13725,10 +14708,13 @@ } }, "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "optional": true, "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" }, "engines": { @@ -13739,11 +14725,13 @@ } }, "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "optional": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -13756,7 +14744,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", - "dev": true, + "optional": true, "bin": { "is-docker": "cli.js" }, @@ -13777,13 +14765,12 @@ } }, "node_modules/is-finalizationregistry": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.0.tgz", - "integrity": "sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA==", - "license": "MIT", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "optional": true, "dependencies": { - "call-bind": "^1.0.7" + "call-bound": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -13805,19 +14792,21 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } }, "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "license": "MIT", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", "optional": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -13838,6 +14827,22 @@ "node": ">=0.10.0" } }, + "node_modules/is-identifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-identifier/-/is-identifier-1.0.1.tgz", + "integrity": "sha512-HQ5v4rEJ7REUV54bCd2l5FaD299SGDEn2UPoVXaTHAyGviLq2menVUD2udi3trQ32uvB6LdAh/0ck2EuizrtpA==", + "dev": true, + "dependencies": { + "identifier-regex": "^1.0.0", + "super-regex": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -13877,8 +14882,8 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "global-dirs": "^3.0.0", "is-path-inside": "^3.0.2" @@ -13894,8 +14899,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "devOptional": true, - "license": "MIT", + "optional": true, "engines": { "node": ">= 0.4" }, @@ -13907,6 +14911,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "optional": true, "engines": { "node": ">= 0.4" }, @@ -13928,9 +14933,14 @@ } }, "node_modules/is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "optional": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, "engines": { "node": ">= 0.4" }, @@ -13971,21 +14981,12 @@ "node": ">=0.10.0" } }, - "node_modules/is-plain-object/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-port-reachable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -14000,12 +15001,15 @@ "dev": true }, "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "optional": true, "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -14018,8 +15022,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "devOptional": true, - "license": "MIT", + "optional": true, "engines": { "node": ">= 0.4" }, @@ -14028,11 +15031,12 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "optional": true, "dependencies": { - "call-bind": "^1.0.7" + "call-bound": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -14045,7 +15049,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -14055,11 +15059,13 @@ } }, "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "optional": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -14069,11 +15075,14 @@ } }, "node_modules/is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "optional": true, "dependencies": { - "has-symbols": "^1.0.1" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -14083,11 +15092,12 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "optional": true, "dependencies": { - "which-typed-array": "^1.1.14" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -14100,15 +15110,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "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", + "optional": true, "engines": { "node": ">=10" }, @@ -14120,8 +15130,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "devOptional": true, - "license": "MIT", + "optional": true, "engines": { "node": ">= 0.4" }, @@ -14130,25 +15139,28 @@ } }, "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "optional": true, "dependencies": { - "call-bind": "^1.0.2" + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-weakset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", - "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", - "devOptional": true, - "license": "MIT", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "optional": true, "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -14161,8 +15173,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } @@ -14171,7 +15183,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, + "optional": true, "dependencies": { "is-docker": "^2.0.0" }, @@ -14183,25 +15195,34 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" + "optional": true }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "devOptional": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true, - "license": "MIT" + "optional": true }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -14210,8 +15231,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, "license": "BSD-3-Clause", + "optional": true, "dependencies": { "append-transform": "^2.0.0" }, @@ -14223,7 +15244,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -14239,8 +15260,8 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", - "dev": true, "license": "ISC", + "optional": true, "dependencies": { "archy": "^1.0.0", "cross-spawn": "^7.0.3", @@ -14257,8 +15278,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "aggregate-error": "^3.0.0" }, @@ -14270,7 +15291,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, + "devOptional": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -14284,7 +15305,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -14293,7 +15314,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, + "devOptional": true, "dependencies": { "semver": "^7.5.3" }, @@ -14308,7 +15329,7 @@ "version": "7.6.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "dev": true, + "devOptional": true, "bin": { "semver": "bin/semver.js" }, @@ -14320,7 +15341,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -14332,7 +15353,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, + "devOptional": true, "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -14346,7 +15367,7 @@ "version": "3.1.7", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, + "devOptional": true, "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -14356,17 +15377,17 @@ } }, "node_modules/iterator.prototype": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.3.tgz", - "integrity": "sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==", - "license": "MIT", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", "optional": true, "dependencies": { - "define-properties": "^1.2.1", - "get-intrinsic": "^1.2.1", - "has-symbols": "^1.0.3", - "reflect.getprototypeof": "^1.0.4", - "set-function-name": "^2.0.1" + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -14392,7 +15413,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -14418,7 +15439,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, + "devOptional": true, "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", @@ -14432,7 +15453,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -14447,7 +15468,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -14478,7 +15499,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -14493,7 +15514,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -14509,7 +15530,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -14521,13 +15542,13 @@ "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 + "devOptional": true }, "node_modules/jest-circus/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -14536,7 +15557,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -14551,7 +15572,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -14563,7 +15584,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", @@ -14596,7 +15617,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -14611,7 +15632,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -14627,7 +15648,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -14639,13 +15660,13 @@ "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 + "devOptional": true }, "node_modules/jest-cli/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -14654,7 +15675,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -14666,7 +15687,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", @@ -14711,7 +15732,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -14726,7 +15747,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -14742,7 +15763,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -14754,13 +15775,13 @@ "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 + "devOptional": true }, "node_modules/jest-config/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -14769,7 +15790,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -14781,7 +15802,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, + "devOptional": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", @@ -14796,7 +15817,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -14811,7 +15832,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -14827,7 +15848,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -14839,13 +15860,13 @@ "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 + "devOptional": true }, "node_modules/jest-diff/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -14854,7 +15875,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -14866,7 +15887,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, + "devOptional": true, "dependencies": { "detect-newline": "^3.0.0" }, @@ -14878,7 +15899,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -14894,7 +15915,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -14909,7 +15930,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -14925,7 +15946,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -14937,13 +15958,13 @@ "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 + "devOptional": true }, "node_modules/jest-each/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -14952,7 +15973,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -14991,7 +16012,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -15008,7 +16029,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, + "devOptional": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -15017,7 +16038,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -15042,7 +16063,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, + "devOptional": true, "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" @@ -15055,7 +16076,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, + "devOptional": true, "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", @@ -15070,7 +16091,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -15085,7 +16106,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -15101,7 +16122,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -15113,13 +16134,13 @@ "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 + "devOptional": true }, "node_modules/jest-matcher-utils/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -15128,7 +16149,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -15140,7 +16161,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", @@ -15160,7 +16181,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -15175,7 +16196,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -15191,7 +16212,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -15203,13 +16224,13 @@ "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 + "devOptional": true }, "node_modules/jest-message-util/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -15218,7 +16239,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -15230,7 +16251,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -15244,7 +16265,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" }, @@ -15261,7 +16282,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, + "devOptional": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -15270,7 +16291,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, + "devOptional": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", @@ -15290,7 +16311,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, + "devOptional": true, "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" @@ -15303,7 +16324,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -15318,7 +16339,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -15334,7 +16355,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -15346,13 +16367,13 @@ "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 + "devOptional": true }, "node_modules/jest-resolve/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -15361,7 +16382,7 @@ "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, + "devOptional": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -15378,7 +16399,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -15390,7 +16411,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", @@ -15422,7 +16443,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -15437,7 +16458,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -15453,7 +16474,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -15465,13 +16486,13 @@ "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 + "devOptional": true }, "node_modules/jest-runner/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -15480,7 +16501,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -15495,7 +16516,7 @@ "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, + "devOptional": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -15505,7 +16526,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -15517,7 +16538,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -15550,7 +16571,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -15565,7 +16586,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -15581,7 +16602,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -15593,13 +16614,13 @@ "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 + "devOptional": true }, "node_modules/jest-runtime/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -15608,7 +16629,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -15620,7 +16641,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", @@ -15651,7 +16672,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -15666,7 +16687,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -15682,7 +16703,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -15694,13 +16715,29 @@ "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 + "devOptional": true + }, + "node_modules/jest-snapshot/node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "devOptional": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, "node_modules/jest-snapshot/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -15709,7 +16746,7 @@ "version": "7.6.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "dev": true, + "devOptional": true, "bin": { "semver": "bin/semver.js" }, @@ -15721,7 +16758,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -15733,7 +16770,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -15750,7 +16787,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -15765,7 +16802,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -15781,7 +16818,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -15793,13 +16830,13 @@ "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 + "devOptional": true }, "node_modules/jest-util/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -15808,7 +16845,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -15820,7 +16857,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", @@ -15837,7 +16874,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -15852,7 +16889,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -15864,7 +16901,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -15880,7 +16917,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -15892,13 +16929,13 @@ "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 + "devOptional": true }, "node_modules/jest-validate/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -15907,7 +16944,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -15919,7 +16956,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", @@ -15938,7 +16975,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -15953,7 +16990,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -15969,7 +17006,7 @@ "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, + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -15981,13 +17018,13 @@ "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 + "devOptional": true }, "node_modules/jest-watcher/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, + "devOptional": true, "engines": { "node": ">=8" } @@ -15996,7 +17033,7 @@ "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, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -16008,7 +17045,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, + "devOptional": true, "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", @@ -16023,7 +17060,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -16032,7 +17069,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -16044,10 +17081,10 @@ } }, "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", - "optional": true, + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "dev": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -16073,8 +17110,7 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true, - "license": "MIT" + "optional": true }, "node_modules/jsdom": { "version": "20.0.3", @@ -16204,7 +17240,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "devOptional": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -16217,20 +17252,19 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "license": "MIT" + "license": "MIT", + "optional": true }, "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==", - "devOptional": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true, - "license": "(AFL-2.1 OR BSD-3-Clause)" + "optional": true }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -16248,14 +17282,14 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, + "devOptional": true, "bin": { "json5": "lib/cli.js" }, @@ -16267,7 +17301,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -16280,11 +17314,10 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", - "dev": true, "engines": [ "node >=0.6.0" ], - "license": "MIT", + "optional": true, "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -16309,13 +17342,12 @@ } }, "node_modules/junit-report-merger": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/junit-report-merger/-/junit-report-merger-7.0.0.tgz", - "integrity": "sha512-i7IYPpwVFpju+UKdxYIG9UTMb6NjfRGzWzE/lRExdEf4K3agqXtVBnJWhL9aMM2lNX7uWW/rhVidiDBsG4n5cw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/junit-report-merger/-/junit-report-merger-7.0.1.tgz", + "integrity": "sha512-jNmdXAu0zkpXB9xIVMRocVoMvMm38esLZogDI42pFwEgANFzOgy7QC6DNewGek8SAqmsGWCCfq/koTZkjZHVZA==", "dev": true, - "license": "MIT", "dependencies": { - "commander": "~12.0.0", + "commander": "~12.1.0", "fast-glob": "~3.3.0", "xmlbuilder2": "3.1.1" }, @@ -16327,23 +17359,16 @@ "node": ">=18" } }, - "node_modules/junit-report-merger/node_modules/commander": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", - "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/junk": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", - "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz", + "integrity": "sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/kind-of": { @@ -16359,7 +17384,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -16399,8 +17424,8 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": "> 0.8" } @@ -16409,7 +17434,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -16428,26 +17453,28 @@ } }, "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true, "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "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==", - "devOptional": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/listr2": { "version": "3.14.0", "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "cli-truncate": "^2.1.0", "colorette": "^2.0.16", @@ -16475,6 +17502,7 @@ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", "license": "MIT", + "optional": true, "dependencies": { "graceful-fs": "^4.1.2", "parse-json": "^4.0.0", @@ -16490,6 +17518,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "license": "MIT", + "optional": true, "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -16503,23 +17532,30 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "license": "MIT", + "optional": true, "engines": { "node": ">=4" } }, - "node_modules/local-access": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/local-access/-/local-access-1.1.0.tgz", - "integrity": "sha512-XfegD5pyTAfb+GY6chk283Ox5z8WexG56OvM06RWLpAc/UHozO8X6xAxEkIitZOtsSMM1Yr3DkHgW5W+onLhCw==", + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, "engines": { - "node": ">=6" + "node": ">=8.9.0" } }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, + "devOptional": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -16532,54 +17568,57 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true, - "license": "MIT", - "peer": true + "optional": true }, "node_modules/lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/lodash.isempty": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/lodash.isfunction": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/lodash.isobject": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true + "devOptional": true }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -16591,8 +17630,8 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/lodash.uniq": { "version": "4.5.0", @@ -16604,8 +17643,8 @@ "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", + "optional": true, "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -16621,8 +17660,8 @@ "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", + "optional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -16637,8 +17676,8 @@ "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", + "optional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -16654,8 +17693,8 @@ "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", + "optional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -16667,15 +17706,15 @@ "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" + "license": "MIT", + "optional": true }, "node_modules/log-symbols/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", + "optional": true, "engines": { "node": ">=8" } @@ -16684,8 +17723,8 @@ "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", + "optional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -16697,8 +17736,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "ansi-escapes": "^4.3.0", "cli-cursor": "^3.1.0", @@ -16716,8 +17755,8 @@ "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", + "optional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -16732,8 +17771,8 @@ "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", + "optional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -16745,15 +17784,15 @@ "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" + "license": "MIT", + "optional": true }, "node_modules/log-update/node_modules/slice-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -16770,8 +17809,8 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -16817,7 +17856,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, + "optional": true, "bin": { "lz-string": "bin/bin.js" } @@ -16826,8 +17865,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "semver": "^6.0.0" }, @@ -16842,13 +17881,13 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "optional": true }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, + "devOptional": true, "dependencies": { "tmpl": "1.0.5" } @@ -16863,9 +17902,9 @@ } }, "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", "dev": true }, "node_modules/media-typer": { @@ -16894,6 +17933,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "optional": true, "engines": { "node": ">= 0.10.0" } @@ -16912,7 +17952,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "devOptional": true }, "node_modules/merge2": { "version": "1.4.1", @@ -17027,7 +18067,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -17036,7 +18076,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, + "optional": true, "engines": { "node": ">=4" } @@ -17071,6 +18111,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "devOptional": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -17102,8 +18143,8 @@ "version": "11.0.1", "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.0.1.tgz", "integrity": "sha512-+3GkODfsDG71KSCQhc4IekSW+ItCK/kiez1Z28ksWvYhKXV/syxMlerR/sC7whDp7IyreZ4YxceMLdTs5hQE8A==", - "dev": true, "license": "MIT", + "optional": true, "peer": true, "dependencies": { "ansi-colors": "^4.1.3", @@ -17139,8 +18180,8 @@ "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", + "optional": true, "peer": true, "dependencies": { "balanced-match": "^1.0.0" @@ -17150,8 +18191,8 @@ "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", + "optional": true, "peer": true, "dependencies": { "string-width": "^4.2.0", @@ -17163,8 +18204,8 @@ "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", + "optional": true, "peer": true, "engines": { "node": ">=10" @@ -17177,8 +18218,8 @@ "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", + "optional": true, "peer": true, "dependencies": { "locate-path": "^6.0.0", @@ -17195,8 +18236,8 @@ "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", + "optional": true, "peer": true, "dependencies": { "foreground-child": "^3.1.0", @@ -17217,8 +18258,8 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "license": "ISC", + "optional": true, "peer": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -17234,8 +18275,8 @@ "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", + "optional": true, "peer": true, "engines": { "node": ">=8" @@ -17245,8 +18286,8 @@ "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", + "optional": true, "peer": true, "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -17262,8 +18303,8 @@ "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", + "optional": true, "peer": true, "dependencies": { "p-locate": "^5.0.0" @@ -17279,16 +18320,16 @@ "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", + "optional": true, "peer": true }, "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", + "optional": true, "peer": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -17301,8 +18342,8 @@ "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", + "optional": true, "peer": true, "dependencies": { "yocto-queue": "^0.1.0" @@ -17318,8 +18359,8 @@ "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", + "optional": true, "peer": true, "dependencies": { "p-limit": "^3.0.2" @@ -17335,8 +18376,8 @@ "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", + "optional": true, "peer": true, "dependencies": { "lru-cache": "^10.2.0", @@ -17353,8 +18394,8 @@ "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", + "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -17370,8 +18411,8 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, "license": "MIT", + "optional": true, "peer": true, "dependencies": { "cliui": "^7.0.2", @@ -17390,8 +18431,8 @@ "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", + "optional": true, "peer": true, "engines": { "node": ">=10" @@ -17401,8 +18442,8 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/mochawesome/-/mochawesome-7.1.3.tgz", "integrity": "sha512-Vkb3jR5GZ1cXohMQQ73H3cZz7RoxGjjUo0G5hu0jLaW+0FdUxUwg3Cj29bqQdh0rFcnyV06pWmqmi5eBPnEuNQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "chalk": "^4.1.2", "diff": "^5.0.0", @@ -17423,8 +18464,8 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/mochawesome-merge/-/mochawesome-merge-4.3.0.tgz", "integrity": "sha512-1roR6g+VUlfdaRmL8dCiVpKiaUhbPVm1ZQYUM6zHX46mWk+tpsKVZR6ba98k2zc8nlPvYd71yn5gyH970pKBSw==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "fs-extra": "^7.0.1", "glob": "^7.1.6", @@ -17441,8 +18482,8 @@ "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", + "optional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -17457,8 +18498,8 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, "license": "ISC", + "optional": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -17469,8 +18510,8 @@ "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", + "optional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -17482,15 +18523,15 @@ "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" + "license": "MIT", + "optional": true }, "node_modules/mochawesome-merge/node_modules/fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -17504,8 +18545,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, "license": "MIT", + "optional": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -17514,8 +18555,8 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">= 4.0.0" } @@ -17524,8 +18565,8 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -17539,15 +18580,15 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/mochawesome-merge/node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -17569,8 +18610,8 @@ "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, "license": "ISC", + "optional": true, "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -17583,8 +18624,8 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/mochawesome-report-generator/-/mochawesome-report-generator-6.2.0.tgz", "integrity": "sha512-Ghw8JhQFizF0Vjbtp9B0i//+BOkV5OWcQCPpbO0NGOoxV33o+gKDYU0Pr2pGxkIHnqZ+g5mYiXF7GMNgAcDpSg==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "chalk": "^4.1.2", "dateformat": "^4.5.1", @@ -17607,8 +18648,8 @@ "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", + "optional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -17623,8 +18664,8 @@ "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", + "optional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -17640,8 +18681,8 @@ "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", + "optional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -17653,15 +18694,15 @@ "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" + "license": "MIT", + "optional": true }, "node_modules/mochawesome-report-generator/node_modules/fs-extra": { "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", + "optional": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -17675,8 +18716,8 @@ "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", + "optional": true, "engines": { "node": ">=8" } @@ -17685,8 +18726,8 @@ "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", + "optional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -17698,8 +18739,8 @@ "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", + "optional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -17714,8 +18755,8 @@ "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", + "optional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -17731,8 +18772,8 @@ "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", + "optional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -17744,15 +18785,15 @@ "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" + "license": "MIT", + "optional": true }, "node_modules/mochawesome/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", + "optional": true, "engines": { "node": ">=8" } @@ -17761,8 +18802,8 @@ "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", + "optional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -17777,18 +18818,11 @@ "license": "MIT", "peer": true }, - "node_modules/mri": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz", - "integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==", - "engines": { - "node": ">=4" - } - }, "node_modules/mrmime": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "dev": true, "engines": { "node": ">=10" } @@ -17797,7 +18831,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "devOptional": true, "license": "MIT" }, "node_modules/multicast-dns": { @@ -17815,9 +18848,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -17825,7 +18858,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -17833,6 +18865,21 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-postinstall": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", + "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", + "optional": true, + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -17843,7 +18890,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.6" } @@ -17852,13 +18899,14 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "devOptional": true }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/no-case": { "version": "3.0.4", @@ -17888,7 +18936,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "optional": true, + "dev": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -17908,7 +18956,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", - "optional": true, + "dev": true, "dependencies": { "http2-client": "^1.2.5" }, @@ -17917,10 +18965,10 @@ } }, "node_modules/node-fetch-native": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.6.tgz", - "integrity": "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==", - "optional": true + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "dev": true }, "node_modules/node-forge": { "version": "1.3.1", @@ -17936,14 +18984,14 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true + "devOptional": true }, "node_modules/node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "process-on-spawn": "^1.0.0" }, @@ -17955,23 +19003,23 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", "integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==", - "optional": true, + "dev": true, "dependencies": { "es6-promise": "^3.2.1" } }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "dev": true, - "license": "MIT" + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "devOptional": true }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "license": "BSD-2-Clause", + "optional": true, "dependencies": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -17984,6 +19032,7 @@ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "license": "MIT", + "optional": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -18001,6 +19050,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "license": "ISC", + "optional": true, "bin": { "semver": "bin/semver" } @@ -18009,7 +19059,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -18019,6 +19069,7 @@ "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", "license": "MIT", + "optional": true, "dependencies": { "ansi-styles": "^3.2.1", "chalk": "^2.4.1", @@ -18044,6 +19095,7 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "license": "MIT", + "optional": true, "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -18060,6 +19112,7 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", "license": "MIT", + "optional": true, "engines": { "node": ">=4" } @@ -18069,6 +19122,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "license": "ISC", + "optional": true, "bin": { "semver": "bin/semver" } @@ -18078,6 +19132,7 @@ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "license": "MIT", + "optional": true, "dependencies": { "shebang-regex": "^1.0.0" }, @@ -18090,6 +19145,7 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } @@ -18099,6 +19155,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "license": "ISC", + "optional": true, "dependencies": { "isexe": "^2.0.0" }, @@ -18110,7 +19167,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, + "devOptional": true, "dependencies": { "path-key": "^3.0.0" }, @@ -18140,8 +19197,8 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", - "dev": true, "license": "ISC", + "optional": true, "dependencies": { "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", @@ -18182,8 +19239,8 @@ "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", + "optional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -18198,8 +19255,8 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, "license": "ISC", + "optional": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -18210,8 +19267,8 @@ "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", + "optional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -18223,15 +19280,15 @@ "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" + "license": "MIT", + "optional": true }, "node_modules/nyc/node_modules/find-cache-dir": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "commondir": "^1.0.1", "make-dir": "^3.0.2", @@ -18248,8 +19305,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, "license": "ISC", + "optional": true, "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^3.0.2" @@ -18262,8 +19319,8 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, "license": "BSD-3-Clause", + "optional": true, "dependencies": { "@babel/core": "^7.7.5", "@istanbuljs/schema": "^0.1.2", @@ -18278,8 +19335,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "aggregate-error": "^3.0.0" }, @@ -18291,8 +19348,8 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -18306,15 +19363,15 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/nyc/node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -18336,8 +19393,8 @@ "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, "license": "ISC", + "optional": true, "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -18347,16 +19404,16 @@ } }, "node_modules/nypm": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.0.tgz", - "integrity": "sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==", - "optional": true, + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.1.tgz", + "integrity": "sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w==", + "dev": true, "dependencies": { "citty": "^0.1.6", - "consola": "^3.4.0", + "consola": "^3.4.2", "pathe": "^2.0.3", - "pkg-types": "^2.0.0", - "tinyexec": "^0.3.2" + "pkg-types": "^2.2.0", + "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" @@ -18369,7 +19426,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", - "optional": true, + "dev": true, "dependencies": { "fast-safe-stringify": "^2.0.7" } @@ -18378,7 +19435,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz", "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", - "optional": true, + "dev": true, "dependencies": { "@exodus/schemasafe": "^1.0.0-rc.2", "should": "^13.2.1", @@ -18392,7 +19449,7 @@ "version": "2.5.6", "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz", "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", - "optional": true, + "dev": true, "dependencies": { "node-fetch-h2": "^2.3.0", "oas-kit-common": "^1.0.8", @@ -18411,7 +19468,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz", "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==", - "optional": true, + "dev": true, "funding": { "url": "https://github.com/Mermade/oas-kit?sponsor=1" } @@ -18420,7 +19477,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz", "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", - "optional": true, + "dev": true, "dependencies": { "call-me-maybe": "^1.0.1", "oas-kit-common": "^1.0.8", @@ -18444,27 +19501,10 @@ } }, "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-is": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", - "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1" - }, + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "devOptional": true, "engines": { "node": ">= 0.4" }, @@ -18476,18 +19516,22 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "optional": true, "engines": { "node": ">= 0.4" } }, "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "optional": true, "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", "object-keys": "^1.1.1" }, "engines": { @@ -18498,14 +19542,15 @@ } }, "node_modules/object.entries": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", - "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", "optional": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "es-object-atoms": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -18545,12 +19590,13 @@ } }, "node_modules/object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", "optional": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, @@ -18571,7 +19617,7 @@ "version": "2.0.11", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", - "optional": true + "dev": true }, "node_modules/on-finished": { "version": "2.4.1", @@ -18590,7 +19636,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.8" } @@ -18599,7 +19645,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, + "devOptional": true, "dependencies": { "wrappy": "1" } @@ -18608,7 +19654,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, + "devOptional": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -18658,7 +19704,7 @@ "version": "1.5.2", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "dev": true, + "devOptional": true, "bin": { "opener": "bin/opener-bin.js" } @@ -18684,14 +19730,63 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", + "license": "MIT", + "optional": true + }, + "node_modules/ow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ow/-/ow-2.0.0.tgz", + "integrity": "sha512-ESUigmGrdhUZ2nQSFNkeKSl6ZRPupXzprMs3yF9DYlNVpJ8XAjM/fI9RUZxA7PI1K9HQDCCvBo1jr/GEIo9joQ==", "dev": true, - "license": "MIT" + "dependencies": { + "@sindresorhus/is": "^6.3.0", + "callsites": "^4.1.0", + "dot-prop": "^8.0.2", + "environment": "^1.0.0", + "fast-equals": "^5.0.1", + "is-identifier": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ow/node_modules/callsites": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-4.2.0.tgz", + "integrity": "sha512-kfzR4zzQtAE9PC7CzZsjl3aBNbXWuXiSeOCdLcPpBfGW8YuCqQHcRPFDbr/BPVmd3EEPVpuFzLyuT/cUhPr4OQ==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "optional": true, + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, + "devOptional": true, "dependencies": { "p-try": "^2.0.0" }, @@ -18706,7 +19801,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, + "devOptional": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -18718,8 +19813,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "aggregate-error": "^3.0.0" }, @@ -18764,7 +19859,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -18773,8 +19868,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", - "dev": true, "license": "ISC", + "optional": true, "dependencies": { "graceful-fs": "^4.1.15", "hasha": "^5.0.0", @@ -18806,7 +19901,6 @@ "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==", - "devOptional": true, "dependencies": { "callsites": "^3.0.0" }, @@ -18818,7 +19912,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "devOptional": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -18864,7 +19957,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -18873,8 +19966,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", - "dev": true, - "license": "(WTFPL OR MIT)" + "license": "(WTFPL OR MIT)", + "optional": true }, "node_modules/path-key": { "version": "3.1.1", @@ -18928,7 +20021,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "devOptional": true, "engines": { "node": ">=8" } @@ -18937,15 +20029,15 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "optional": true + "dev": true }, "node_modules/peek-readable": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", - "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.4.2.tgz", + "integrity": "sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg==", "dev": true, "engines": { - "node": ">=8" + "node": ">=14.16" }, "funding": { "type": "github", @@ -18956,27 +20048,25 @@ "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" + "license": "MIT", + "optional": true }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "optional": true + "dev": true }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true, - "license": "MIT" + "optional": true }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "devOptional": true, "license": "ISC" }, "node_modules/picomatch": { @@ -18996,6 +20086,7 @@ "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", "license": "MIT", + "optional": true, "bin": { "pidtree": "bin/pidtree.js" }, @@ -19008,6 +20099,7 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "license": "MIT", + "optional": true, "engines": { "node": ">=4" } @@ -19016,7 +20108,7 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 6" } @@ -19025,7 +20117,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, + "devOptional": true, "dependencies": { "find-up": "^4.0.0" }, @@ -19034,28 +20126,29 @@ } }, "node_modules/pkg-types": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.1.0.tgz", - "integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==", - "optional": true, + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, "dependencies": { - "confbox": "^0.2.1", - "exsolve": "^1.0.1", + "confbox": "^0.2.2", + "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "optional": true, "engines": { "node": ">= 0.4" } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -19072,208 +20165,264 @@ } ], "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/postcss-calc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz", + "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==", "dev": true, "dependencies": { - "postcss-selector-parser": "^6.0.11", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12 || ^20.9 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.2" + "postcss": "^8.4.38" + } + }, + "node_modules/postcss-calc/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" } }, "node_modules/postcss-colormin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.0.0.tgz", - "integrity": "sha512-EuO+bAUmutWoZYgHn2T1dG1pPqHU6L4TjzPlu4t1wZGXQ/fxV16xg2EJmYi0z+6r+MGV1yvpx1BHkUaRrPa2bw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.4.tgz", + "integrity": "sha512-ziQuVzQZBROpKpfeDwmrG+Vvlr0YWmY/ZAk99XD+mGEBuEojoFekL41NCsdhyNUtZI7DPOoIWIR7vQQK9xwluw==", "dev": true, "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.25.1", "caniuse-api": "^3.0.0", - "colord": "^2.9.1", + "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-convert-values": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.0.0.tgz", - "integrity": "sha512-U5D8QhVwqT++ecmy8rnTb+RL9n/B806UVaS3m60lqle4YDFcpbS3ae5bTQIh3wOGUSDHSEtMYLs/38dNG7EYFw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.6.tgz", + "integrity": "sha512-MD/eb39Mr60hvgrqpXsgbiqluawYg/8K4nKsqRsuDX9f+xN1j6awZCUv/5tLH8ak3vYp/EMXwdcnXvfZYiejCQ==", "dev": true, "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.25.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-comments": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.0.tgz", - "integrity": "sha512-p2skSGqzPMZkEQvJsgnkBhCn8gI7NzRH2683EEjrIkoMiwRELx68yoUJ3q3DGSGuQ8Ug9Gsn+OuDr46yfO+eFw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.4.tgz", + "integrity": "sha512-6tCUoql/ipWwKtVP/xYiFf1U9QgJ0PUvxN7pTcsQ8Ns3Fnwq1pU5D5s1MhT/XySeLq6GXNvn37U46Ded0TckWg==", "dev": true, + "dependencies": { + "postcss-selector-parser": "^7.1.0" + }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-discard-comments/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" } }, "node_modules/postcss-discard-duplicates": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.0.tgz", - "integrity": "sha512-bU1SXIizMLtDW4oSsi5C/xHKbhLlhek/0/yCnoMQany9k3nPBq+Ctsv/9oMmyqbR96HYHxZcHyK2HR5P/mqoGA==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.2.tgz", + "integrity": "sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==", "dev": true, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-empty": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.0.tgz", - "integrity": "sha512-b+h1S1VT6dNhpcg+LpyiUrdnEZfICF0my7HAKgJixJLW7BnNmpRH34+uw/etf5AhOlIhIAuXApSzzDzMI9K/gQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.1.tgz", + "integrity": "sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==", "dev": true, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-overridden": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.0.tgz", - "integrity": "sha512-4VELwssYXDFigPYAZ8vL4yX4mUepF/oCBeeIT4OXsJPYOtvJumyz9WflmJWTfDwCUcpDR+z0zvCWBXgTx35SVw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.1.tgz", + "integrity": "sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==", "dev": true, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-merge-longhand": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.0.tgz", - "integrity": "sha512-4VSfd1lvGkLTLYcxFuISDtWUfFS4zXe0FpF149AyziftPFQIWxjvFSKhA4MIxMe4XM3yTDgQMbSNgzIVxChbIg==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.5.tgz", + "integrity": "sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.0.0" + "stylehacks": "^7.0.5" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-merge-rules": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.1.tgz", - "integrity": "sha512-a4tlmJIQo9SCjcfiCcCMg/ZCEe0XTkl/xK0XHBs955GWg9xDX3NwP9pwZ78QUOWB8/0XCjZeJn98Dae0zg6AAw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.6.tgz", + "integrity": "sha512-2jIPT4Tzs8K87tvgCpSukRQ2jjd+hH6Bb8rEEOUDmmhOeTcqDg5fEFK8uKIu+Pvc3//sm3Uu6FRqfyv7YF7+BQ==", "dev": true, "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.25.1", "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.0", - "postcss-selector-parser": "^6.0.5" + "cssnano-utils": "^5.0.1", + "postcss-selector-parser": "^7.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-merge-rules/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" } }, "node_modules/postcss-minify-font-values": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.0.0.tgz", - "integrity": "sha512-zNRAVtyh5E8ndZEYXA4WS8ZYsAp798HiIQ1V2UF/C/munLp2r1UGHwf1+6JFu7hdEhJFN+W1WJQKBrtjhFgEnA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.1.tgz", + "integrity": "sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-gradients": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.0.tgz", - "integrity": "sha512-wO0F6YfVAR+K1xVxF53ueZJza3L+R3E6cp0VwuXJQejnNUH0DjcAFe3JEBeTY1dLwGa0NlDWueCA1VlEfiKgAA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.1.tgz", + "integrity": "sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==", "dev": true, "dependencies": { - "colord": "^2.9.1", - "cssnano-utils": "^4.0.0", + "colord": "^2.9.3", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-params": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.0.0.tgz", - "integrity": "sha512-Fz/wMQDveiS0n5JPcvsMeyNXOIMrwF88n7196puSuQSWSa+/Ofc1gDOSY2xi8+A4PqB5dlYCKk/WfqKqsI+ReQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.4.tgz", + "integrity": "sha512-3OqqUddfH8c2e7M35W6zIwv7jssM/3miF9cbCSb1iJiWvtguQjlxZGIHK9JRmc8XAKmE2PFGtHSM7g/VcW97sw==", "dev": true, "dependencies": { - "browserslist": "^4.21.4", - "cssnano-utils": "^4.0.0", + "browserslist": "^4.25.1", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-selectors": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.0.tgz", - "integrity": "sha512-ec/q9JNCOC2CRDNnypipGfOhbYPuUkewGwLnbv6omue/PSASbHSU7s6uSQ0tcFRVv731oMIx8k0SP4ZX6be/0g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.5.tgz", + "integrity": "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==", "dev": true, "dependencies": { - "postcss-selector-parser": "^6.0.5" + "cssesc": "^3.0.0", + "postcss-selector-parser": "^7.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-minify-selectors/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" } }, "node_modules/postcss-modules-extract-imports": { @@ -19336,183 +20485,183 @@ } }, "node_modules/postcss-normalize-charset": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.0.tgz", - "integrity": "sha512-cqundwChbu8yO/gSWkuFDmKrCZ2vJzDAocheT2JTd0sFNA4HMGoKMfbk2B+J0OmO0t5GUkiAkSM5yF2rSLUjgQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.1.tgz", + "integrity": "sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==", "dev": true, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-display-values": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.0.tgz", - "integrity": "sha512-Qyt5kMrvy7dJRO3OjF7zkotGfuYALETZE+4lk66sziWSPzlBEt7FrUshV6VLECkI4EN8Z863O6Nci4NXQGNzYw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.1.tgz", + "integrity": "sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-positions": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.0.tgz", - "integrity": "sha512-mPCzhSV8+30FZyWhxi6UoVRYd3ZBJgTRly4hOkaSifo0H+pjDYcii/aVT4YE6QpOil15a5uiv6ftnY3rm0igPg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.1.tgz", + "integrity": "sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-repeat-style": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.0.tgz", - "integrity": "sha512-50W5JWEBiOOAez2AKBh4kRFm2uhrT3O1Uwdxz7k24aKtbD83vqmcVG7zoIwo6xI2FZ/HDlbrCopXhLeTpQib1A==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.1.tgz", + "integrity": "sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-string": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.0.tgz", - "integrity": "sha512-KWkIB7TrPOiqb8ZZz6homet2KWKJwIlysF5ICPZrXAylGe2hzX/HSf4NTX2rRPJMAtlRsj/yfkrWGavFuB+c0w==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.1.tgz", + "integrity": "sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-timing-functions": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.0.tgz", - "integrity": "sha512-tpIXWciXBp5CiFs8sem90IWlw76FV4oi6QEWfQwyeREVwUy39VSeSqjAT7X0Qw650yAimYW5gkl2Gd871N5SQg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.1.tgz", + "integrity": "sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-unicode": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.0.tgz", - "integrity": "sha512-ui5crYkb5ubEUDugDc786L/Me+DXp2dLg3fVJbqyAl0VPkAeALyAijF2zOsnZyaS1HyfPuMH0DwyY18VMFVNkg==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.4.tgz", + "integrity": "sha512-LvIURTi1sQoZqj8mEIE8R15yvM+OhbR1avynMtI9bUzj5gGKR/gfZFd8O7VMj0QgJaIFzxDwxGl/ASMYAkqO8g==", "dev": true, "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.25.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-url": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.0.tgz", - "integrity": "sha512-98mvh2QzIPbb02YDIrYvAg4OUzGH7s1ZgHlD3fIdTHLgPLRpv1ZTKJDnSAKr4Rt21ZQFzwhGMXxpXlfrUBKFHw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.1.tgz", + "integrity": "sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-whitespace": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.0.tgz", - "integrity": "sha512-7cfE1AyLiK0+ZBG6FmLziJzqQCpTQY+8XjMhMAz8WSBSCsCNNUKujgIgjCAmDT3cJ+3zjTXFkoD15ZPsckArVw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.1.tgz", + "integrity": "sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-ordered-values": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.0.tgz", - "integrity": "sha512-K36XzUDpvfG/nWkjs6d1hRBydeIxGpKS2+n+ywlKPzx1nMYDYpoGbcjhj5AwVYJK1qV2/SDoDEnHzlPD6s3nMg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.2.tgz", + "integrity": "sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==", "dev": true, "dependencies": { - "cssnano-utils": "^4.0.0", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-reduce-initial": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.0.tgz", - "integrity": "sha512-s2UOnidpVuXu6JiiI5U+fV2jamAw5YNA9Fdi/GRK0zLDLCfXmSGqQtzpUPtfN66RtCbb9fFHoyZdQaxOB3WxVA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.4.tgz", + "integrity": "sha512-rdIC9IlMBn7zJo6puim58Xd++0HdbvHeHaPgXsimMfG1ijC5A9ULvNLSE0rUKVJOvNMcwewW4Ga21ngyJjY/+Q==", "dev": true, "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.25.1", "caniuse-api": "^3.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-reduce-transforms": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.0.tgz", - "integrity": "sha512-FQ9f6xM1homnuy1wLe9lP1wujzxnwt1EwiigtWwuyf8FsqqXUDUp2Ulxf9A5yjlUOTdCJO6lonYjg1mgqIIi2w==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.1.tgz", + "integrity": "sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-selector-parser": { @@ -19529,34 +20678,47 @@ } }, "node_modules/postcss-svgo": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.0.tgz", - "integrity": "sha512-r9zvj/wGAoAIodn84dR/kFqwhINp5YsJkLoujybWG59grR/IHx+uQ2Zo+IcOwM0jskfYX3R0mo+1Kip1VSNcvw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.1.0.tgz", + "integrity": "sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0", - "svgo": "^3.0.2" + "svgo": "^4.0.0" }, "engines": { - "node": "^14 || ^16 || >= 18" + "node": "^18.12.0 || ^20.9.0 || >= 18" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" } }, "node_modules/postcss-unique-selectors": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.0.tgz", - "integrity": "sha512-EPQzpZNxOxP7777t73RQpZE5e9TrnCrkvp7AH7a0l89JmZiPnS82y216JowHXwpBCQitfyxrof9TK3rYbi7/Yw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz", + "integrity": "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==", "dev": true, "dependencies": { - "postcss-selector-parser": "^6.0.5" + "postcss-selector-parser": "^7.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-unique-selectors/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" } }, "node_modules/postcss-value-parser": { @@ -19575,9 +20737,9 @@ } }, "node_modules/prettier": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.0.tgz", - "integrity": "sha512-J9odKxERhCQ10OC2yb93583f6UnYutOeiV5i0zEDS7UGTdUt0u+y8erxl3lBKvwo/JHyyoEdXjwp4dke9oyZ/g==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "devOptional": true, "bin": { "prettier": "bin/prettier.cjs" @@ -19605,8 +20767,8 @@ "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=6" }, @@ -19618,7 +20780,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -19632,7 +20794,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -19644,14 +20806,14 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "devOptional": true }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">= 0.6.0" } @@ -19666,8 +20828,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "fromentries": "^1.2.0" }, @@ -19679,7 +20841,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, + "devOptional": true, "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -19729,8 +20891,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/psl": { "version": "1.9.0", @@ -19742,8 +20904,8 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -19762,7 +20924,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "individual", @@ -19820,7 +20982,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, + "devOptional": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -19870,20 +21032,6 @@ "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/raw-loader/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, "node_modules/raw-loader/node_modules/schema-utils": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", @@ -19906,8 +21054,8 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -19922,15 +21070,15 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } @@ -19939,7 +21087,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", - "optional": true, + "dev": true, "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" @@ -19989,38 +21137,60 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT", + "optional": true + }, + "node_modules/react-refresh": { + "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" + } }, "node_modules/react-router": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.2.tgz", - "integrity": "sha512-tvN1iuT03kHgOFnLPfLJ8V95eijteveqdOSk+srqfePtQvqCExB8eHOYnlilbOcyJyKnYkr1vJvf7YqotAJu1A==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.1.tgz", + "integrity": "sha512-5cy/M8DHcG51/KUIka1nfZ2QeylS4PJRs6TT8I4PF5axVsI5JUxp0hC0NZ/AEEj8Vw7xsEoD7L/6FY+zoYaOGA==", "dependencies": { - "@remix-run/router": "1.19.2" + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" }, "peerDependencies": { - "react": ">=16.8" + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } } }, "node_modules/react-router-dom": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.2.tgz", - "integrity": "sha512-z7YkaEW0Dy35T3/QKPYB1LjMK2R1fxnHO8kWpUMTBdfVzZrWOiY9a7CtN8HqdWtDUWd5FY6Dl8HFsqVwH4uOtQ==", - "dev": true, + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.8.1.tgz", + "integrity": "sha512-NkgBCF3sVgCiAWIlSt89GR2PLaksMpoo3HDCorpRfnCEfdtRPLiuTf+CNXvqZMI5SJLZCLpVCvcZrTdtGW64xQ==", "dependencies": { - "@remix-run/router": "1.19.2", - "react-router": "6.26.2" + "react-router": "7.8.1" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" }, "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "engines": { + "node": ">=18" } }, "node_modules/react-transition-group": { @@ -20044,6 +21214,7 @@ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", "license": "MIT", + "optional": true, "dependencies": { "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", @@ -20058,6 +21229,7 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "license": "MIT", + "optional": true, "dependencies": { "pify": "^3.0.0" }, @@ -20086,41 +21258,11 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", - "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", - "dev": true, - "dependencies": { - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, + "devOptional": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -20161,7 +21303,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, + "optional": true, "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" @@ -20171,19 +21313,19 @@ } }, "node_modules/reflect.getprototypeof": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.7.tgz", - "integrity": "sha512-bMvFGIUKlc/eSfXNX+aZ+EL95/EgZzuwA0OBPTbZZDEJw/0AkentjMuM1oiRfwHrshqk4RzdgiTg5CcDalXN5g==", - "license": "MIT", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", "optional": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "which-builtin-type": "^1.1.4" + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -20196,7 +21338,7 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", - "optional": true, + "dev": true, "funding": { "url": "https://github.com/Mermade/oas-kit?sponsor=1" } @@ -20205,17 +21347,15 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true, "license": "MIT", - "peer": true + "optional": true }, "node_modules/regenerate-unicode-properties": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", - "dev": true, "license": "MIT", - "peer": true, + "optional": true, "dependencies": { "regenerate": "^1.4.2" }, @@ -20224,31 +21364,21 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true - }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regexp.prototype.flags": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", - "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", - "license": "MIT", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "optional": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", "set-function-name": "^2.0.2" }, "engines": { @@ -20262,9 +21392,8 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", - "dev": true, "license": "MIT", - "peer": true, + "optional": true, "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.0", @@ -20281,8 +21410,8 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "rc": "^1.1.6", "safe-buffer": "^5.0.1" @@ -20292,8 +21421,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "rc": "^1.0.1" }, @@ -20305,17 +21434,15 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "dev": true, "license": "MIT", - "peer": true + "optional": true }, "node_modules/regjsparser": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", - "dev": true, "license": "BSD-2-Clause", - "peer": true, + "optional": true, "dependencies": { "jsesc": "~3.0.2" }, @@ -20336,8 +21463,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", - "dev": true, "license": "ISC", + "optional": true, "dependencies": { "es6-error": "^4.0.1" }, @@ -20349,27 +21476,18 @@ "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.10" } }, - "node_modules/replace-ext": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", - "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, "node_modules/request-progress": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "throttleit": "^1.0.0" } @@ -20387,7 +21505,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -20396,8 +21514,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/requires-port": { "version": "1.0.0", @@ -20405,6 +21523,18 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, + "node_modules/reserved-identifiers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.0.0.tgz", + "integrity": "sha512-h0bP2Katmvf3hv4Z3WtDl4+6xt/OglQ2Xa6TnhZ/Rm9/7IH1crXQqMwD4J2ngKBonVv+fB55zfGgNDAmsevLVQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/resolve": { "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", @@ -20426,7 +21556,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, + "devOptional": true, "dependencies": { "resolve-from": "^5.0.0" }, @@ -20438,7 +21568,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -20447,7 +21577,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "license": "MIT", "optional": true, "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" @@ -20457,7 +21586,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" } @@ -20466,8 +21595,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -20500,8 +21629,8 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/rimraf": { "version": "6.0.1", @@ -20613,31 +21742,22 @@ "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } }, - "node_modules/sade": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz", - "integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "optional": true, "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, "engines": { @@ -20651,16 +21771,33 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "devOptional": true + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "optional": true, + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "optional": true, "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "is-regex": "^1.1.4" + "is-regex": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -20673,14 +21810,13 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "devOptional": true }, "node_modules/sass": { - "version": "1.83.4", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.83.4.tgz", - "integrity": "sha512-B1bozCeNQiOgDcLd33e2Cs2U60wZwjUUXzh900ZyQF5qUasvMdDZYbQ566LJu7cqR+sAHlAfO6RMkaID5s6qpA==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", + "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", "dev": true, - "license": "MIT", "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -20765,6 +21901,12 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "dev": true + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -20786,10 +21928,10 @@ } }, "node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", - "dev": true, + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "devOptional": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -20797,7 +21939,7 @@ "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 10.13.0" }, "funding": { "type": "opencollective", @@ -20808,7 +21950,7 @@ "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, + "devOptional": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -20824,7 +21966,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, + "devOptional": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -20836,7 +21978,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "devOptional": true }, "node_modules/select-hose": { "version": "2.0.0", @@ -20858,14 +22000,6 @@ "node": ">=10" } }, - "node_modules/semiver": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semiver/-/semiver-1.1.0.tgz", - "integrity": "sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg==", - "engines": { - "node": ">=6" - } - }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -20951,7 +22085,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" @@ -20961,8 +22095,8 @@ "version": "14.2.4", "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.4.tgz", "integrity": "sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "@zeit/schemas": "2.36.0", "ajv": "8.12.0", @@ -20987,8 +22121,8 @@ "version": "6.1.6", "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", @@ -21003,8 +22137,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">= 0.8" } @@ -21013,8 +22147,8 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">= 0.6" } @@ -21023,8 +22157,8 @@ "version": "1.33.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">= 0.6" } @@ -21033,8 +22167,8 @@ "version": "2.1.18", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "mime-db": "~1.33.0" }, @@ -21046,15 +22180,15 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/serve-handler/node_modules/range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">= 0.6" } @@ -21139,8 +22273,8 @@ "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -21156,8 +22290,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -21169,20 +22303,26 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, - "license": "ISC" + "license": "ISC", + "optional": true + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "optional": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -21199,6 +22339,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "optional": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -21209,6 +22350,20 @@ "node": ">= 0.4" } }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "optional": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -21253,6 +22408,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "devOptional": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -21261,7 +22417,7 @@ "version": "13.2.3", "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", - "optional": true, + "dev": true, "dependencies": { "should-equal": "^2.0.0", "should-format": "^3.0.3", @@ -21274,7 +22430,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", - "optional": true, + "dev": true, "dependencies": { "should-type": "^1.4.0" } @@ -21283,7 +22439,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", - "optional": true, + "dev": true, "dependencies": { "should-type": "^1.3.0", "should-type-adaptors": "^1.0.1" @@ -21293,13 +22449,13 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", - "optional": true + "dev": true }, "node_modules/should-type-adaptors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", - "optional": true, + "dev": true, "dependencies": { "should-type": "^1.3.0", "should-util": "^1.0.0" @@ -21309,12 +22465,36 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", - "optional": true + "dev": true + }, + "node_modules/showdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz", + "integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==", + "dependencies": { + "commander": "^9.0.0" + }, + "bin": { + "showdown": "bin/showdown.js" + }, + "funding": { + "type": "individual", + "url": "https://www.paypal.me/tiviesantos" + } + }, + "node_modules/showdown/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "engines": { + "node": "^12.20.0 || >=14" + } }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "devOptional": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -21334,6 +22514,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "devOptional": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -21350,6 +22531,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "devOptional": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -21368,6 +22550,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "devOptional": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -21387,12 +22570,13 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "devOptional": true }, "node_modules/sirv": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dev": true, "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", @@ -21402,46 +22586,17 @@ "node": ">= 10" } }, - "node_modules/sirv-cli": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/sirv-cli/-/sirv-cli-2.0.2.tgz", - "integrity": "sha512-OtSJDwxsF1NWHc7ps3Sa0s+dPtP15iQNJzfKVz+MxkEo3z72mCD+yu30ct79rPr0CaV1HXSOBp+MIY5uIhHZ1A==", - "dependencies": { - "console-clear": "^1.1.0", - "get-port": "^3.2.0", - "kleur": "^4.1.4", - "local-access": "^1.0.1", - "sade": "^1.6.0", - "semiver": "^1.0.0", - "sirv": "^2.0.0", - "tinydate": "^1.0.0" - }, - "bin": { - "sirv": "bin.js" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/sirv-cli/node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "engines": { - "node": ">=6" - } - }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true + "devOptional": true }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -21450,8 +22605,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -21465,8 +22620,8 @@ "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", + "optional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -21481,8 +22636,8 @@ "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", + "optional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -21494,8 +22649,8 @@ "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" + "license": "MIT", + "optional": true }, "node_modules/sockjs": { "version": "0.3.24", @@ -21512,15 +22667,15 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "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, "engines": { "node": ">=0.10.0" @@ -21530,7 +22685,7 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, + "devOptional": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -21540,8 +22695,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", - "dev": true, "license": "ISC", + "optional": true, "dependencies": { "foreground-child": "^2.0.0", "is-windows": "^1.0.2", @@ -21558,8 +22713,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, "license": "ISC", + "optional": true, "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^3.0.2" @@ -21573,6 +22728,7 @@ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "license": "Apache-2.0", + "optional": true, "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -21582,13 +22738,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "license": "CC-BY-3.0" + "license": "CC-BY-3.0", + "optional": true }, "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==", "license": "MIT", + "optional": true, "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -21598,7 +22756,8 @@ "version": "3.0.20", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", - "license": "CC0-1.0" + "license": "CC0-1.0", + "optional": true }, "node_modules/spdy": { "version": "4.0.2", @@ -21644,18 +22803,102 @@ "node": ">= 6" } }, + "node_modules/speed-measure-webpack-plugin": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.5.0.tgz", + "integrity": "sha512-Re0wX5CtM6gW7bZA64ONOfEPEhwbiSF/vz6e2GvadjuaPrQcHTQdRGsD8+BE7iUOysXH8tIenkPCQBEcspXsNg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "webpack": "^1 || ^2 || ^3 || ^4 || ^5" + } + }, + "node_modules/speed-measure-webpack-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/speed-measure-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/speed-measure-webpack-plugin/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/speed-measure-webpack-plugin/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/speed-measure-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/speed-measure-webpack-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "devOptional": true }, "node_modules/sshpk": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", - "dev": true, - "license": "MIT", + "optional": true, "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -21677,17 +22920,16 @@ } }, "node_modules/stable-hash": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz", - "integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==", - "license": "MIT", + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", "optional": true }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, + "devOptional": true, "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -21699,11 +22941,17 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "dev": true + }, "node_modules/state-local": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", @@ -21723,8 +22971,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "dev": true, - "license": "MIT", + "optional": true, "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" @@ -21746,7 +22993,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, + "devOptional": true, "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" @@ -21801,23 +23048,24 @@ } }, "node_modules/string.prototype.matchall": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", - "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", "optional": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", + "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.7", - "regexp.prototype.flags": "^1.5.2", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -21831,6 +23079,7 @@ "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", "license": "MIT", + "optional": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -21856,14 +23105,18 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "optional": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -21873,14 +23126,19 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "optional": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -21889,6 +23147,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "optional": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -21931,7 +23190,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -21940,7 +23199,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -21949,7 +23208,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, + "optional": true, "dependencies": { "min-indent": "^1.0.0" }, @@ -21970,16 +23229,16 @@ } }, "node_modules/strtok3": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", - "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-9.1.1.tgz", + "integrity": "sha512-FhwotcEqjr241ZbjFzjlIYg6c5/L/s4yBGWSMvJ9UoExiSqL+FnFA/CaeZx17WGaZMS/4SOZp8wH18jSS4R4lw==", "dev": true, "dependencies": { "@tokenizer/token": "^0.3.0", - "peek-readable": "^4.1.0" + "peek-readable": "^5.3.1" }, "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { "type": "github", @@ -21987,47 +23246,76 @@ } }, "node_modules/style-loader": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", - "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", "dev": true, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^5.0.0" + "webpack": "^5.27.0" } }, "node_modules/stylehacks": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.0.0.tgz", - "integrity": "sha512-+UT589qhHPwz6mTlCLSt/vMNTJx8dopeJlZAlBMJPWA3ORqu6wmQY7FBXf+qD+FsqoBJODyqNxOUP3jdntFRdw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.6.tgz", + "integrity": "sha512-iitguKivmsueOmTO0wmxURXBP8uqOO+zikLGZ7Mm9e/94R4w5T999Js2taS/KBOnQ/wdC3jN3vNSrkGDrlnqQg==", "dev": true, "dependencies": { - "browserslist": "^4.21.4", - "postcss-selector-parser": "^6.0.4" + "browserslist": "^4.25.1", + "postcss-selector-parser": "^7.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.32" + } + }, + "node_modules/stylehacks/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" } }, "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", - "devOptional": true + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "node_modules/super-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz", + "integrity": "sha512-CY8u7DtbvucKuquCmOFEKhr9Besln7n9uN8eFbwcoGYWXOMW07u2o8njWaiXt11ylS3qoGF55pILjRmPlbodyg==", + "dev": true, + "dependencies": { + "function-timeout": "^1.0.1", + "time-span": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "optional": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -22062,23 +23350,24 @@ } }, "node_modules/svgo": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.2.tgz", - "integrity": "sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", + "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", "dev": true, "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", + "commander": "^11.1.0", "css-select": "^5.1.0", - "css-tree": "^2.2.1", + "css-tree": "^3.0.1", + "css-what": "^6.1.0", "csso": "^5.0.5", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1", + "sax": "^1.4.1" }, "bin": { - "svgo": "bin/svgo" + "svgo": "bin/svgo.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=16" }, "funding": { "type": "opencollective", @@ -22086,25 +23375,25 @@ } }, "node_modules/svgo/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "dev": true, "engines": { - "node": ">= 10" + "node": ">=16" } }, "node_modules/swagger-schema-official": { "version": "2.0.0-bab6bed", "resolved": "https://registry.npmjs.org/swagger-schema-official/-/swagger-schema-official-2.0.0-bab6bed.tgz", "integrity": "sha512-rCC0NWGKr/IJhtRuPq/t37qvZHI/mH4I4sxflVM+qgVe5Z2uOCivzWaVbuioJaB61kvm5UvB7b49E+oBY0M8jA==", - "optional": true + "dev": true }, "node_modules/swagger-typescript-api": { "version": "13.2.7", "resolved": "https://registry.npmjs.org/swagger-typescript-api/-/swagger-typescript-api-13.2.7.tgz", "integrity": "sha512-rfqqoRFpZJPl477M/snMJPM90EvI8WqhuUHSF5ecC2r/w376T29+QXNJFVPsJmbFu5rBc/8m3vhArtMctjONdw==", - "optional": true, + "dev": true, "dependencies": { "@biomejs/js-api": "1.0.0", "@biomejs/wasm-nodejs": "2.0.5", @@ -22132,13 +23421,13 @@ "version": "5.1.5", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], - "optional": true, "bin": { "nanoid": "bin/nanoid.js" }, @@ -22146,11 +23435,24 @@ "node": "^18 || >=20" } }, + "node_modules/swagger-typescript-api/node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/swagger2openapi": { "version": "7.0.8", "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", - "optional": true, + "dev": true, "dependencies": { "call-me-maybe": "^1.0.1", "node-fetch": "^2.6.1", @@ -22173,6 +23475,19 @@ "url": "https://github.com/Mermade/oas-kit?sponsor=1" } }, + "node_modules/swc-loader": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.6.tgz", + "integrity": "sha512-9Zi9UP2YmDpgmQVbyOPJClY0dwf58JDyDMQ7uRc4krmc72twNI2fvlBWHLqVekBpPc7h5NJkGVT1zNDxFrqhvg==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3" + }, + "peerDependencies": { + "@swc/core": "^1.2.147", + "webpack": ">=2" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -22180,19 +23495,18 @@ "dev": true }, "node_modules/synckit": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", - "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", "optional": true, "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" + "@pkgr/core": "^0.2.9" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/unts" + "url": "https://opencollective.com/synckit" } }, "node_modules/tabbable": { @@ -22214,30 +23528,48 @@ "version": "3.2.29", "resolved": "https://registry.npmjs.org/tcomb/-/tcomb-3.2.29.tgz", "integrity": "sha512-di2Hd1DB2Zfw6StGv861JoAF5h/uQVu/QJp2g8KVbtfKnoHdBQl5M32YWq6mnSYBQ1vFFrns5B1haWJL7rKaOQ==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/tcomb-validation": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/tcomb-validation/-/tcomb-validation-3.4.1.tgz", "integrity": "sha512-urVVMQOma4RXwiVCa2nM2eqrAomHROHvWPuj6UkDGz/eb5kcy0x6P0dVt6kzpUZtYMNoAqJLWmz1BPtxrtjtrA==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "tcomb": "^3.0.0" } }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", - "dev": true, + "node_modules/terser": { + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", + "devOptional": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "devOptional": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -22265,7 +23597,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -22274,7 +23606,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, + "devOptional": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -22284,29 +23616,11 @@ "node": ">= 10.13.0" } }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/terser-webpack-plugin/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -22317,29 +23631,17 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/terser-webpack-plugin/node_modules/terser": { - "version": "5.26.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.26.0.tgz", - "integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "devOptional": true }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, + "devOptional": true, "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -22372,8 +23674,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", - "dev": true, "license": "MIT", + "optional": true, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -22382,8 +23684,8 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/thunky": { "version": "1.1.0", @@ -22392,46 +23694,96 @@ "dev": true, "license": "MIT" }, - "node_modules/tinydate": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tinydate/-/tinydate-1.3.0.tgz", - "integrity": "sha512-7cR8rLy2QhYHpsBDBVYnnWXm8uRTr38RoZakFSW7Bs7PzfMPNZthuMLkwqZv7MTu8lhQ91cOFYS5a7iFj2oR3w==", + "node_modules/time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "dev": true, + "dependencies": { + "convert-hrtime": "^5.0.0" + }, "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "optional": true + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "dev": true + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "devOptional": true, + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "devOptional": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "devOptional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/tldts": { - "version": "6.1.58", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.58.tgz", - "integrity": "sha512-MQJrJhjHOYGYb8DobR6Y4AdDbd4TYkyQ+KBDVc5ODzs1cbrvPpfN1IemYi9jfipJ/vR1YWvrDli0hg1y19VRoA==", - "dev": true, - "license": "MIT", + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "optional": true, "dependencies": { - "tldts-core": "^6.1.58" + "tldts-core": "^6.1.86" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.58", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.58.tgz", - "integrity": "sha512-dR936xmhBm7AeqHIhCWwK765gZ7dFyL+IqLSFAjJbFlUXGMLCb8i2PzlzaOuWBuplBTaBYseSb565nk/ZEM0Bg==", - "dev": true, - "license": "MIT" + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "optional": true }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=14.14" } @@ -22440,7 +23792,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true + "devOptional": true }, "node_modules/toidentifier": { "version": "1.0.1", @@ -22453,16 +23805,17 @@ } }, "node_modules/token-types": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", - "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz", + "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", "dev": true, "dependencies": { + "@borewit/text-codec": "^0.1.0", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" }, "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { "type": "github", @@ -22473,6 +23826,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, "engines": { "node": ">=6" } @@ -22505,7 +23859,7 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "optional": true + "dev": true }, "node_modules/tree-dump": { "version": "1.0.3", @@ -22528,39 +23882,39 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "tree-kill": "cli.js" } }, "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", - "license": "MIT", - "optional": true, + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "devOptional": true, "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/ts-jest": { - "version": "29.1.4", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.4.tgz", - "integrity": "sha512-YiHwDhSvCiItoAgsKtoLFCuakDzDsJ1DLDnSouTaTmdOcOwIkSzbLXduaQ6M5DRVhuZC/NYaaZ/mtHbWMv/S6Q==", - "dev": true, + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.1.tgz", + "integrity": "sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw==", + "optional": true, "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "^7.5.3", - "yargs-parser": "^21.0.1" + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.2", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" }, "bin": { "ts-jest": "cli.js" @@ -22570,10 +23924,11 @@ }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", "typescript": ">=4.3 <6" }, "peerDependenciesMeta": { @@ -22591,17 +23946,17 @@ }, "esbuild": { "optional": true + }, + "jest-util": { + "optional": true } } }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "optional": true, "bin": { "semver": "bin/semver.js" }, @@ -22610,9 +23965,9 @@ } }, "node_modules/ts-loader": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", - "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.2.tgz", + "integrity": "sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -22840,8 +24195,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "license": "Apache-2.0", + "optional": true, "dependencies": { "safe-buffer": "^5.0.1" }, @@ -22853,8 +24207,7 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true, - "license": "Unlicense" + "optional": true }, "node_modules/type-check": { "version": "0.4.0", @@ -22872,19 +24225,21 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, + "devOptional": true, "engines": { "node": ">=4" } }, "node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "optional": true, "engines": { - "node": ">=8" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/type-is": { @@ -22902,28 +24257,30 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "optional": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" + "is-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "optional": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -22933,16 +24290,18 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "optional": true, "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" }, "engines": { "node": ">= 0.4" @@ -22952,16 +24311,17 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "optional": true, "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-proto": "^1.0.3", "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" }, "engines": { "node": ">= 0.4" @@ -22974,16 +24334,16 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "is-typedarray": "^1.0.0" } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "devOptional": true, "bin": { "tsc": "bin/tsc", @@ -22993,15 +24353,43 @@ "node": ">=14.17" } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uint8array-extras": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.1.tgz", + "integrity": "sha512-+NWHrac9dvilNgme+gP4YrBSumsaMZP0fNBtXXFIf33RLLKEcBUKaQZ7ULUbS0sBfcjxIZ4V96OTRkCbM7hxpw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "optional": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bound": "^1.0.3", "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -23011,15 +24399,14 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "devOptional": true }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "dev": true, "license": "MIT", - "peer": true, + "optional": true, "engines": { "node": ">=4" } @@ -23028,9 +24415,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, "license": "MIT", - "peer": true, + "optional": true, "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -23043,9 +24429,8 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", - "dev": true, "license": "MIT", - "peer": true, + "optional": true, "engines": { "node": ">=4" } @@ -23054,18 +24439,29 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true, "license": "MIT", - "peer": true, + "optional": true, "engines": { "node": ">=4" } }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 10.0.0" @@ -23081,21 +24477,55 @@ "node": ">= 0.8" } }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, "node_modules/untildify": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=8" } }, "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", - "dev": true, + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "devOptional": true, "funding": [ { "type": "opencollective", @@ -23110,10 +24540,9 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "escalade": "^3.2.0", - "picocolors": "^1.1.0" + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -23126,8 +24555,8 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "registry-auth-token": "3.3.2", "registry-url": "3.1.0" @@ -23169,20 +24598,6 @@ } } }, - "node_modules/url-loader/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, "node_modules/url-loader/node_modules/schema-utils": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", @@ -23236,7 +24651,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "uuid": "dist/bin/uuid" @@ -23246,7 +24661,7 @@ "version": "9.2.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -23260,13 +24675,14 @@ "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 + "devOptional": true }, "node_modules/validate-npm-package-license": { "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==", "license": "Apache-2.0", + "optional": true, "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -23276,8 +24692,8 @@ "version": "13.12.0", "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">= 0.10" } @@ -23286,7 +24702,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.8" } @@ -23295,11 +24711,10 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, "engines": [ "node >=0.6.0" ], - "license": "MIT", + "optional": true, "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -23310,15 +24725,15 @@ "version": "1.0.12", "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/vscode-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", - "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", @@ -23336,7 +24751,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, + "devOptional": true, "dependencies": { "makeerror": "1.0.12" } @@ -23354,24 +24769,25 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "optional": true + "dev": true }, "node_modules/webpack": { - "version": "5.95.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", - "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", - "dev": true, - "license": "MIT", + "version": "5.101.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz", + "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", + "devOptional": true, "dependencies": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", + "enhanced-resolve": "^5.17.3", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -23381,11 +24797,11 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", + "schema-utils": "^4.3.2", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", + "terser-webpack-plugin": "^5.3.11", "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" + "webpack-sources": "^3.3.3" }, "bin": { "webpack": "bin/webpack.js" @@ -23475,42 +24891,39 @@ } }, "node_modules/webpack-cli": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", - "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", + "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", + "@discoveryjs/json-ext": "^0.6.1", + "@webpack-cli/configtest": "^3.0.1", + "@webpack-cli/info": "^3.0.1", + "@webpack-cli/serve": "^3.0.1", "colorette": "^2.0.14", - "commander": "^10.0.1", + "commander": "^12.1.0", "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", + "envinfo": "^7.14.0", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^3.1.1", "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" + "webpack-merge": "^6.0.1" }, "bin": { "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "5.x.x" + "webpack": "^5.82.0" }, "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, "webpack-bundle-analyzer": { "optional": true }, @@ -23519,13 +24932,13 @@ } } }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "node_modules/webpack-cli/node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", "dev": true, "engines": { - "node": ">=14" + "node": ">=14.17.0" } }, "node_modules/webpack-dev-middleware": { @@ -23659,159 +25072,33 @@ } }, "node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", "dev": true, "dependencies": { "clone-deep": "^4.0.1", "flat": "^5.0.2", - "wildcard": "^2.0.0" + "wildcard": "^2.0.1" }, "engines": { - "node": ">=10.0.0" + "node": ">=18.0.0" } }, - "node_modules/webpack/node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, - "node_modules/webpack/node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", - "dev": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" - } - }, - "node_modules/webpack/node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true - }, - "node_modules/webpack/node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", - "dev": true - }, - "node_modules/webpack/node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true - }, - "node_modules/webpack/node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" - } - }, - "node_modules/webpack/node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/webpack/node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", - "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/webpack/node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true - }, - "node_modules/webpack/node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" - } - }, - "node_modules/webpack/node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "node_modules/webpack/node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" - } - }, - "node_modules/webpack/node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "node_modules/webpack/node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@xtuc/long": "4.2.2" + "node_modules/webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "devOptional": true, + "engines": { + "node": ">=10.13.0" } }, "node_modules/webpack/node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, + "devOptional": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -23824,34 +25111,16 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6.11.5" } }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/webpack/node_modules/watchpack": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", - "dev": true, + "devOptional": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -23860,15 +25129,6 @@ "node": ">=10.13.0" } }, - "node_modules/webpack/node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -23929,7 +25189,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "optional": true, + "dev": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -23951,40 +25211,43 @@ } }, "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "optional": true, "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/which-builtin-type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.0.tgz", - "integrity": "sha512-I+qLGQ/vucCby4tf5HsLmGueEla4ZhwTBSqaooS+Y0BuxN4Cp+okmGuV+8mXZ84KDI9BA+oklo+RzKg0ONdSUA==", - "license": "MIT", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", "optional": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", - "is-date-object": "^1.0.5", + "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", - "is-regex": "^1.1.4", + "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", - "which-boxed-primitive": "^1.0.2", + "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", - "which-typed-array": "^1.1.15" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -23997,8 +25260,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "devOptional": true, - "license": "MIT", + "optional": true, "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", @@ -24016,18 +25278,21 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/which-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "optional": true, "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" }, "engines": { @@ -24041,8 +25306,8 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", - "dev": true, "license": "MIT", + "optional": true, "dependencies": { "string-width": "^5.0.1" }, @@ -24057,8 +25322,8 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=12" }, @@ -24070,15 +25335,15 @@ "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" + "license": "MIT", + "optional": true }, "node_modules/widest-line/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", + "optional": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -24095,8 +25360,8 @@ "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", + "optional": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -24108,17 +25373,23 @@ } }, "node_modules/wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "optional": true + }, "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", + "optional": true, "peer": true }, "node_modules/wrap-ansi": { @@ -24230,13 +25501,13 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "devOptional": true }, "node_modules/write-file-atomic": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, + "devOptional": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" @@ -24270,8 +25541,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", - "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=12" }, @@ -24354,7 +25625,6 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "optional": true, "engines": { "node": ">= 6" } @@ -24390,8 +25660,8 @@ "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", + "optional": true, "peer": true, "dependencies": { "camelcase": "^6.0.0", @@ -24407,8 +25677,8 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, "license": "MIT", + "optional": true, "peer": true, "engines": { "node": ">=10" @@ -24421,8 +25691,8 @@ "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", + "optional": true, "peer": true, "engines": { "node": ">=10" @@ -24435,8 +25705,8 @@ "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", + "optional": true, "peer": true, "engines": { "node": ">=8" @@ -24446,8 +25716,8 @@ "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", + "optional": true, "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" diff --git a/workspaces/frontend/package.json b/workspaces/frontend/package.json index bf5e5ca4..b1e52e3b 100644 --- a/workspaces/frontend/package.json +++ b/workspaces/frontend/package.json @@ -18,91 +18,88 @@ "build:bundle-profile": "webpack --config ./config/webpack.prod.js --profile --json > ./bundle.stats.json", "build:bundle-analyze": "webpack-bundle-analyzer ./bundle.stats.json", "build:clean": "rimraf ./dist", - "build:prod": "cross-env PRODUCTION=true webpack --config ./config/webpack.prod.js", + "build:prod": "webpack --config ./config/webpack.prod.js", "generate:api": "./scripts/generate-api.sh && npm run prettier", - "start:dev": "cross-env STYLE_THEME=$npm_config_theme webpack serve --hot --color --config ./config/webpack.dev.js", - "start:dev:mock": "cross-env MOCK_API_ENABLED=true STYLE_THEME=$npm_config_theme npm run start:dev", - "test": "run-s prettier:check test:lint test:unit test:cypress-ci", - "test:cypress-ci": "npx concurrently -P -k -s first \"npm run cypress:server:build && npm run cypress:server\" \"npx wait-on tcp:127.0.0.1:9001 && npm run cypress:run:mock -- {@}\" -- ", + "start:dev": "webpack serve --hot --color --config ./config/webpack.dev.js", + "test": "run-s prettier:check test:lint test:type-check test:unit test:cypress-ci", + "test:cypress-ci": "npx concurrently -P -k -s first \"CY_MOCK=1 npm run cypress:server:build && npm run cypress:server\" \"npx wait-on tcp:127.0.0.1:9001 && npm run cypress:run:mock -- {@}\" -- ", "test:jest": "jest --passWithNoTests", "test:unit": "npm run test:jest -- --silent", "test:watch": "jest --watch", "test:coverage": "jest --coverage", "test:lint": "eslint --max-warnings 0 --ext .js,.ts,.jsx,.tsx ./src", "test:lint:fix": "eslint --ext .js,.ts,.jsx,.tsx ./src --fix", + "test:type-check": "tsc --noEmit", "test:fix": "run-s prettier test:lint:fix", "cypress:open": "cypress open --project src/__tests__/cypress", "cypress:open:mock": "CY_MOCK=1 CY_WS_PORT=9002 npm run cypress:open -- ", "cypress:run": "cypress run -b chrome --project src/__tests__/cypress", "cypress:run:mock": "CY_MOCK=1 npm run cypress:run -- ", - "cypress:server:build": "POLL_INTERVAL=9999999 FAST_POLL_INTERVAL=9999999 APP_PREFIX=/ webpack --config ./config/webpack.prod.js", + "cypress:server:build": "npm run build", "cypress:server": "serve ./dist -p 9001 -s -L", "prettier": "prettier --ignore-path .gitignore --write \"**/*{.ts,.tsx,.js,.cjs,.jsx,.css,.json}\"", "prettier:check": "prettier --ignore-path .gitignore --check \"**/*{.ts,.tsx,.js,.cjs,.jsx,.css,.json}\"", "prepare": "cd ../../ && husky workspaces/frontend/.husky" }, "devDependencies": { - "@cspell/eslint-plugin": "^9.1.2", - "@cypress/code-coverage": "^3.13.5", - "@mui/icons-material": "^6.3.1", + "@mui/icons-material": "^6.4.8", "@mui/material": "^6.3.1", "@mui/types": "^7.2.21", - "@testing-library/cypress": "^10.0.1", - "@testing-library/dom": "^10.4.0", - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "14.4.3", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", + "@swc/core": "^1.9.1", "@types/chai-subset": "^1.3.5", - "@types/jest": "^29.5.3", - "@types/react-router-dom": "^5.3.3", - "@types/victory": "^33.1.5", + "@types/classnames": "^2.3.1", + "@types/dompurify": "^3.2.0", + "@types/jest": "^29.5.13", + "@types/js-yaml": "^4.0.9", + "@types/lodash-es": "^4.17.8", + "@types/react-dom": "^18.3.1", + "@types/showdown": "^2.0.3", + "@typescript-eslint/eslint-plugin": "^8.35.0", "chai-subset": "^1.6.0", "concurrently": "^9.1.0", - "copy-webpack-plugin": "^11.0.0", - "core-js": "^3.39.0", - "cross-env": "^7.0.3", - "css-loader": "^6.11.0", - "css-minimizer-webpack-plugin": "^5.0.1", - "cypress": "^13.16.1", - "cypress-axe": "^1.5.0", - "cypress-high-resolution": "^1.0.0", - "cypress-mochawesome-reporter": "^3.8.2", - "cypress-multi-reporters": "^2.0.4", - "dotenv": "^16.4.5", - "dotenv-webpack": "^8.1.0", - "expect": "^29.7.0", - "fork-ts-checker-webpack-plugin": "^9.0.3", - "html-webpack-plugin": "^5.6.0", + "copy-webpack-plugin": "^13.0.0", + "core-js": "^3.40.0", + "css-loader": "^7.1.2", + "css-minimizer-webpack-plugin": "^7.0.0", + "dotenv": "^16.5.0", + "dotenv-expand": "^5.1.0", + "dotenv-webpack": "^6.0.0", + "expect": "^30.0.2", + "file-loader": "^6.2.0", + "fork-ts-checker-webpack-plugin": "^9.0.2", + "html-webpack-plugin": "^5.6.3", "husky": "^9.1.7", - "imagemin": "^8.0.1", + "imagemin": "^9.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "junit-report-merger": "^7.0.0", + "junit-report-merger": "^7.0.1", "mini-css-extract-plugin": "^2.9.0", - "postcss": "^8.4.38", - "prettier": "^3.3.0", + "postcss": "^8.4.49", + "prettier": "^3.3.3", "prop-types": "^15.8.1", "raw-loader": "^4.0.2", - "react-router-dom": "^6.26.1", - "regenerator-runtime": "^0.13.11", + "react-refresh": "^0.14.2", + "regenerator-runtime": "^0.14.1", "rimraf": "^6.0.1", - "sass": "^1.83.4", - "sass-loader": "^16.0.4", - "serve": "^14.2.1", - "style-loader": "^3.3.4", + "sass": "^1.87.0", + "sass-loader": "^16.0.0", + "speed-measure-webpack-plugin": "^1.5.0", + "style-loader": "^4.0.0", "svg-url-loader": "^8.0.0", - "terser-webpack-plugin": "^5.3.10", - "ts-jest": "^29.1.4", - "ts-loader": "^9.5.1", + "swagger-typescript-api": "13.2.7", + "swc-loader": "^0.2.6", + "terser-webpack-plugin": "^5.3.11", + "ts-loader": "^9.5.2", "tsconfig-paths-webpack-plugin": "^4.1.0", - "tslib": "^2.6.0", - "typescript": "^5.4.5", + "tslib": "^2.7.0", + "typescript": "^5.8.2", "url-loader": "^4.1.1", - "webpack": "^5.91.0", + "webpack": "^5.97.1", "webpack-bundle-analyzer": "^4.10.2", - "webpack-cli": "^5.1.4", - "webpack-dev-server": "^5.2.2", - "webpack-merge": "^5.10.0" + "webpack-cli": "^6.0.1", + "webpack-dev-server": "^5.2.0", + "webpack-merge": "^6.0.1" }, "dependencies": { "@patternfly/patternfly": "^6.3.1", @@ -112,35 +109,58 @@ "@patternfly/react-icons": "^6.3.1", "@patternfly/react-styles": "^6.3.1", "@patternfly/react-table": "^6.3.1", + "@patternfly/react-templates": "^6.3.1", "@patternfly/react-tokens": "^6.3.1", "@types/js-yaml": "^4.0.9", - "axios": "^1.10.0", - "date-fns": "^4.1.0", - "eslint-plugin-local-rules": "^3.0.2", - "js-yaml": "^4.1.0", - "npm-run-all": "^4.1.5", - "react": "^18", - "react-dom": "^18", - "react-router": "^6.26.2", - "sirv-cli": "^2.0.2" - }, - "optionalDependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", - "@typescript-eslint/eslint-plugin": "^8.8.1", - "@typescript-eslint/parser": "^8.12.2", + "axios": "^1.10.0", + "date-fns": "^4.1.0", + "classnames": "^2.2.6", + "dompurify": "^3.2.4", + "js-yaml": "^4.1.0", + "lodash-es": "^4.17.15", + "react": "^18", + "react-dom": "^18", + "react-router": "^7.5.2", + "react-router-dom": "^7.6.1", + "sass": "^1.83.0", + "showdown": "^2.1.0" + }, + "optionalDependencies": { + "@babel/preset-env": "^7.26.9", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.5", + "@cspell/eslint-plugin": "^9.1.2", + "@cypress/code-coverage": "^3.14.1", + "@cypress/webpack-preprocessor": "^6.0.4", + "@testing-library/cypress": "^10.0.3", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.2", + "@testing-library/react": "^16.2.0", + "@testing-library/user-event": "14.6.1", + "@typescript-eslint/eslint-plugin": "^8.31.1", + "@typescript-eslint/parser": "^8.31.1", + "cypress": "^14.4.1", + "cypress-axe": "^1.6.0", + "cypress-high-resolution": "^1.0.0", + "cypress-mochawesome-reporter": "^3.8.2", + "cypress-multi-reporters": "^2.0.5", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-node": "^0.3.7", - "eslint-import-resolver-typescript": "^3.5.3", + "eslint-import-resolver-typescript": "^3.8.3", "eslint-plugin-cypress": "^3.3.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-local-rules": "^3.0.2", "eslint-plugin-no-only-tests": "^3.1.0", - "eslint-plugin-no-relative-import-paths": "^1.5.2", - "eslint-plugin-prettier": "^5.0.0", - "eslint-plugin-react": "^7.37.2", - "eslint-plugin-react-hooks": "^5.0.0", - "swagger-typescript-api": "^13.2.7" + "eslint-plugin-no-relative-import-paths": "^1.6.1", + "eslint-plugin-prettier": "^5.4.0", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", + "npm-run-all": "^4.1.5", + "serve": "^14.2.4", + "ts-jest": "^29.4.0" } } diff --git a/workspaces/frontend/src/__tests__/cypress/cypress.config.ts b/workspaces/frontend/src/__tests__/cypress/cypress.config.ts index 3708b3d9..319115b8 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress.config.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress.config.ts @@ -4,12 +4,16 @@ import { defineConfig } from 'cypress'; import coverage from '@cypress/code-coverage/task'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore no types available +import webpack from '@cypress/webpack-preprocessor'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore no types available import cypressHighResolution from 'cypress-high-resolution'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore no types available import { beforeRunHook, afterRunHook } from 'cypress-mochawesome-reporter/lib'; import { mergeFiles } from 'junit-report-merger'; import { env, BASE_URL } from '~/__tests__/cypress/cypress/utils/testConfig'; +import webpackConfig from './cypress/webpack.config'; const resultsDir = `${env.CY_RESULTS_DIR || 'results'}/${env.CY_MOCK ? 'mocked' : 'e2e'}`; @@ -41,7 +45,6 @@ export default defineConfig({ env: { MOCK: !!env.CY_MOCK, coverage: !!env.CY_COVERAGE, - APP_PREFIX: env.APP_PREFIX || '/workspaces', codeCoverage: { exclude: [path.resolve(__dirname, '../../third_party/**')], }, @@ -49,12 +52,20 @@ export default defineConfig({ }, defaultCommandTimeout: 10000, e2e: { - baseUrl: env.CY_MOCK ? BASE_URL || 'http://localhost:9001' : 'http://localhost:9000', + baseUrl: BASE_URL, specPattern: env.CY_MOCK ? `cypress/tests/mocked/**/*.cy.ts` : `cypress/tests/e2e/**/*.cy.ts`, experimentalInteractiveRunEvents: true, setupNodeEvents(on, config) { cypressHighResolution(on, config); coverage(on, config); + + // Configure webpack preprocessor with custom webpack config + const options = { + webpackOptions: webpackConfig, + watchOptions: {}, + }; + on('file:preprocessor', webpack(options)); + on('task', { readJSON(filePath: string) { const absPath = path.resolve(__dirname, filePath); diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts index 4bbfcdc2..ce5a4ba3 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts @@ -13,7 +13,7 @@ describe('Application', () => { cy.intercept('GET', `/api/v1/workspaces/${mockNamespaces[0].name}`, { body: mockBFFResponse({ mockWorkspace1 }), }).as('getWorkspaces'); - cy.visit('/workspaces'); + cy.visit('/'); cy.wait('@getNamespaces'); cy.wait('@getWorkspaces'); }); diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/WorkspaceDetailsActivity.cy.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/WorkspaceDetailsActivity.cy.ts index 73648410..528ca287 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/WorkspaceDetailsActivity.cy.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/WorkspaceDetailsActivity.cy.ts @@ -6,7 +6,7 @@ describe('WorkspaceDetailsActivity Component', () => { cy.intercept('GET', 'api/v1/workspaces', { body: mockBFFResponse(mockWorkspaces), }).as('getWorkspaces'); - cy.visit('/workspaces'); + cy.visit('/'); }); // This tests depends on the mocked workspaces data at home page, needs revisit once workspace data fetched from BE diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/webpack.config.ts b/workspaces/frontend/src/__tests__/cypress/cypress/webpack.config.ts new file mode 100644 index 00000000..53d0c6c3 --- /dev/null +++ b/workspaces/frontend/src/__tests__/cypress/cypress/webpack.config.ts @@ -0,0 +1,115 @@ +import path from 'path'; + +const webpackConfig = { + mode: 'development' as const, + resolve: { + extensions: ['.ts', '.tsx', '.js', '.jsx'], + alias: { + '~': path.resolve(__dirname, '../../../'), + }, + }, + module: { + rules: [ + { + test: /\.(tsx|ts|jsx|js)?$/, + exclude: [/node_modules/], + use: [ + { + loader: 'ts-loader', + options: { + transpileOnly: true, + }, + }, + ], + }, + { + test: /\.(svg|ttf|eot|woff|woff2)$/, + // Handle fonts from PatternFly and other sources + include: [ + path.resolve(__dirname, '../../../node_modules/patternfly/dist/fonts'), + path.resolve( + __dirname, + '../../../node_modules/@patternfly/react-core/dist/styles/assets/fonts', + ), + path.resolve( + __dirname, + '../../../node_modules/@patternfly/react-core/dist/styles/assets/pficon', + ), + path.resolve(__dirname, '../../../node_modules/@patternfly/patternfly/assets/fonts'), + path.resolve(__dirname, '../../../node_modules/@patternfly/patternfly/assets/pficon'), + ], + use: { + loader: 'file-loader', + options: { + limit: 5000, + outputPath: 'fonts', + name: '[name].[ext]', + }, + }, + }, + { + test: /\.svg$/, + include: (input: string): boolean => input.indexOf('background-filter.svg') > 1, + use: [ + { + loader: 'url-loader', + options: { + limit: 5000, + outputPath: 'svgs', + name: '[name].[ext]', + }, + }, + ], + }, + { + test: /\.svg$/, + // Handle SVG files + include: (input: string): boolean => + input.indexOf('images') > -1 && + input.indexOf('fonts') === -1 && + input.indexOf('background-filter') === -1 && + input.indexOf('pficon') === -1, + use: { + loader: 'raw-loader', + options: {}, + }, + }, + { + test: /\.(jpg|jpeg|png|gif)$/i, + include: [ + path.resolve(__dirname, '../../'), + path.resolve(__dirname, '../../../node_modules/patternfly'), + path.resolve(__dirname, '../../../node_modules/@patternfly/patternfly/assets/images'), + path.resolve( + __dirname, + '../../../node_modules/@patternfly/react-styles/css/assets/images', + ), + path.resolve( + __dirname, + '../../../node_modules/@patternfly/react-core/dist/styles/assets/images', + ), + ], + use: [ + { + loader: 'url-loader', + options: { + limit: 5000, + outputPath: 'images', + name: '[name].[ext]', + }, + }, + ], + }, + { + test: /\.s[ac]ss$/i, + use: ['style-loader', 'css-loader', 'sass-loader'], + }, + { + test: /\.css$/i, + use: ['style-loader', 'css-loader'], + }, + ], + }, +}; + +export default webpackConfig; diff --git a/workspaces/frontend/src/__tests__/unit/jest.setup.ts b/workspaces/frontend/src/__tests__/unit/jest.setup.ts index 05995b97..a157b6b7 100644 --- a/workspaces/frontend/src/__tests__/unit/jest.setup.ts +++ b/workspaces/frontend/src/__tests__/unit/jest.setup.ts @@ -1,4 +1,4 @@ -import { TextEncoder } from 'util'; +import { TextEncoder as UtilTextEncoder } from 'util'; import { JestAssertionError } from 'expect'; import 'core-js/actual/array/to-sorted'; import { @@ -7,7 +7,10 @@ import { createComparativeValue, } from '~/__tests__/unit/testUtils/hooks'; -global.TextEncoder = TextEncoder; +// Ensure TextEncoder is available in the JSDOM environment for tests. +// Node's util.TextEncoder has slightly different TS types than DOM's, so cast to any. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +(globalThis as any).TextEncoder = UtilTextEncoder; const tryExpect = (expectFn: () => void) => { try { diff --git a/workspaces/frontend/src/app/App.tsx b/workspaces/frontend/src/app/App.tsx index df321091..d51be6ea 100644 --- a/workspaces/frontend/src/app/App.tsx +++ b/workspaces/frontend/src/app/App.tsx @@ -22,14 +22,15 @@ import { BarsIcon } from '@patternfly/react-icons/dist/esm/icons/bars-icon'; import ErrorBoundary from '~/app/error/ErrorBoundary'; import NamespaceSelector from '~/shared/components/NamespaceSelector'; import logoDarkTheme from '~/images/logo-dark-theme.svg'; +import { DEPLOYMENT_MODE, isMUITheme } from '~/shared/utilities/const'; +import { DeploymentMode, Theme } from '~/shared/utilities/types'; import { NamespaceContextProvider } from './context/NamespaceContextProvider'; import AppRoutes from './AppRoutes'; import NavSidebar from './NavSidebar'; import { NotebookContextProvider } from './context/NotebookContext'; -import { isMUITheme, Theme } from './const'; import { BrowserStorageContextProvider } from './context/BrowserStorageContext'; -const isStandalone = process.env.PRODUCTION !== 'true'; +const isStandalone = DEPLOYMENT_MODE === DeploymentMode.Standalone; const App: React.FC = () => { useEffect(() => { diff --git a/workspaces/frontend/src/app/AppRoutes.tsx b/workspaces/frontend/src/app/AppRoutes.tsx index 6b0136b4..ea9a2ba9 100644 --- a/workspaces/frontend/src/app/AppRoutes.tsx +++ b/workspaces/frontend/src/app/AppRoutes.tsx @@ -69,7 +69,10 @@ const AppRoutes: React.FC = () => { } /> } /> } /> - } /> + } + /> } /> { // TODO: Remove the linter skip when we implement authentication diff --git a/workspaces/frontend/src/app/NavSidebar.tsx b/workspaces/frontend/src/app/NavSidebar.tsx index 3fd90631..c1ef5238 100644 --- a/workspaces/frontend/src/app/NavSidebar.tsx +++ b/workspaces/frontend/src/app/NavSidebar.tsx @@ -9,8 +9,8 @@ import { } from '@patternfly/react-core/dist/esm/components/Nav'; import { PageSidebar, PageSidebarBody } from '@patternfly/react-core/dist/esm/components/Page'; import { useTypedLocation } from '~/app/routerHelper'; +import { isMUITheme, LOGO_LIGHT, URL_PREFIX } from '~/shared/utilities/const'; import { useNavData, isNavDataGroup, NavDataHref, NavDataGroup } from './AppRoutes'; -import { APP_PREFIX, isMUITheme, LOGO_LIGHT } from './const'; const NavHref: React.FC<{ item: NavDataHref }> = ({ item }) => { const location = useTypedLocation(); @@ -61,7 +61,7 @@ const NavSidebar: React.FC = () => { diff --git a/workspaces/frontend/src/app/components/ThemeAwareSearchInput.tsx b/workspaces/frontend/src/app/components/ThemeAwareSearchInput.tsx index ce82341a..d916b8d1 100644 --- a/workspaces/frontend/src/app/components/ThemeAwareSearchInput.tsx +++ b/workspaces/frontend/src/app/components/ThemeAwareSearchInput.tsx @@ -4,8 +4,8 @@ import { SearchInputProps, } from '@patternfly/react-core/dist/esm/components/SearchInput'; import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput'; -import FormFieldset from 'app/components/FormFieldset'; -import { isMUITheme } from 'app/const'; +import FormFieldset from '~/app/components/FormFieldset'; +import { isMUITheme } from '~/shared/utilities/const'; type ThemeAwareSearchInputProps = Omit & { onChange: (value: string) => void; // Simplified onChange signature diff --git a/workspaces/frontend/src/app/components/WorkspaceTable.tsx b/workspaces/frontend/src/app/components/WorkspaceTable.tsx index 7c813f05..b12db8cb 100644 --- a/workspaces/frontend/src/app/components/WorkspaceTable.tsx +++ b/workspaces/frontend/src/app/components/WorkspaceTable.tsx @@ -504,12 +504,7 @@ const WorkspaceTable = React.forwardRef( })} {canCreateWorkspaces && ( - diff --git a/workspaces/frontend/src/app/const.ts b/workspaces/frontend/src/app/const.ts deleted file mode 100644 index 04f215bc..00000000 --- a/workspaces/frontend/src/app/const.ts +++ /dev/null @@ -1,16 +0,0 @@ -export const BFF_API_VERSION = 'v1'; - -export enum Theme { - Default = 'default-theme', - MUI = 'mui-theme', - // Future themes can be added here -} - -export const isMUITheme = (): boolean => STYLE_THEME === Theme.MUI; -const STYLE_THEME = process.env.STYLE_THEME || Theme.MUI; - -export const LOGO_LIGHT = process.env.LOGO || 'logo.svg'; - -export const DEFAULT_POLLING_RATE_MS = 10000; - -export const APP_PREFIX = process.env.APP_PREFIX || '/workspaces'; diff --git a/workspaces/frontend/src/app/context/NotebookContext.tsx b/workspaces/frontend/src/app/context/NotebookContext.tsx index 5cd35053..ba26a04a 100644 --- a/workspaces/frontend/src/app/context/NotebookContext.tsx +++ b/workspaces/frontend/src/app/context/NotebookContext.tsx @@ -1,6 +1,6 @@ import React, { ReactNode, useMemo } from 'react'; -import { APP_PREFIX, BFF_API_VERSION } from '~/app/const'; import EnsureAPIAvailability from '~/app/EnsureAPIAvailability'; +import { BFF_API_PREFIX, BFF_API_VERSION } from '~/shared/utilities/const'; import useNotebookAPIState, { NotebookAPIState } from './useNotebookAPIState'; export type NotebookContextType = { @@ -19,8 +19,8 @@ interface NotebookContextProviderProps { } export const NotebookContextProvider: React.FC = ({ children }) => { - // Remove trailing slash from APP_PREFIX to avoid double slashes - const cleanPrefix = APP_PREFIX.replace(/\/$/, ''); + // Remove trailing slash from BFF_API_PREFIX to avoid double slashes + const cleanPrefix = BFF_API_PREFIX.replace(/\/$/, ''); const hostPath = `${cleanPrefix}/api/${BFF_API_VERSION}`; const [apiState, refreshAPIState] = useNotebookAPIState(hostPath); diff --git a/workspaces/frontend/src/app/context/useNotebookAPIState.tsx b/workspaces/frontend/src/app/context/useNotebookAPIState.tsx index 0fc313ac..fa8db8b1 100644 --- a/workspaces/frontend/src/app/context/useNotebookAPIState.tsx +++ b/workspaces/frontend/src/app/context/useNotebookAPIState.tsx @@ -3,11 +3,10 @@ import { NotebookApis, notebookApisImpl } from '~/shared/api/notebookApi'; import { APIState } from '~/shared/api/types'; import useAPIState from '~/shared/api/useAPIState'; import { mockNotebookApisImpl } from '~/shared/mock/mockNotebookApis'; +import { MOCK_API_ENABLED } from '~/shared/utilities/const'; export type NotebookAPIState = APIState; -const MOCK_API_ENABLED = process.env.WEBPACK_REPLACE__mockApiEnabled === 'true'; - const useNotebookAPIState = ( hostPath: string | null, ): [apiState: NotebookAPIState, refreshAPIState: () => void] => { diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx index 449b49ce..47209804 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx @@ -15,8 +15,9 @@ import useGenericObjectState from '~/app/hooks/useGenericObjectState'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { WorkspaceKindFormData } from '~/app/types'; import { safeApiCall } from '~/shared/api/apiUtils'; -import { CONTENT_TYPE_KEY, ContentType } from '~/shared/utilities/const'; +import { CONTENT_TYPE_KEY } from '~/shared/utilities/const'; import { ApiValidationError, WorkspacekindsWorkspaceKind } from '~/generated/data-contracts'; +import { ContentType } from '~/shared/utilities/types'; import { WorkspaceKindFileUpload } from './fileUpload/WorkspaceKindFileUpload'; import { WorkspaceKindFormProperties } from './properties/WorkspaceKindFormProperties'; import { WorkspaceKindFormImage } from './image/WorkspaceKindFormImage'; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsImages.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsImages.tsx index 09e4cab3..1ed3e010 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsImages.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsImages.tsx @@ -23,7 +23,7 @@ export const WorkspaceKindDetailsImages: React.FunctionComponent { const [isSummaryExpanded, setIsSummaryExpanded] = useState(true); @@ -30,7 +30,7 @@ const WorkspaceKindSummary: React.FC = () => { podConfigId, }); - usePolling(refreshWorkspaces, DEFAULT_POLLING_RATE_MS); + usePolling(refreshWorkspaces, POLL_INTERVAL); const tableRowActions = useWorkspaceRowActions([{ id: 'viewDetails' }]); diff --git a/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx b/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx index 3e461396..39ffae42 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx @@ -5,12 +5,12 @@ import { Stack, StackItem } from '@patternfly/react-core/dist/esm/layouts/Stack' import WorkspaceTable from '~/app/components/WorkspaceTable'; import { useNamespaceContext } from '~/app/context/NamespaceContextProvider'; import { useWorkspacesByNamespace } from '~/app/hooks/useWorkspaces'; -import { DEFAULT_POLLING_RATE_MS } from '~/app/const'; import { LoadingSpinner } from '~/app/components/LoadingSpinner'; import { LoadError } from '~/app/components/LoadError'; import { useWorkspaceRowActions } from '~/app/hooks/useWorkspaceRowActions'; import { usePolling } from '~/app/hooks/usePolling'; import { WorkspacesWorkspaceState } from '~/generated/data-contracts'; +import { POLL_INTERVAL } from '~/shared/utilities/const'; export const Workspaces: React.FunctionComponent = () => { const { selectedNamespace } = useNamespaceContext(); @@ -18,7 +18,7 @@ export const Workspaces: React.FunctionComponent = () => { const [workspaces, workspacesLoaded, workspacesLoadError, refreshWorkspaces] = useWorkspacesByNamespace(selectedNamespace); - usePolling(refreshWorkspaces, DEFAULT_POLLING_RATE_MS); + usePolling(refreshWorkspaces, POLL_INTERVAL); const tableRowActions = useWorkspaceRowActions([ { id: 'viewDetails' }, diff --git a/workspaces/frontend/src/app/routerHelper.ts b/workspaces/frontend/src/app/routerHelper.ts index 332b473d..64446ff8 100644 --- a/workspaces/frontend/src/app/routerHelper.ts +++ b/workspaces/frontend/src/app/routerHelper.ts @@ -62,7 +62,7 @@ type NavigateOptions = CommonNavigateOptions & * Go to my route * */ -export function buildPath(to: T, params: RouteParamsMap[T]): string { +export function buildPath(to: T, params?: RouteParamsMap[T]): string { return generatePath(AppRoutePaths[to], params as RouteParamsMap[T]); } diff --git a/workspaces/frontend/src/images/logo.svg b/workspaces/frontend/src/images/logo-light-theme.svg similarity index 100% rename from workspaces/frontend/src/images/logo.svg rename to workspaces/frontend/src/images/logo-light-theme.svg diff --git a/workspaces/frontend/src/index.tsx b/workspaces/frontend/src/index.tsx index 3ad481ce..4c20fbdc 100644 --- a/workspaces/frontend/src/index.tsx +++ b/workspaces/frontend/src/index.tsx @@ -2,15 +2,15 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import { BrowserRouter as Router } from 'react-router-dom'; import { ThemeProvider, createTheme } from '@mui/material/styles'; +import { URL_PREFIX } from '~/shared/utilities/const'; import App from './app/App'; -import { APP_PREFIX } from './app/const'; const theme = createTheme({ cssVariables: true }); const root = ReactDOM.createRoot(document.getElementById('root')!); root.render( - + diff --git a/workspaces/frontend/src/shared/typeHelpers.ts b/workspaces/frontend/src/shared/typeHelpers.ts index a590b848..887c7114 100644 --- a/workspaces/frontend/src/shared/typeHelpers.ts +++ b/workspaces/frontend/src/shared/typeHelpers.ts @@ -158,3 +158,21 @@ type AtLeastOne }> = Partial & U[keyof U] * ``` */ export type ExactlyOne = AtMostOne & AtLeastOne; + +export const asEnumMember = ( + member: T[keyof T] | string | number | undefined | null, + e: T, +): T[keyof T] | null => (isEnumMember(member, e) ? member : null); + +export const isEnumMember = ( + member: T[keyof T] | string | number | undefined | unknown | null, + e: T, +): member is T[keyof T] => { + if (member != null) { + return Object.entries(e) + .filter(([key]) => Number.isNaN(Number(key))) + .map(([, value]) => value) + .includes(member); + } + return false; +}; diff --git a/workspaces/frontend/src/shared/utilities/const.ts b/workspaces/frontend/src/shared/utilities/const.ts index 1ddc2224..1fcd382a 100644 --- a/workspaces/frontend/src/shared/utilities/const.ts +++ b/workspaces/frontend/src/shared/utilities/const.ts @@ -1,8 +1,19 @@ +import { asEnumMember } from '~/shared/typeHelpers'; +import { DeploymentMode, Theme } from '~/shared/utilities/types'; + +export const STYLE_THEME = asEnumMember(process.env.STYLE_THEME, Theme) || Theme.MUI; +export const DEPLOYMENT_MODE = + asEnumMember(process.env.DEPLOYMENT_MODE, DeploymentMode) || DeploymentMode.Kubeflow; export const DEV_MODE = process.env.APP_ENV === 'development'; -export const AUTH_HEADER = process.env.AUTH_HEADER || 'kubeflow-userid'; +export const POLL_INTERVAL = process.env.POLL_INTERVAL + ? parseInt(process.env.POLL_INTERVAL) + : 30000; +export const LOGO_LIGHT = process.env.LOGO || 'logo-light-theme.svg'; +export const URL_PREFIX = process.env.URL_PREFIX ?? '/workspaces'; +export const BFF_API_PREFIX = process.env.BFF_API_PREFIX ?? '/'; +export const BFF_API_VERSION = 'v1'; +export const MOCK_API_ENABLED = process.env.MOCK_API_ENABLED === 'true'; export const CONTENT_TYPE_KEY = 'Content-Type'; -export enum ContentType { - YAML = 'application/yaml', -} +export const isMUITheme = (): boolean => STYLE_THEME === Theme.MUI; diff --git a/workspaces/frontend/src/shared/utilities/types.ts b/workspaces/frontend/src/shared/utilities/types.ts new file mode 100644 index 00000000..dab53f8a --- /dev/null +++ b/workspaces/frontend/src/shared/utilities/types.ts @@ -0,0 +1,14 @@ +export enum DeploymentMode { + Standalone = 'standalone', + Kubeflow = 'kubeflow', +} + +export enum Theme { + Default = 'default-theme', + MUI = 'mui-theme', + // Future themes can be added here +} + +export enum ContentType { + YAML = 'application/yaml', +} diff --git a/workspaces/frontend/tsconfig.json b/workspaces/frontend/tsconfig.json index 1a9726b8..406515c8 100644 --- a/workspaces/frontend/tsconfig.json +++ b/workspaces/frontend/tsconfig.json @@ -1,14 +1,13 @@ { "compilerOptions": { - "baseUrl": "./src", "rootDir": ".", "outDir": "dist", "module": "esnext", - "target": "ES6", - "lib": ["es6", "dom"], + "target": "es2021", + "lib": ["es2021", "dom", "ES2023.Array"], "sourceMap": true, "jsx": "react", - "moduleResolution": "node", + "moduleResolution": "bundler", "downlevelIteration": true, "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, @@ -18,12 +17,16 @@ "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, + "baseUrl": "./src", "paths": { "~/*": ["./*"] }, "importHelpers": true, - "skipLibCheck": true + "skipLibCheck": true, + "noErrorTruncation": true, + "noEmit": true, + "allowImportingTsExtensions": true }, "include": ["**/*.ts", "**/*.tsx", "**/*.jsx", "**/*.js"], - "exclude": ["node_modules", "src/__tests__/cypress"] + "exclude": ["node_modules", "dist", "public-cypress", "src/__tests__/cypress"] } From b210a5656fe35d8ad1ba058613c4a453b72af4b5 Mon Sep 17 00:00:00 2001 From: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> Date: Wed, 27 Aug 2025 10:18:21 -0300 Subject: [PATCH 64/68] feat: enhance husky pre-commit hook to conditionally run lint checks for frontend changes (#549) Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> --- workspaces/frontend/.husky/pre-commit | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/workspaces/frontend/.husky/pre-commit b/workspaces/frontend/.husky/pre-commit index ab36af88..89cdd194 100755 --- a/workspaces/frontend/.husky/pre-commit +++ b/workspaces/frontend/.husky/pre-commit @@ -1,3 +1,10 @@ echo "Running husky pre-commit hook..." -cd workspaces/frontend -npm run test:lint + +# Check if there are any staged changes in workspaces/frontend +if git diff --cached --name-only | grep -q "^workspaces/frontend/"; then + echo "Changes detected in workspaces/frontend, running lint check..." + cd workspaces/frontend + npm run test:lint +else + echo "No changes in workspaces/frontend, skipping lint check." +fi From 95431b482048026c83fa7351c296beb25af5c00f Mon Sep 17 00:00:00 2001 From: Marina Koushnir Date: Thu, 4 Sep 2025 22:04:12 +0300 Subject: [PATCH 65/68] feat(ws): frontend Makefile to support deploy (#534) * feat(ws): frontend Makefile to support deploy Signed-off-by: CI Bot * mathew: fix 1 Signed-off-by: Mathew Wicks <5735406+thesuperzapper@users.noreply.github.com> --------- Signed-off-by: CI Bot Signed-off-by: Mathew Wicks <5735406+thesuperzapper@users.noreply.github.com> Co-authored-by: Mathew Wicks <5735406+thesuperzapper@users.noreply.github.com> --- workspaces/frontend/.gitignore | 2 + workspaces/frontend/Makefile | 102 +++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100755 workspaces/frontend/Makefile diff --git a/workspaces/frontend/.gitignore b/workspaces/frontend/.gitignore index 574a1040..f059546c 100644 --- a/workspaces/frontend/.gitignore +++ b/workspaces/frontend/.gitignore @@ -7,3 +7,5 @@ coverage .idea .vscode/* !.vscode/settings.json +bin/* +Dockerfile.cross diff --git a/workspaces/frontend/Makefile b/workspaces/frontend/Makefile new file mode 100755 index 00000000..a7758ae5 --- /dev/null +++ b/workspaces/frontend/Makefile @@ -0,0 +1,102 @@ +# Image URL to use for building/pushing the frontend image +IMG ?= nbv2-frontend:latest + +# CONTAINER_TOOL defines the container tool to be used for building images. +# Tested with Docker by default. You may replace with podman. +CONTAINER_TOOL ?= docker + +# Setting SHELL to bash allows bash commands to be executed by recipes. +# Options are set to exit when a recipe line exits non-zero or a piped command fails. +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec + +.PHONY: all +all: help + +##@ General + +# The help target prints all targets with descriptions organized beneath categories. +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-18s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Clean + +.PHONY: clean +clean: ## Remove local test/build artifacts. + rm -rf ./bin ./Dockerfile.cross + +##@ Build + +# If you wish to build the image targeting other platforms you can use the --platform flag. +# (i.e. docker build --platform linux/arm64). Requires Docker BuildKit. +.PHONY: docker-build +docker-build: ## Build docker image for the frontend. + $(CONTAINER_TOOL) build -t ${IMG} . + +.PHONY: docker-push +docker-push: ## Push docker image for the frontend. + $(CONTAINER_TOOL) push ${IMG} + +# PLATFORMS defines the target platforms for cross-platform support. +PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le + +.PHONY: docker-buildx +docker-buildx: ## Build and push docker image for cross-platform support. + # copy existing Dockerfile and insert --platform=$${BUILDPLATFORM} into Dockerfile.cross, preserving the original + sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross + - $(CONTAINER_TOOL) buildx create --name project-v3-builder + $(CONTAINER_TOOL) buildx use project-v3-builder + - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . + - $(CONTAINER_TOOL) buildx rm project-v3-builder + rm Dockerfile.cross + +##@ Deployment + +# KUSTOMIZE_DIR point at a directory containing kustomization.yaml for the frontend deployment. +# Optionally set KUSTOMIZE_IMAGE_NAME (defaults to "frontend") to the image name key used in that kustomization. + +.PHONY: deploy +deploy: kustomize ## Deploy frontend to the K8s cluster specified in ~/.kube/config. + cd manifests/kustomize/overlays/istio && $(KUSTOMIZE) edit set image workspaces-frontend=${IMG} + $(KUBECTL) apply -k manifests/kustomize/overlays/istio + +.PHONY: undeploy +undeploy: kustomize ## Undeploy frontend from the K8s cluster specified in ~/.kube/config. + $(KUBECTL) delete -k manifests/kustomize/overlays/istio --ignore-not-found=true + + +##@ Dependencies + +# Location to install dependencies to +LOCALBIN ?= $(shell pwd)/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + +# Tool Binaries +KUBECTL ?= kubectl +KUSTOMIZE ?= $(LOCALBIN)/kustomize + +# Tool Versions +KUSTOMIZE_VERSION ?= v5.5.0 + +.PHONY: kustomize +kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. +$(KUSTOMIZE): $(LOCALBIN) + $(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION)) + +# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist +# $1 - target path with name of binary +# $2 - package url which can be installed +# $3 - specific version of package +define go-install-tool +@[ -f "$(1)-$(3)" ] || { \ +set -e; \ +package=$(2)@$(3) ;\ +echo "Downloading $${package}" ;\ +rm -f $(1) || true ;\ +GOBIN=$(LOCALBIN) go install $${package} ;\ +mv $(1) $(1)-$(3) ;\ +} ;\ +ln -sf $(1)-$(3) $(1) +endef From 253b25e477da6fd6760199fa6803b5a85ec0e634 Mon Sep 17 00:00:00 2001 From: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> Date: Thu, 4 Sep 2025 16:13:12 -0300 Subject: [PATCH 66/68] feat: integrate the frontend shared libraries (#552) Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com> --- workspaces/frontend/.env | 8 +- workspaces/frontend/.env.cypress.mock | 2 +- workspaces/frontend/.env.development | 1 + .../frontend/config/moduleFederation.js | 29 + workspaces/frontend/config/transform.file.js | 8 + workspaces/frontend/config/transform.style.js | 1 + workspaces/frontend/config/webpack.common.js | 3 + workspaces/frontend/config/webpack.dev.js | 5 + workspaces/frontend/config/webpack.prod.js | 1 + workspaces/frontend/jest.config.js | 6 +- workspaces/frontend/package-lock.json | 1871 ++++++++++++++++- workspaces/frontend/package.json | 4 + .../__tests__/cypress/cypress/pages/navBar.ts | 33 + .../cypress/tests/e2e/NamespaceSelector.cy.ts | 57 - .../cypress/tests/mocked/application.cy.ts | 26 +- .../tests/mocked/workspaces/Workspaces.cy.ts | 16 +- .../workspaces/filterWorkspacesTest.cy.ts | 6 +- .../cypress/cypress/webpack.config.ts | 6 +- workspaces/frontend/src/app/App.tsx | 127 +- workspaces/frontend/src/app/AppRoutes.tsx | 17 +- .../frontend/src/app/components/LoadError.tsx | 2 +- .../src/app/components/LoadingSpinner.tsx | 2 +- .../app/components/ThemeAwareSearchInput.tsx | 5 +- .../frontend/src/app/context/AppContext.tsx | 43 + .../src/app/context/BrowserStorageContext.tsx | 75 - .../app/context/NamespaceContextProvider.tsx | 99 +- .../src/app/context/NotebookContext.tsx | 24 +- .../app/context/WorkspaceActionsContext.tsx | 7 +- .../frontend/src/app/hooks/useNamespaces.ts | 5 +- .../src/app/hooks/useWorkspaceFormData.ts | 5 +- .../src/app/hooks/useWorkspaceKindByName.ts | 5 +- .../src/app/hooks/useWorkspaceKinds.ts | 5 +- .../frontend/src/app/hooks/useWorkspaces.ts | 5 +- .../WorkspaceKinds/Form/WorkspaceKindForm.tsx | 11 +- .../pages/WorkspaceKinds/WorkspaceKinds.tsx | 6 +- .../summary/WorkspaceKindSummaryWrapper.tsx | 2 +- .../pages/Workspaces/Form/WorkspaceForm.tsx | 13 +- .../Form/kind/WorkspaceFormKindSelection.tsx | 6 +- .../Workspaces/WorkspaceConfigDetails.tsx | 2 +- .../Workspaces/WorkspacePackageDetails.tsx | 2 +- .../app/pages/Workspaces/WorkspaceStorage.tsx | 2 +- .../src/app/pages/Workspaces/Workspaces.tsx | 2 +- .../pages/Workspaces/WorkspacesWrapper.tsx | 2 +- .../WorkspaceStartActionModal.tsx | 16 +- .../WorkspaceStopActionModal.tsx | 16 +- .../frontend/src/app/standalone/NavBar.tsx | 134 ++ .../src/app/{ => standalone}/NavSidebar.tsx | 15 +- .../src/app/standalone/ToastNotification.tsx | 52 + .../src/app/standalone/ToastNotifications.tsx | 18 + .../frontend/src/app/standalone/types.ts | 16 + .../frontend/src/app/theme-overrides.css | 4 + .../frontend/src/images/logo-dark-theme.svg | 43 - .../frontend/src/images/logo-light-theme.svg | 43 - workspaces/frontend/src/index.html | 38 +- workspaces/frontend/src/index.tsx | 36 +- .../shared/components/NamespaceSelector.tsx | 134 -- .../frontend/src/shared/style/MUI-theme.scss | 966 --------- workspaces/frontend/src/shared/typeHelpers.ts | 18 - .../frontend/src/shared/utilities/const.ts | 14 +- .../src/shared/utilities/useFetchState.ts | 258 --- 60 files changed, 2474 insertions(+), 1904 deletions(-) create mode 100644 workspaces/frontend/config/moduleFederation.js create mode 100644 workspaces/frontend/config/transform.file.js create mode 100644 workspaces/frontend/config/transform.style.js create mode 100644 workspaces/frontend/src/__tests__/cypress/cypress/pages/navBar.ts delete mode 100644 workspaces/frontend/src/__tests__/cypress/cypress/tests/e2e/NamespaceSelector.cy.ts create mode 100644 workspaces/frontend/src/app/context/AppContext.tsx delete mode 100644 workspaces/frontend/src/app/context/BrowserStorageContext.tsx create mode 100644 workspaces/frontend/src/app/standalone/NavBar.tsx rename workspaces/frontend/src/app/{ => standalone}/NavSidebar.tsx (86%) create mode 100644 workspaces/frontend/src/app/standalone/ToastNotification.tsx create mode 100644 workspaces/frontend/src/app/standalone/ToastNotifications.tsx create mode 100644 workspaces/frontend/src/app/standalone/types.ts create mode 100644 workspaces/frontend/src/app/theme-overrides.css delete mode 100644 workspaces/frontend/src/images/logo-dark-theme.svg delete mode 100644 workspaces/frontend/src/images/logo-light-theme.svg delete mode 100644 workspaces/frontend/src/shared/components/NamespaceSelector.tsx delete mode 100644 workspaces/frontend/src/shared/style/MUI-theme.scss delete mode 100644 workspaces/frontend/src/shared/utilities/useFetchState.ts diff --git a/workspaces/frontend/.env b/workspaces/frontend/.env index 112ddcad..1d21e59f 100644 --- a/workspaces/frontend/.env +++ b/workspaces/frontend/.env @@ -1,4 +1,8 @@ -LOGO=logo-light-theme.svg -LOGO_DARK=logo-dark-theme.svg +IS_PROJECT_ROOT_DIR=false +PORT=${FRONTEND_PORT} + +########## Change the following variables to customize the Dashboard ########## FAVICON=favicon.ico PRODUCT_NAME="Notebooks" +KUBEFLOW_USERNAME=user@example.com +COMPANY_URI=oci://kubeflow.io diff --git a/workspaces/frontend/.env.cypress.mock b/workspaces/frontend/.env.cypress.mock index b4c03de2..39acdc86 100644 --- a/workspaces/frontend/.env.cypress.mock +++ b/workspaces/frontend/.env.cypress.mock @@ -3,5 +3,5 @@ BASE_URL=http://localhost:9001 DEPLOYMENT_MODE=standalone POLL_INTERVAL=9999999 DIST_DIR=./dist -URL_PREFIX=/ +URL_PREFIX= PUBLIC_PATH=/ diff --git a/workspaces/frontend/.env.development b/workspaces/frontend/.env.development index 9fc5aa7a..c1a92ed3 100644 --- a/workspaces/frontend/.env.development +++ b/workspaces/frontend/.env.development @@ -1,3 +1,4 @@ APP_ENV=development DEPLOYMENT_MODE=standalone MOCK_API_ENABLED=true +MANDATORY_NAMESPACE=workspace-test-1 diff --git a/workspaces/frontend/config/moduleFederation.js b/workspaces/frontend/config/moduleFederation.js new file mode 100644 index 00000000..cc39dc84 --- /dev/null +++ b/workspaces/frontend/config/moduleFederation.js @@ -0,0 +1,29 @@ +const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack'); + +const deps = require('../package.json').dependencies; + +const moduleFederationConfig = { + name: 'notebooks', + filename: 'remoteEntry.js', + + shared: { + react: { singleton: true, eager: true, requiredVersion: deps.react }, + 'react-dom': { singleton: true, eager: true, requiredVersion: deps['react-dom'] }, + 'react-router': { singleton: true, eager: true, requiredVersion: deps['react-router'] }, + 'react-router-dom': { singleton: true, eager: true, requiredVersion: deps['react-router-dom'] }, + }, + exposes: { + // TODO expose api. eg: + // './index': './src/plugin/index.tsx', + // './plugin': './src/plugin/index.tsx', + }, + // For module federation to work when optimization.runtimeChunk="single": + // See https://github.com/webpack/webpack/issues/18810 + runtime: false, + // TODO generate types when exposing api + dts: false, +}; + +module.exports = { + moduleFederationPlugins: [new ModuleFederationPlugin(moduleFederationConfig)], +}; diff --git a/workspaces/frontend/config/transform.file.js b/workspaces/frontend/config/transform.file.js new file mode 100644 index 00000000..7ee8755d --- /dev/null +++ b/workspaces/frontend/config/transform.file.js @@ -0,0 +1,8 @@ +const path = require('path'); + +module.exports = { + process(_src, filename) { + const assetFilename = JSON.stringify(path.basename(filename)); + return `module.exports = ${assetFilename};`; + }, +}; diff --git a/workspaces/frontend/config/transform.style.js b/workspaces/frontend/config/transform.style.js new file mode 100644 index 00000000..f053ebf7 --- /dev/null +++ b/workspaces/frontend/config/transform.style.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/workspaces/frontend/config/webpack.common.js b/workspaces/frontend/config/webpack.common.js index f772c659..d0c682ca 100644 --- a/workspaces/frontend/config/webpack.common.js +++ b/workspaces/frontend/config/webpack.common.js @@ -4,6 +4,8 @@ const CopyPlugin = require('copy-webpack-plugin'); const { setupWebpackDotenvFilesForEnv } = require('./dotenv'); const { name } = require('../package.json'); +const { moduleFederationPlugins } = require('./moduleFederation'); + const RELATIVE_DIRNAME = process.env._RELATIVE_DIRNAME; const IS_PROJECT_ROOT_DIR = process.env._IS_PROJECT_ROOT_DIR; const IMAGES_DIRNAME = process.env._IMAGES_DIRNAME; @@ -180,6 +182,7 @@ module.exports = (env) => ({ uniqueName: name, }, plugins: [ + ...moduleFederationPlugins, ...setupWebpackDotenvFilesForEnv({ directory: RELATIVE_DIRNAME, isRoot: IS_PROJECT_ROOT_DIR, diff --git a/workspaces/frontend/config/webpack.dev.js b/workspaces/frontend/config/webpack.dev.js index d3e00a9e..777d55e1 100644 --- a/workspaces/frontend/config/webpack.dev.js +++ b/workspaces/frontend/config/webpack.dev.js @@ -100,6 +100,7 @@ module.exports = smp.wrap( ], devMiddleware: { stats: 'errors-only', + publicPath: BASE_PATH, }, client: { overlay: false, @@ -127,6 +128,10 @@ module.exports = smp.wrap( SRC_DIR, COMMON_DIR, path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly'), + path.resolve( + RELATIVE_DIRNAME, + 'node_modules/mod-arch-shared/node_modules/@patternfly', + ), ], use: ['style-loader', 'css-loader'], }, diff --git a/workspaces/frontend/config/webpack.prod.js b/workspaces/frontend/config/webpack.prod.js index a14ef40f..4746bb8e 100644 --- a/workspaces/frontend/config/webpack.prod.js +++ b/workspaces/frontend/config/webpack.prod.js @@ -52,6 +52,7 @@ module.exports = merge( SRC_DIR, COMMON_DIR, path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly'), + path.resolve(RELATIVE_DIRNAME, 'node_modules/mod-arch-shared/node_modules/@patternfly'), ], use: [MiniCssExtractPlugin.loader, 'css-loader'], }, diff --git a/workspaces/frontend/jest.config.js b/workspaces/frontend/jest.config.js index 01c94bcd..37fbce0f 100644 --- a/workspaces/frontend/jest.config.js +++ b/workspaces/frontend/jest.config.js @@ -26,15 +26,15 @@ module.exports = { testEnvironment: 'jest-environment-jsdom', // include projects from node_modules as required - transformIgnorePatterns: ['node_modules/(?!yaml|lodash-es|uuid|@patternfly|delaunator)'], + transformIgnorePatterns: [ + 'node_modules/(?!yaml|lodash-es|uuid|@patternfly|delaunator|mod-arch-shared|mod-arch-core|mod-arch-kubeflow)', + ], // A list of paths to snapshot serializer modules Jest should use for snapshot testing snapshotSerializers: [], setupFilesAfterEnv: ['/src/__tests__/unit/jest.setup.ts'], - preset: 'ts-jest', - coverageDirectory: 'jest-coverage', collectCoverageFrom: [ diff --git a/workspaces/frontend/package-lock.json b/workspaces/frontend/package-lock.json index 9ad96a67..5178a674 100644 --- a/workspaces/frontend/package-lock.json +++ b/workspaces/frontend/package-lock.json @@ -27,6 +27,9 @@ "dompurify": "^3.2.4", "js-yaml": "^4.1.0", "lodash-es": "^4.17.15", + "mod-arch-core": "^1.1.0", + "mod-arch-kubeflow": "^1.1.0", + "mod-arch-shared": "^1.1.0", "react": "^18", "react-dom": "^18", "react-router": "^7.5.2", @@ -35,6 +38,7 @@ "showdown": "^2.1.0" }, "devDependencies": { + "@module-federation/enhanced": "^0.13.1", "@mui/icons-material": "^6.4.8", "@mui/material": "^6.3.1", "@mui/types": "^7.2.21", @@ -3044,6 +3048,358 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -4169,6 +4525,552 @@ "dev": true, "license": "MIT" }, + "node_modules/@modern-js/node-bundle-require": { + "version": "2.65.1", + "resolved": "https://registry.npmjs.org/@modern-js/node-bundle-require/-/node-bundle-require-2.65.1.tgz", + "integrity": "sha512-XpEkciVEfDbkkLUI662ZFlI9tXsUQtLXk4NRJDBGosNnk9uL2XszmC8sKsdCSLK8AYuPW2w6MTVWuJsOR0EU8A==", + "dev": true, + "dependencies": { + "@modern-js/utils": "2.65.1", + "@swc/helpers": "0.5.13", + "esbuild": "0.17.19" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@swc/helpers": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.13.tgz", + "integrity": "sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@modern-js/utils": { + "version": "2.65.1", + "resolved": "https://registry.npmjs.org/@modern-js/utils/-/utils-2.65.1.tgz", + "integrity": "sha512-HrChf19F+6nALo5XPra8ycjhXGQfGi23+S7Y2FLfTKe8vaNnky8duT/XvRWpbS4pp3SQj8ryO8m/qWSsJ1Rogw==", + "dev": true, + "dependencies": { + "@swc/helpers": "0.5.13", + "caniuse-lite": "^1.0.30001520", + "lodash": "^4.17.21", + "rslog": "^1.1.0" + } + }, + "node_modules/@modern-js/utils/node_modules/@swc/helpers": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.13.tgz", + "integrity": "sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@module-federation/bridge-react-webpack-plugin": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@module-federation/bridge-react-webpack-plugin/-/bridge-react-webpack-plugin-0.13.1.tgz", + "integrity": "sha512-3RgGd8KcRw5vibnxWa1NUWwfb0tKwn8OvHeQ4GFKzMvDLm+QpCgQd9LeTEBP38wZgGXVtIJR3y5FPnufWswFKw==", + "dev": true, + "dependencies": { + "@module-federation/sdk": "0.13.1", + "@types/semver": "7.5.8", + "semver": "7.6.3" + } + }, + "node_modules/@module-federation/bridge-react-webpack-plugin/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@module-federation/cli": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@module-federation/cli/-/cli-0.13.1.tgz", + "integrity": "sha512-ej7eZTVUiRMor37pkl2y3hbXwcaNvPgbZJVO+hb2c7cKBjWto7AndgR5qcKpcXXXlhbGwtnI+VrgldruKC+AqQ==", + "dev": true, + "dependencies": { + "@modern-js/node-bundle-require": "2.65.1", + "@module-federation/dts-plugin": "0.13.1", + "@module-federation/sdk": "0.13.1", + "chalk": "3.0.0", + "commander": "11.1.0" + }, + "bin": { + "mf": "bin/mf.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@module-federation/cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@module-federation/cli/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@module-federation/cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@module-federation/cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@module-federation/cli/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@module-federation/cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@module-federation/cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@module-federation/data-prefetch": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@module-federation/data-prefetch/-/data-prefetch-0.13.1.tgz", + "integrity": "sha512-hj3R72rRyune4fb4V4OFmo1Rfa9T9u0so2Q4vt69frPc2NV2FPPJkIvHGs/geGTLOgt4nn7OH1/ukmR3wWvSuA==", + "dev": true, + "dependencies": { + "@module-federation/runtime": "0.13.1", + "@module-federation/sdk": "0.13.1", + "fs-extra": "9.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@module-federation/dts-plugin": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@module-federation/dts-plugin/-/dts-plugin-0.13.1.tgz", + "integrity": "sha512-PQMs57h9s5pCkLWZ0IyDGCcac4VZ+GgJE40pAWrOQ+/AgTC+WFyAT16M7PsRENS57Qed4wWQwgfOjS9zmfxKJA==", + "dev": true, + "dependencies": { + "@module-federation/error-codes": "0.13.1", + "@module-federation/managers": "0.13.1", + "@module-federation/sdk": "0.13.1", + "@module-federation/third-party-dts-extractor": "0.13.1", + "adm-zip": "^0.5.10", + "ansi-colors": "^4.1.3", + "axios": "^1.8.2", + "chalk": "3.0.0", + "fs-extra": "9.1.0", + "isomorphic-ws": "5.0.0", + "koa": "2.16.1", + "lodash.clonedeepwith": "4.5.0", + "log4js": "6.9.1", + "node-schedule": "2.1.1", + "rambda": "^9.1.0", + "ws": "8.18.0" + }, + "peerDependencies": { + "typescript": "^4.9.0 || ^5.0.0", + "vue-tsc": ">=1.0.24" + }, + "peerDependenciesMeta": { + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/@module-federation/dts-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@module-federation/dts-plugin/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@module-federation/dts-plugin/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@module-federation/dts-plugin/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@module-federation/dts-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@module-federation/dts-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@module-federation/dts-plugin/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "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/@module-federation/enhanced": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@module-federation/enhanced/-/enhanced-0.13.1.tgz", + "integrity": "sha512-jbbk68RnvNmusGGcXNXVDJAzJOFB/hV+RVV2wWNWmBOVkDZPiWj7aFb0cJAwc9EYZbPel3QzRitZJ73+SaH1IA==", + "dev": true, + "dependencies": { + "@module-federation/bridge-react-webpack-plugin": "0.13.1", + "@module-federation/cli": "0.13.1", + "@module-federation/data-prefetch": "0.13.1", + "@module-federation/dts-plugin": "0.13.1", + "@module-federation/error-codes": "0.13.1", + "@module-federation/inject-external-runtime-core-plugin": "0.13.1", + "@module-federation/managers": "0.13.1", + "@module-federation/manifest": "0.13.1", + "@module-federation/rspack": "0.13.1", + "@module-federation/runtime-tools": "0.13.1", + "@module-federation/sdk": "0.13.1", + "btoa": "^1.2.1", + "schema-utils": "^4.3.0", + "upath": "2.0.1" + }, + "bin": { + "mf": "bin/mf.js" + }, + "peerDependencies": { + "typescript": "^4.9.0 || ^5.0.0", + "vue-tsc": ">=1.0.24", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue-tsc": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/@module-federation/error-codes": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.13.1.tgz", + "integrity": "sha512-azgGDBnFRfqlivHOl96ZjlFUFlukESz2Rnnz/pINiSqoBBNjUE0fcAZP4X6jgrVITuEg90YkruZa7pW9I3m7Uw==", + "dev": true + }, + "node_modules/@module-federation/inject-external-runtime-core-plugin": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@module-federation/inject-external-runtime-core-plugin/-/inject-external-runtime-core-plugin-0.13.1.tgz", + "integrity": "sha512-K+ltl2AqVqlsvEds1PffCMLDMlC5lvdkyMXOfcZO6u0O4dZlaTtZbT32NchY7kIEvEsj0wyYhX1i2DnsbHpUBw==", + "dev": true, + "peerDependencies": { + "@module-federation/runtime-tools": "0.13.1" + } + }, + "node_modules/@module-federation/managers": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@module-federation/managers/-/managers-0.13.1.tgz", + "integrity": "sha512-vQMrqSFQxjSuGgByC2wcY7zUTmVfhzCyDpnCCq0PtaozK8DcgwsEMzrAT3dbg8ifGUmse/xiRIbTmS5leKK+UQ==", + "dev": true, + "dependencies": { + "@module-federation/sdk": "0.13.1", + "find-pkg": "2.0.0", + "fs-extra": "9.1.0" + } + }, + "node_modules/@module-federation/manifest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@module-federation/manifest/-/manifest-0.13.1.tgz", + "integrity": "sha512-XcuFtLycoR0jQj8op+w20V5n459blNBvGXe//AwkEppQERk8SM5kQgIPvOVbZ8zGx7tl/F2HGTDVZlhDiKzIew==", + "dev": true, + "dependencies": { + "@module-federation/dts-plugin": "0.13.1", + "@module-federation/managers": "0.13.1", + "@module-federation/sdk": "0.13.1", + "chalk": "3.0.0", + "find-pkg": "2.0.0" + } + }, + "node_modules/@module-federation/manifest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@module-federation/manifest/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@module-federation/manifest/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@module-federation/manifest/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@module-federation/manifest/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@module-federation/manifest/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@module-federation/rspack": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@module-federation/rspack/-/rspack-0.13.1.tgz", + "integrity": "sha512-+qz8sW99SYDULajjjn4rSNaI4rogEPVOZsBvT6y0PdfpMD/wZxvh5HlV0u7+5DgWEjgrdm0cJHBHChlIbV/CMQ==", + "dev": true, + "dependencies": { + "@module-federation/bridge-react-webpack-plugin": "0.13.1", + "@module-federation/dts-plugin": "0.13.1", + "@module-federation/inject-external-runtime-core-plugin": "0.13.1", + "@module-federation/managers": "0.13.1", + "@module-federation/manifest": "0.13.1", + "@module-federation/runtime-tools": "0.13.1", + "@module-federation/sdk": "0.13.1", + "btoa": "1.2.1" + }, + "peerDependencies": { + "@rspack/core": ">=0.7", + "typescript": "^4.9.0 || ^5.0.0", + "vue-tsc": ">=1.0.24" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/@module-federation/runtime": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.13.1.tgz", + "integrity": "sha512-ZHnYvBquDm49LiHfv6fgagMo/cVJneijNJzfPh6S0CJrPS2Tay1bnTXzy8VA5sdIrESagYPaskKMGIj7YfnPug==", + "dev": true, + "dependencies": { + "@module-federation/error-codes": "0.13.1", + "@module-federation/runtime-core": "0.13.1", + "@module-federation/sdk": "0.13.1" + } + }, + "node_modules/@module-federation/runtime-core": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.13.1.tgz", + "integrity": "sha512-TfyKfkSAentKeuvSsAItk8s5tqQSMfIRTPN2e1aoaq/kFhE+7blps719csyWSX5Lg5Es7WXKMsXHy40UgtBtuw==", + "dev": true, + "dependencies": { + "@module-federation/error-codes": "0.13.1", + "@module-federation/sdk": "0.13.1" + } + }, + "node_modules/@module-federation/runtime-tools": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.13.1.tgz", + "integrity": "sha512-GEF1pxqLc80osIMZmE8j9UKZSaTm2hX2lql8tgIH/O9yK4wnF06k6LL5Ah+wJt+oJv6Dj55ri/MoxMP4SXoPNA==", + "dev": true, + "dependencies": { + "@module-federation/runtime": "0.13.1", + "@module-federation/webpack-bundler-runtime": "0.13.1" + } + }, + "node_modules/@module-federation/sdk": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.13.1.tgz", + "integrity": "sha512-bmf2FGQ0ymZuxYnw9bIUfhV3y6zDhaqgydEjbl4msObKMLGXZqhse2pTIIxBFpIxR1oONKX/y2FAolDCTlWKiw==", + "dev": true + }, + "node_modules/@module-federation/third-party-dts-extractor": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@module-federation/third-party-dts-extractor/-/third-party-dts-extractor-0.13.1.tgz", + "integrity": "sha512-0kWSupoC0aTxFjJZE5TVPNsoZ9kBsZhkvRxFnUW2vDYLgtvgs2dIrDlNlIXYiS/MaQCNHGyvdNepbchKQiwFaw==", + "dev": true, + "dependencies": { + "find-pkg": "2.0.0", + "fs-extra": "9.1.0", + "resolve": "1.22.8" + } + }, + "node_modules/@module-federation/third-party-dts-extractor/node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@module-federation/webpack-bundler-runtime": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.13.1.tgz", + "integrity": "sha512-QSuSIGa09S8mthbB1L6xERqrz+AzPlHR6D7RwAzssAc+IHf40U6NiTLPzUqp9mmKDhC5Tm0EISU0ZHNeJpnpBQ==", + "dev": true, + "dependencies": { + "@module-federation/runtime": "0.13.1", + "@module-federation/sdk": "0.13.1" + } + }, "node_modules/@monaco-editor/loader": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.4.0.tgz", @@ -4199,7 +5101,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.5.0.tgz", "integrity": "sha512-LGb8t8i6M2ZtS3Drn3GbTI1DVhDY6FJ9crEey2lZ0aN2EMZo8IZBZj9wRf4vqbZHaWjsYgtbOnJw5V8UWbmK2Q==", - "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" @@ -4235,7 +5136,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.5.0.tgz", "integrity": "sha512-yjvtXoFcrPLGtgKRxFaH6OQPtcLPhkloC0BML6rBG5UeldR0nPULR/2E2BfXdo5JNV7j7lOzrrLX2Qf/iSidow==", - "dev": true, "dependencies": { "@babel/runtime": "^7.26.0", "@mui/core-downloads-tracker": "^6.5.0", @@ -4283,14 +5183,12 @@ "node_modules/@mui/material/node_modules/react-is": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", - "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", - "dev": true + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==" }, "node_modules/@mui/private-theming": { "version": "6.4.9", "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.9.tgz", "integrity": "sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==", - "dev": true, "dependencies": { "@babel/runtime": "^7.26.0", "@mui/utils": "^6.4.9", @@ -4317,7 +5215,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.5.0.tgz", "integrity": "sha512-8woC2zAqF4qUDSPIBZ8v3sakj+WgweolpyM/FXf8jAx6FMls+IE4Y8VDZc+zS805J7PRz31vz73n2SovKGaYgw==", - "dev": true, "dependencies": { "@babel/runtime": "^7.26.0", "@emotion/cache": "^11.13.5", @@ -4351,7 +5248,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.5.0.tgz", "integrity": "sha512-XcbBYxDS+h/lgsoGe78ExXFZXtuIlSBpn/KsZq8PtZcIkUNJInkuDqcLd2rVBQrDC1u+rvVovdaWPf2FHKJf3w==", - "dev": true, "dependencies": { "@babel/runtime": "^7.26.0", "@mui/private-theming": "^6.4.9", @@ -4391,7 +5287,6 @@ "version": "7.2.24", "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", - "dev": true, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -4405,7 +5300,6 @@ "version": "6.4.9", "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.9.tgz", "integrity": "sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==", - "dev": true, "dependencies": { "@babel/runtime": "^7.26.0", "@mui/types": "~7.2.24", @@ -4434,8 +5328,7 @@ "node_modules/@mui/utils/node_modules/react-is": { "version": "19.1.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz", - "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==", - "dev": true + "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==" }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", @@ -5035,12 +5928,275 @@ "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" } }, + "node_modules/@rspack/binding": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.5.0.tgz", + "integrity": "sha512-UGXQmwEu2gdO+tnGv2q4rOWJdWioy6dlLXeZOLYAZVh3mrfKJhZWtDEygX9hCdE5thWNRTlEvx30QQchJAszIQ==", + "dev": true, + "peer": true, + "optionalDependencies": { + "@rspack/binding-darwin-arm64": "1.5.0", + "@rspack/binding-darwin-x64": "1.5.0", + "@rspack/binding-linux-arm64-gnu": "1.5.0", + "@rspack/binding-linux-arm64-musl": "1.5.0", + "@rspack/binding-linux-x64-gnu": "1.5.0", + "@rspack/binding-linux-x64-musl": "1.5.0", + "@rspack/binding-wasm32-wasi": "1.5.0", + "@rspack/binding-win32-arm64-msvc": "1.5.0", + "@rspack/binding-win32-ia32-msvc": "1.5.0", + "@rspack/binding-win32-x64-msvc": "1.5.0" + } + }, + "node_modules/@rspack/binding-darwin-arm64": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.5.0.tgz", + "integrity": "sha512-7909YLNnKf0BYxiCpCWOk13WyWS4493Kxk1NQwy9KPLY9ydQExk84KVsix2NuNBaI8Pnk3aVLBPJiSNXtHLjnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rspack/binding-darwin-x64": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.5.0.tgz", + "integrity": "sha512-poGuQsGKCMQqSswgrz8X+frqMVTdmtzUDyvi/p9BLwW+2DwWgmywU8jwE+BYtjfWp1tErBSTlLxmEPQTdcIQgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rspack/binding-linux-arm64-gnu": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.5.0.tgz", + "integrity": "sha512-Bvmk8h3tRhN9UgOtH+vK0SgFM3qEO36eJz7oddOl4lJQxBf2GNA87bGtkMtX+AVPz/PUn7r82uWxrlVNQHAbFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rspack/binding-linux-arm64-musl": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.5.0.tgz", + "integrity": "sha512-bH7UwkbACDYT37YnN9kkhaF9niFFK9ndcdNvYFFr1oUT4W9Ie3V9b41EXijqp3pyh0mDSeeLPFY0aEx1t3e7Pw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rspack/binding-linux-x64-gnu": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.5.0.tgz", + "integrity": "sha512-xZ5dwNrE5KtpQyMd9israpJTcTQ3UYUUq23fTcNc79xE5aspkGixDFAYoql4YkhO0O+JWRmdSaFAn6jD+IQWQA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rspack/binding-linux-x64-musl": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.5.0.tgz", + "integrity": "sha512-mv65jYvcyYPkPZJ9kjSvTAcH0o7C5jfICWCQcMmN1tCGD3b8gmf9GqSZ8e+W/JkuvrJ05qTo/PvEq9nhu+pNIg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rspack/binding-wasm32-wasi": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.5.0.tgz", + "integrity": "sha512-8rVpl6xfaAFJgo1wCd+emksfl+/8nlehrtkmjY9bj79Ou+kp07L9e1B+UU0jfs8e7aLPntQuF68kzLHwYLzWIQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.1" + } + }, + "node_modules/@rspack/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.3.tgz", + "integrity": "sha512-rZxtMsLwjdXkMUGC3WwsPwLNVqVqnTJT6MNIB6e+5fhMcSCPP0AOsNWuMQ5mdCq6HNjs/ZeWAEchpqeprqBD2Q==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/core": "^1.4.5", + "@emnapi/runtime": "^1.4.5", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@rspack/binding-win32-arm64-msvc": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.5.0.tgz", + "integrity": "sha512-dWSmNm+GR6WSkOwbhlUcot4Oqwyon+1PRZ9E0vIMFHKGvESf9CQjgHAX0QE9G0kJmRM5x3I16J4x44Kw3W/98Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rspack/binding-win32-ia32-msvc": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.5.0.tgz", + "integrity": "sha512-YtOrFEkwhO3Y3sY6Jq0OOYPY7NBTNYuwJ6epTgzPEDGs2cBnwZfzhq0jmD/koWtv1L9+twX95vKosBdauF0tNA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rspack/binding-win32-x64-msvc": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.5.0.tgz", + "integrity": "sha512-V4fcPVYWJgDkIkSsFwmUdwC9lkL8+1dzDOwyTWe6KW2MYHF2D148WPHNyVVE6gum12TShpbIsh0j4NiiMhkMtw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rspack/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.5.0.tgz", + "integrity": "sha512-eEtiKV+CUcAtnt1K+eiHDzmBXQcNM8CfCXOzr0+gHGp4w4Zks2B8RF36sYD03MM2bg8VRXXsf0MicQ8FvRMCOg==", + "dev": true, + "peer": true, + "dependencies": { + "@module-federation/runtime-tools": "0.18.0", + "@rspack/binding": "1.5.0", + "@rspack/lite-tapable": "1.0.1" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.1" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@rspack/core/node_modules/@module-federation/error-codes": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.18.0.tgz", + "integrity": "sha512-Woonm8ehyVIUPXChmbu80Zj6uJkC0dD9SJUZ/wOPtO8iiz/m+dkrOugAuKgoiR6qH4F+yorWila954tBz4uKsQ==", + "dev": true, + "peer": true + }, + "node_modules/@rspack/core/node_modules/@module-federation/runtime": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.18.0.tgz", + "integrity": "sha512-+C4YtoSztM7nHwNyZl6dQKGUVJdsPrUdaf3HIKReg/GQbrt9uvOlUWo2NXMZ8vDAnf/QRrpSYAwXHmWDn9Obaw==", + "dev": true, + "peer": true, + "dependencies": { + "@module-federation/error-codes": "0.18.0", + "@module-federation/runtime-core": "0.18.0", + "@module-federation/sdk": "0.18.0" + } + }, + "node_modules/@rspack/core/node_modules/@module-federation/runtime-core": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.18.0.tgz", + "integrity": "sha512-ZyYhrDyVAhUzriOsVfgL6vwd+5ebYm595Y13KeMf6TKDRoUHBMTLGQ8WM4TDj8JNsy7LigncK8C03fn97of0QQ==", + "dev": true, + "peer": true, + "dependencies": { + "@module-federation/error-codes": "0.18.0", + "@module-federation/sdk": "0.18.0" + } + }, + "node_modules/@rspack/core/node_modules/@module-federation/runtime-tools": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.18.0.tgz", + "integrity": "sha512-fSga9o4t1UfXNV/Kh6qFvRyZpPp3EHSPRISNeyT8ZoTpzDNiYzhtw0BPUSSD8m6C6XQh2s/11rI4g80UY+d+hA==", + "dev": true, + "peer": true, + "dependencies": { + "@module-federation/runtime": "0.18.0", + "@module-federation/webpack-bundler-runtime": "0.18.0" + } + }, + "node_modules/@rspack/core/node_modules/@module-federation/sdk": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.18.0.tgz", + "integrity": "sha512-Lo/Feq73tO2unjmpRfyyoUkTVoejhItXOk/h5C+4cistnHbTV8XHrW/13fD5e1Iu60heVdAhhelJd6F898Ve9A==", + "dev": true, + "peer": true + }, + "node_modules/@rspack/core/node_modules/@module-federation/webpack-bundler-runtime": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.18.0.tgz", + "integrity": "sha512-TEvErbF+YQ+6IFimhUYKK3a5wapD90d90sLsNpcu2kB3QGT7t4nIluE25duXuZDVUKLz86tEPrza/oaaCWTpvQ==", + "dev": true, + "peer": true, + "dependencies": { + "@module-federation/runtime": "0.18.0", + "@module-federation/sdk": "0.18.0" + } + }, + "node_modules/@rspack/lite-tapable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.0.1.tgz", + "integrity": "sha512-VynGOEsVw2s8TAlLf/uESfrgfrq2+rcXB1muPJYBWbsm1Oa6r5qVQhjA5ggM6z/coYPrsVMgovl3Ff7Q7OCp1w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -5306,6 +6462,17 @@ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", "dev": true }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@swc/types": { "version": "0.1.24", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.24.tgz", @@ -5902,8 +7069,7 @@ "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "devOptional": true + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==" }, "node_modules/@types/qs": { "version": "6.9.5", @@ -5921,7 +7087,6 @@ "version": "18.3.23", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", - "devOptional": true, "peer": true, "dependencies": { "@types/prop-types": "*", @@ -5941,7 +7106,6 @@ "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==", - "dev": true, "peerDependencies": { "@types/react": "*" } @@ -5953,6 +7117,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", @@ -6079,7 +7249,7 @@ "version": "8.40.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.40.0.tgz", "integrity": "sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==", - "dev": true, + "devOptional": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.40.0", @@ -6108,7 +7278,7 @@ "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 4" } @@ -6195,7 +7365,7 @@ "version": "8.40.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.40.0.tgz", "integrity": "sha512-eE60cK4KzAc6ZrzlJnflXdrMqOBaugeukWICO2rB0KNvwdIMaEaYiywwHMzA1qFpTxrLhN9Lp4E/00EgWcD3Ow==", - "dev": true, + "devOptional": true, "dependencies": { "@typescript-eslint/types": "8.40.0", "@typescript-eslint/typescript-estree": "8.40.0", @@ -6296,7 +7466,7 @@ "version": "8.40.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.40.0.tgz", "integrity": "sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==", - "dev": true, + "devOptional": true, "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.40.0", @@ -6861,6 +8031,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/adm-zip": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", + "dev": true, + "engines": { + "node": ">=12.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -6965,8 +8144,8 @@ "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==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">=6" } @@ -7351,8 +8530,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "devOptional": true, "license": "ISC", - "optional": true, "engines": { "node": ">= 4.0.0" } @@ -8042,6 +9221,18 @@ "node-int64": "^0.4.0" } }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "dev": true, + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -8177,6 +9368,19 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dev": true, + "dependencies": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/cachedir": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", @@ -8763,7 +9967,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "dev": true, "engines": { "node": ">=6" } @@ -9167,6 +10370,28 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, + "node_modules/cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", + "dev": true, + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cookies/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/copy-webpack-plugin": { "version": "13.0.1", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.1.tgz", @@ -9349,6 +10574,18 @@ "node": ">=8" } }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "dev": true, + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -10350,6 +11587,15 @@ "url": "https://github.com/sponsors/kossnocorp" } }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/dateformat": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", @@ -10420,6 +11666,12 @@ } } }, + "node_modules/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==", + "dev": true + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -10552,6 +11804,12 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, "node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -10690,7 +11948,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "dev": true, "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" @@ -11235,6 +12492,43 @@ "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", "dev": true }, + "node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -12167,6 +13461,18 @@ "node": ">= 0.8.0" } }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/expect": { "version": "30.0.5", "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.5.tgz", @@ -13013,6 +14319,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-file-up": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-2.0.1.tgz", + "integrity": "sha512-qVdaUhYO39zmh28/JLQM5CoYN9byEOKEH4qfa8K1eNV17W0UUMJ9WgbR/hHFH+t5rcl+6RTb5UC7ck/I+uRkpQ==", + "dev": true, + "dependencies": { + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-2.0.0.tgz", + "integrity": "sha512-WgZ+nKbELDa6N3i/9nrHeNznm+lY3z4YfhDDWgW+5P0pdmMj26bxaxU11ookgY3NyP9GC7HvZ9etp0jRFqGEeQ==", + "dev": true, + "dependencies": { + "find-file-up": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -13054,9 +14384,9 @@ } }, "node_modules/flatted": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", - "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "devOptional": true }, "node_modules/focus-trap": { @@ -13378,8 +14708,8 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -13689,6 +15019,54 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", @@ -13919,6 +15297,18 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -14110,6 +15500,35 @@ "entities": "^2.0.0" } }, + "node_modules/http-assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", + "dev": true, + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-assert/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", @@ -14801,7 +16220,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", - "optional": true, + "devOptional": true, "dependencies": { "call-bound": "^1.0.3", "get-proto": "^1.0.0", @@ -15004,7 +16423,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "optional": true, + "devOptional": true, "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", @@ -15173,8 +16592,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">=0.10.0" } @@ -15212,6 +16631,15 @@ "node": ">=0.10.0" } }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "dev": true, + "peerDependencies": { + "ws": "*" + } + }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -17371,6 +18799,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dev": true, + "dependencies": { + "tsscmp": "1.0.6" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -17389,6 +18829,102 @@ "node": ">=6" } }, + "node_modules/koa": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.1.tgz", + "integrity": "sha512-umfX9d3iuSxTQP4pnzLOz0HKnPg0FaUUIKcye2lOiz3KPu1Y3M3xlz76dISdFPQs37P9eJz1wUpcTS6KDPn9fA==", + "dev": true, + "dependencies": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.9.0", + "debug": "^4.3.2", + "delegates": "^1.0.0", + "depd": "^2.0.0", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^2.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "engines": { + "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" + } + }, + "node_modules/koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true + }, + "node_modules/koa-convert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", + "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", + "dev": true, + "dependencies": { + "co": "^4.6.0", + "koa-compose": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/koa/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/koa/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/koa/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", @@ -17573,6 +19109,12 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, + "node_modules/lodash.clonedeepwith": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeepwith/-/lodash.clonedeepwith-4.5.0.tgz", + "integrity": "sha512-QRBRSxhbtsX1nc0baxSkkK5WlVTTm/s48DSukcGcWZwIyI8Zz+lB+kFiELJXtzfH4Aj6kMWQ1VWW4U5uUDgZMA==", + "dev": true + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -17820,6 +19362,28 @@ "node": ">=8" } }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==", + "dev": true + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -17852,6 +19416,15 @@ "node": ">=10" } }, + "node_modules/luxon": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.1.tgz", + "integrity": "sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -18811,6 +20384,101 @@ "node": ">=8" } }, + "node_modules/mod-arch-core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mod-arch-core/-/mod-arch-core-1.1.0.tgz", + "integrity": "sha512-0wuOa7cCQUDo43TghdMm+g3tctQlWs27qc4AZqzXkixmrgxtArvUds2ppisbEnIUm/3mZUNir9yqGf+5pXCxJA==", + "dependencies": { + "lodash-es": "^4.17.15", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "@typescript-eslint/eslint-plugin": "^8.26.1", + "@typescript-eslint/parser": "^8.26.1", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-import-resolver-typescript": "^3.8.3", + "eslint-plugin-cypress": "^3.3.0", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-no-only-tests": "^3.1.0", + "eslint-plugin-no-relative-import-paths": "^1.6.1", + "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/mod-arch-kubeflow": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mod-arch-kubeflow/-/mod-arch-kubeflow-1.1.0.tgz", + "integrity": "sha512-k7XZ18VoW/V+Kd4tYW/KEYlImA/A0YkDwHOvj7bXWk6Qm41TpoxRy/H8yZkJ5sgTRJGbfrjEfH71k+ZQZWcagQ==", + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "@typescript-eslint/eslint-plugin": "^8.26.1", + "@typescript-eslint/parser": "^8.26.1", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-import-resolver-typescript": "^3.8.3", + "eslint-plugin-cypress": "^3.3.0", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-no-only-tests": "^3.1.0", + "eslint-plugin-no-relative-import-paths": "^1.6.1", + "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.2.0" + }, + "peerDependencies": { + "@mui/material": "^6.0.0", + "react": ">=16.8.0" + } + }, + "node_modules/mod-arch-shared": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mod-arch-shared/-/mod-arch-shared-1.1.0.tgz", + "integrity": "sha512-01OqpfnxDBhY/DiPmWzA9j38BTRQXtRWUKUuZ8rke1yQOKD4vyHv/7Dij3Ar58N6ts48QFyCbmzCy0uRdmXTTw==", + "dependencies": { + "@patternfly/patternfly": "^6.2.0", + "classnames": "^2.2.6", + "dompurify": "^3.2.4", + "lodash-es": "^4.17.15", + "showdown": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "@typescript-eslint/eslint-plugin": "^8.26.1", + "@typescript-eslint/parser": "^8.26.1", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-import-resolver-typescript": "^3.8.3", + "eslint-plugin-cypress": "^3.3.0", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-no-only-tests": "^3.1.0", + "eslint-plugin-no-relative-import-paths": "^1.6.1", + "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/monaco-editor": { "version": "0.52.2", "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz", @@ -19014,6 +20682,20 @@ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "devOptional": true }, + "node_modules/node-schedule": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", + "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "dev": true, + "dependencies": { + "cron-parser": "^4.2.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -19665,6 +21347,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==", + "dev": true + }, "node_modules/open": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz", @@ -19925,6 +21613,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -20978,6 +22675,12 @@ } ] }, + "node_modules/rambda": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/rambda/-/rambda-9.4.2.tgz", + "integrity": "sha512-++euMfxnl7OgaEKwXh9QqThOjMeta2HH001N1v4mYQzBjJBnmXBh2BCK6dZAbICFVXOFUVD3xFG0R3ZPU0mxXw==", + "dev": true + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -21197,7 +22900,6 @@ "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "dev": true, "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -21564,6 +23266,19 @@ "node": ">=8" } }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -21629,8 +23344,8 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "license": "MIT", - "optional": true + "devOptional": true, + "license": "MIT" }, "node_modules/rimraf": { "version": "6.0.1", @@ -21702,6 +23417,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rslog": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/rslog/-/rslog-1.2.11.tgz", + "integrity": "sha512-YgMMzQf6lL9q4rD9WS/lpPWxVNJ1ttY9+dOXJ0+7vJrKCAOT4GH0EiRnBi9mKOitcHiOwjqJPV1n/HRqqgZmOQ==", + "dev": true + }, "node_modules/run-applescript": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", @@ -21793,7 +23514,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "optional": true, + "devOptional": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -22663,6 +24384,12 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==", + "dev": true + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -22980,6 +24707,52 @@ "node": ">= 0.4" } }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/streamroller/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/streamroller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/streamroller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -24191,6 +25964,15 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "dev": true, + "engines": { + "node": ">=0.6.x" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -24521,6 +26303,16 @@ "node": ">=8" } }, + "node_modules/upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -25723,6 +27515,15 @@ "fd-slicer": "~1.1.0" } }, + "node_modules/ylru": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz", + "integrity": "sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/workspaces/frontend/package.json b/workspaces/frontend/package.json index b1e52e3b..368cce89 100644 --- a/workspaces/frontend/package.json +++ b/workspaces/frontend/package.json @@ -42,6 +42,7 @@ "prepare": "cd ../../ && husky workspaces/frontend/.husky" }, "devDependencies": { + "@module-federation/enhanced": "^0.13.1", "@mui/icons-material": "^6.4.8", "@mui/material": "^6.3.1", "@mui/types": "^7.2.21", @@ -120,6 +121,9 @@ "dompurify": "^3.2.4", "js-yaml": "^4.1.0", "lodash-es": "^4.17.15", + "mod-arch-core": "^1.1.0", + "mod-arch-kubeflow": "^1.1.0", + "mod-arch-shared": "^1.1.0", "react": "^18", "react-dom": "^18", "react-router": "^7.5.2", diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/pages/navBar.ts b/workspaces/frontend/src/__tests__/cypress/cypress/pages/navBar.ts new file mode 100644 index 00000000..a72bf898 --- /dev/null +++ b/workspaces/frontend/src/__tests__/cypress/cypress/pages/navBar.ts @@ -0,0 +1,33 @@ +class NavBar { + findBrand() { + return cy.get('.pf-v5-c-brand'); + } + + findNavToggleButton() { + return cy.get('#page-nav-toggle'); + } + + findNamespaceSelector() { + return cy.get('.kubeflow-u-namespace-select'); + } + + selectNamespace(name: string) { + this.findNamespaceSelector().findByRole('button').click(); + cy.findByRole('option', { name }).click(); + } + + findUsername() { + return cy.findByTestId('user-menu-toggle-button'); + } + + openUserMenu() { + this.findUsername().click(); + } + + shouldNamespaceSelectorHaveNoItems() { + this.findNamespaceSelector().click(); + cy.findByRole('option').should('not.exist'); + } +} + +export const navBar = new NavBar(); diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/e2e/NamespaceSelector.cy.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/e2e/NamespaceSelector.cy.ts deleted file mode 100644 index 4a9cfdca..00000000 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/e2e/NamespaceSelector.cy.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { mockNamespaces } from '~/__mocks__/mockNamespaces'; -import { mockBFFResponse } from '~/__mocks__/utils'; - -const namespaces = ['default', 'kubeflow', 'custom-namespace']; - -describe('Namespace Selector Dropdown', () => { - beforeEach(() => { - // Mock the namespaces API response - cy.intercept('GET', '/api/v1/namespaces', { - body: mockBFFResponse(mockNamespaces), - }).as('getNamespaces'); - cy.visit('/'); - cy.wait('@getNamespaces'); - }); - - it('should open the namespace dropdown and select a namespace', () => { - cy.findByTestId('namespace-toggle').click(); - cy.findByTestId('namespace-dropdown').should('be.visible'); - namespaces.forEach((ns) => { - cy.findByTestId(`dropdown-item-${ns}`).should('exist').and('contain', ns); - }); - - cy.findByTestId('dropdown-item-kubeflow').click(); - - // Assert the selected namespace is updated - cy.findByTestId('namespace-toggle').should('contain', 'kubeflow'); - }); - - it('should display the default namespace initially', () => { - cy.findByTestId('namespace-toggle').should('contain', 'default'); - }); - - it('should navigate to notebook settings and retain the namespace', () => { - cy.findByTestId('namespace-toggle').click(); - cy.findByTestId('dropdown-item-custom-namespace').click(); - cy.findByTestId('namespace-toggle').should('contain', 'custom-namespace'); - // Click on navigation button - cy.get('#Settings').click(); - cy.findByTestId('nav-link-/notebookSettings').click(); - cy.findByTestId('namespace-toggle').should('contain', 'custom-namespace'); - }); - - it('should filter namespaces based on search input', () => { - cy.findByTestId('namespace-toggle').click(); - cy.findByTestId('namespace-search-input').type('custom'); - cy.findByTestId('namespace-search-input').find('input').should('have.value', 'custom'); - cy.findByTestId('namespace-search-button').click(); - // Verify that only the matching namespace is displayed - namespaces.forEach((ns) => { - if (ns === 'custom-namespace') { - cy.findByTestId(`dropdown-item-${ns}`).should('exist').and('contain', ns); - } else { - cy.findByTestId(`dropdown-item-${ns}`).should('not.exist'); - } - }); - }); -}); diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts index ce5a4ba3..52134087 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts @@ -3,27 +3,27 @@ import { home } from '~/__tests__/cypress/cypress/pages/home'; import { mockNamespaces } from '~/__mocks__/mockNamespaces'; import { mockBFFResponse } from '~/__mocks__/utils'; import { mockWorkspace1 } from '~/shared/mock/mockNotebookServiceData'; +import { navBar } from '~/__tests__/cypress/cypress/pages/navBar'; describe('Application', () => { - beforeEach(() => { - // Mock the namespaces API response - cy.intercept('GET', '/api/v1/namespaces', { - body: mockBFFResponse(mockNamespaces), - }).as('getNamespaces'); - cy.intercept('GET', `/api/v1/workspaces/${mockNamespaces[0].name}`, { - body: mockBFFResponse({ mockWorkspace1 }), - }).as('getWorkspaces'); - cy.visit('/'); - cy.wait('@getNamespaces'); - cy.wait('@getWorkspaces'); - }); - it('Page not found should render', () => { pageNotfound.visit(); }); it('Home page should have primary button', () => { + cy.intercept('GET', '/api/v1/namespaces', { + body: mockBFFResponse(mockNamespaces), + }).as('getNamespaces'); + cy.intercept('GET', `/api/v1/workspaces/${mockNamespaces[0].name}`, { + body: mockBFFResponse([mockWorkspace1]), + }).as('getWorkspaces'); + home.visit(); + navBar.selectNamespace(mockNamespaces[0].name); + + cy.wait('@getNamespaces'); + cy.wait('@getWorkspaces'); + home.findButton(); }); }); diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/Workspaces.cy.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/Workspaces.cy.ts index a593f4f6..6da7b7e6 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/Workspaces.cy.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/Workspaces.cy.ts @@ -6,6 +6,7 @@ import { mockWorkspacesByNS, } from '~/__tests__/cypress/cypress/tests/mocked/workspace.mock'; import type { WorkspacesWorkspace } from '~/generated/data-contracts'; +import { navBar } from '~/__tests__/cypress/cypress/pages/navBar'; // Helper function to validate the content of a single workspace row in the table const validateWorkspaceRow = (workspace: WorkspacesWorkspace, index: number) => { @@ -67,7 +68,7 @@ describe('Workspace by namespace functionality', () => { body: mockBFFResponse(mockNamespaces), }).as('getNamespaces'); - cy.intercept('GET', 'api/v1/workspaces', { body: mockBFFResponse(mockWorkspaces) }).as( + cy.intercept('GET', '/api/v1/workspaces', { body: mockBFFResponse(mockWorkspaces) }).as( 'getWorkspaces', ); @@ -87,8 +88,7 @@ describe('Workspace by namespace functionality', () => { .should('have.length', mockWorkspaces.length); // Change namespace to "kubeflow" - cy.findByTestId('namespace-toggle').click(); - cy.findByTestId('dropdown-item-kubeflow').click(); + navBar.selectNamespace('kubeflow'); // Verify the API call is made with the new namespace cy.wait('@getKubeflowWorkspaces') @@ -110,10 +110,10 @@ describe('Workspaces Component', () => { body: mockBFFResponse(mockNamespaces), }).as('getNamespaces'); cy.wait('@getNamespaces'); - cy.intercept('GET', 'api/v1/workspaces', { + cy.intercept('GET', '/api/v1/workspaces', { body: mockBFFResponse(mockWorkspaces), }).as('getWorkspaces'); - cy.intercept('GET', 'api/v1/workspaces/kubeflow', { + cy.intercept('GET', '/api/v1/workspaces/kubeflow', { body: mockBFFResponse(mockWorkspacesByNS), }); }); @@ -136,8 +136,7 @@ describe('Workspaces Component', () => { ]; // Change namespace to "kubeflow" - cy.findByTestId('namespace-toggle').click(); - cy.findByTestId('dropdown-item-kubeflow').click(); + navBar.selectNamespace('kubeflow'); closeModalActions.forEach((closeAction) => { openDeleteModal(); @@ -156,8 +155,7 @@ describe('Workspaces Component', () => { it('should verify the delete modal verification mechanism', () => { // Change namespace to "kubeflow" - cy.findByTestId('namespace-toggle').click(); - cy.findByTestId('dropdown-item-kubeflow').click(); + navBar.selectNamespace('kubeflow'); openDeleteModal(); cy.findByTestId('delete-modal').within(() => { cy.get('strong') diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/filterWorkspacesTest.cy.ts b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/filterWorkspacesTest.cy.ts index 968d2269..bab66536 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/filterWorkspacesTest.cy.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/filterWorkspacesTest.cy.ts @@ -2,6 +2,7 @@ import { mockNamespaces } from '~/__mocks__/mockNamespaces'; import { mockWorkspaces } from '~/__mocks__/mockWorkspaces'; import { mockBFFResponse } from '~/__mocks__/utils'; import { home } from '~/__tests__/cypress/cypress/pages/home'; +import { mockWorkspaceKinds } from '~/shared/mock/mockNotebookServiceData'; const useFilter = (filterKey: string, filterName: string, searchValue: string) => { cy.get("[id$='filter-workspaces-dropdown']").click(); @@ -19,9 +20,12 @@ describe('Application', () => { cy.intercept('GET', '/api/v1/workspaces', { body: mockBFFResponse(mockWorkspaces), }).as('getWorkspaces'); - cy.intercept('GET', '/api/v1/workspaces/default', { + cy.intercept('GET', '/api/v1/workspaces/custom-namespace', { body: mockBFFResponse(mockWorkspaces), }); + cy.intercept('GET', '/api/v1/workspacekinds', { + body: mockBFFResponse(mockWorkspaceKinds), + }); cy.intercept('GET', '/api/namespaces/test-namespace/workspaces').as('getWorkspaces'); }); diff --git a/workspaces/frontend/src/__tests__/cypress/cypress/webpack.config.ts b/workspaces/frontend/src/__tests__/cypress/cypress/webpack.config.ts index 53d0c6c3..f06798d7 100644 --- a/workspaces/frontend/src/__tests__/cypress/cypress/webpack.config.ts +++ b/workspaces/frontend/src/__tests__/cypress/cypress/webpack.config.ts @@ -37,6 +37,7 @@ const webpackConfig = { ), path.resolve(__dirname, '../../../node_modules/@patternfly/patternfly/assets/fonts'), path.resolve(__dirname, '../../../node_modules/@patternfly/patternfly/assets/pficon'), + path.resolve(__dirname, '../../../node_modules/mod-arch-shared'), ], use: { loader: 'file-loader', @@ -63,9 +64,9 @@ const webpackConfig = { }, { test: /\.svg$/, - // Handle SVG files + // Handle SVG files from mod-arch-shared and other sources include: (input: string): boolean => - input.indexOf('images') > -1 && + (input.indexOf('mod-arch-shared') > -1 || input.indexOf('images') > -1) && input.indexOf('fonts') === -1 && input.indexOf('background-filter') === -1 && input.indexOf('pficon') === -1, @@ -88,6 +89,7 @@ const webpackConfig = { __dirname, '../../../node_modules/@patternfly/react-core/dist/styles/assets/images', ), + path.resolve(__dirname, '../../../node_modules/mod-arch-shared'), ], use: [ { diff --git a/workspaces/frontend/src/app/App.tsx b/workspaces/frontend/src/app/App.tsx index d51be6ea..77d65722 100644 --- a/workspaces/frontend/src/app/App.tsx +++ b/workspaces/frontend/src/app/App.tsx @@ -1,93 +1,56 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import '@patternfly/patternfly/patternfly-addons.css'; import '@patternfly/react-core/dist/styles/base.css'; -import './app.css'; -import { Brand } from '@patternfly/react-core/dist/esm/components/Brand'; -import { Flex } from '@patternfly/react-core/dist/esm/layouts/Flex'; -import { - Masthead, - MastheadBrand, - MastheadContent, - MastheadLogo, - MastheadMain, - MastheadToggle, -} from '@patternfly/react-core/dist/esm/components/Masthead'; -import { - Page, - PageSidebar, - PageToggleButton, -} from '@patternfly/react-core/dist/esm/components/Page'; -import { Title } from '@patternfly/react-core/dist/esm/components/Title'; -import { BarsIcon } from '@patternfly/react-icons/dist/esm/icons/bars-icon'; +import '~/app/app.css'; +import '~/app/theme-overrides.css'; +import { Page, PageSidebar } from '@patternfly/react-core/dist/esm/components/Page'; +import { DeploymentMode, logout, useModularArchContext } from 'mod-arch-core'; import ErrorBoundary from '~/app/error/ErrorBoundary'; -import NamespaceSelector from '~/shared/components/NamespaceSelector'; -import logoDarkTheme from '~/images/logo-dark-theme.svg'; -import { DEPLOYMENT_MODE, isMUITheme } from '~/shared/utilities/const'; -import { DeploymentMode, Theme } from '~/shared/utilities/types'; -import { NamespaceContextProvider } from './context/NamespaceContextProvider'; -import AppRoutes from './AppRoutes'; -import NavSidebar from './NavSidebar'; -import { NotebookContextProvider } from './context/NotebookContext'; -import { BrowserStorageContextProvider } from './context/BrowserStorageContext'; - -const isStandalone = DEPLOYMENT_MODE === DeploymentMode.Standalone; +import AppRoutes from '~/app/AppRoutes'; +import { AppContext, AppContextProvider } from '~/app/context/AppContext'; +import { NamespaceContextProvider } from '~/app/context/NamespaceContextProvider'; +import { NotebookContextProvider } from '~/app/context/NotebookContext'; +import ToastNotifications from '~/app/standalone/ToastNotifications'; +import NavBar from '~/app/standalone/NavBar'; +import NavSidebar from '~/app/standalone/NavSidebar'; const App: React.FC = () => { - useEffect(() => { - // Apply the theme based on the value of STYLE_THEME - if (isMUITheme()) { - document.documentElement.classList.add(Theme.MUI); - } else { - document.documentElement.classList.remove(Theme.MUI); - } - }, []); - - const masthead = ( - - - - - - - - {!isMUITheme() && ( - - - - - - )} - - - - - Kubeflow Notebooks 2.0 - - - - - - ); - const sidebar = ; + const { config } = useModularArchContext(); + const { deploymentMode } = config; + const isStandalone = deploymentMode === DeploymentMode.Standalone; return ( - - - - : sidebar} - isManagedSidebar={isStandalone} - className={isStandalone ? '' : 'embedded'} - > - - - - - + + + {(context) => ( + + + { + logout().then(() => window.location.reload()); + }} + /> + ) : ( + '' + ) + } + isManagedSidebar={isStandalone} + sidebar={isStandalone ? : } + > + + + + + + )} + + ); }; diff --git a/workspaces/frontend/src/app/AppRoutes.tsx b/workspaces/frontend/src/app/AppRoutes.tsx index ea9a2ba9..2de7b7c7 100644 --- a/workspaces/frontend/src/app/AppRoutes.tsx +++ b/workspaces/frontend/src/app/AppRoutes.tsx @@ -3,12 +3,12 @@ import { Route, Routes, Navigate } from 'react-router-dom'; import { AppRoutePaths } from '~/app/routes'; import { WorkspaceKindSummaryWrapper } from '~/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryWrapper'; import { WorkspaceForm } from '~/app/pages/Workspaces/Form/WorkspaceForm'; +import { useAppContext } from '~/app/context/AppContext'; import { Debug } from './pages/Debug/Debug'; import { NotFound } from './pages/notFound/NotFound'; import { WorkspaceKinds } from './pages/WorkspaceKinds/WorkspaceKinds'; import { WorkspacesWrapper } from './pages/Workspaces/WorkspacesWrapper'; import { WorkspaceKindForm } from './pages/WorkspaceKinds/Form/WorkspaceKindForm'; -import '~/shared/style/MUI-theme.scss'; export const isNavDataGroup = (navItem: NavDataItem): navItem is NavDataGroup => 'children' in navItem; @@ -28,12 +28,9 @@ export type NavDataGroup = NavDataCommon & { type NavDataItem = NavDataHref | NavDataGroup; export const useAdminDebugSettings = (): NavDataItem[] => { - // get auth access for example set admin as true - const isAdmin = true; //this should be a call to getting auth / role access + const { user } = useAppContext(); - // TODO: Remove the linter skip when we implement authentication - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (!isAdmin) { + if (!user?.clusterAdmin) { return []; } @@ -58,7 +55,7 @@ export const useNavData = (): NavDataItem[] => [ ]; const AppRoutes: React.FC = () => { - const isAdmin = true; + const { user } = useAppContext(); return ( @@ -74,11 +71,7 @@ const AppRoutes: React.FC = () => { element={} /> } /> - { - // TODO: Remove the linter skip when we implement authentication - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - isAdmin && } /> - } + {user?.clusterAdmin && } />} ); }; diff --git a/workspaces/frontend/src/app/components/LoadError.tsx b/workspaces/frontend/src/app/components/LoadError.tsx index 2ca06b98..0ec8a800 100644 --- a/workspaces/frontend/src/app/components/LoadError.tsx +++ b/workspaces/frontend/src/app/components/LoadError.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { Alert } from '@patternfly/react-core/dist/esm/components/Alert'; import { Bullseye } from '@patternfly/react-core/dist/esm/layouts/Bullseye'; diff --git a/workspaces/frontend/src/app/components/LoadingSpinner.tsx b/workspaces/frontend/src/app/components/LoadingSpinner.tsx index 945bf967..f26d46b2 100644 --- a/workspaces/frontend/src/app/components/LoadingSpinner.tsx +++ b/workspaces/frontend/src/app/components/LoadingSpinner.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { Bullseye } from '@patternfly/react-core/dist/esm/layouts/Bullseye'; import { Spinner } from '@patternfly/react-core/dist/esm/components/Spinner'; diff --git a/workspaces/frontend/src/app/components/ThemeAwareSearchInput.tsx b/workspaces/frontend/src/app/components/ThemeAwareSearchInput.tsx index d916b8d1..d8375d2e 100644 --- a/workspaces/frontend/src/app/components/ThemeAwareSearchInput.tsx +++ b/workspaces/frontend/src/app/components/ThemeAwareSearchInput.tsx @@ -4,8 +4,8 @@ import { SearchInputProps, } from '@patternfly/react-core/dist/esm/components/SearchInput'; import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput'; +import { useThemeContext } from 'mod-arch-kubeflow'; import FormFieldset from '~/app/components/FormFieldset'; -import { isMUITheme } from '~/shared/utilities/const'; type ThemeAwareSearchInputProps = Omit & { onChange: (value: string) => void; // Simplified onChange signature @@ -27,7 +27,8 @@ const ThemeAwareSearchInput: React.FC = ({ 'data-testid': dataTestId, ...rest }) => { - if (isMUITheme()) { + const { isMUITheme } = useThemeContext(); + if (isMUITheme) { // Render MUI version using TextInput + FormFieldset return ( (undefined); + +export const useAppContext = (): AppContextType => { + const context = useContext(AppContext); + if (!context) { + throw new Error('useAppContext must be used within a AppContextProvider'); + } + return context; +}; + +interface AppContextProviderProps { + children: ReactNode; +} + +export const AppContextProvider: React.FC = ({ children }) => { + const { configSettings } = useSettings(); + // TODO: replace userSettings with `const { configSettings, userSettings } = useSettings();` once integrated with users + const userSettings: UserSettings = useMemo( + () => ({ + userId: 'kubeflow-user', + clusterAdmin: true, + }), + [], + ); + + const contextValue = useMemo( + () => ({ + config: configSettings, + user: userSettings, + }), + [configSettings, userSettings], + ); + + return {children}; +}; diff --git a/workspaces/frontend/src/app/context/BrowserStorageContext.tsx b/workspaces/frontend/src/app/context/BrowserStorageContext.tsx deleted file mode 100644 index d3f99c4d..00000000 --- a/workspaces/frontend/src/app/context/BrowserStorageContext.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React, { - createContext, - useCallback, - useContext, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; - -export interface BrowserStorageContextType { - getValue: (key: string) => unknown; - setValue: (key: string, value: string) => void; -} - -export interface BrowserStorageContextProviderProps { - children: React.ReactNode; -} - -const BrowserStorageContext = createContext({ - getValue: () => null, - setValue: () => undefined, -}); - -export const BrowserStorageContextProvider: React.FC = ({ - children, -}) => { - const [values, setValues] = useState<{ [key: string]: unknown }>({}); - const valuesRef = useRef(values); - useEffect(() => { - valuesRef.current = values; - }, [values]); - - const storageEventCb = useCallback(() => { - const keys = Object.keys(values); - setValues(Object.fromEntries(keys.map((k) => [k, localStorage.getItem(k)]))); - }, [values, setValues]); - - useEffect(() => { - window.addEventListener('storage', storageEventCb); - return () => { - window.removeEventListener('storage', storageEventCb); - }; - }, [storageEventCb]); - - const getValue = useCallback( - (key: string) => localStorage.getItem(key), - [], - ); - - const setValue = useCallback( - (key: string, value: string) => { - localStorage.setItem(key, value); - setValues((prev) => ({ ...prev, [key]: value })); - }, - [], - ); - - // eslint-disable-next-line react-hooks/exhaustive-deps - const contextValue = useMemo(() => ({ getValue, setValue }), [getValue, setValue, values]); - - return ( - {children} - ); -}; - -export const useStorage = ( - storageKey: string, - defaultValue: T, -): [T, (key: string, value: string) => void] => { - const context = useContext(BrowserStorageContext); - const { getValue, setValue } = context; - const value = (getValue(storageKey) as T) ?? defaultValue; - return [value, setValue]; -}; diff --git a/workspaces/frontend/src/app/context/NamespaceContextProvider.tsx b/workspaces/frontend/src/app/context/NamespaceContextProvider.tsx index c62a1981..3b86104a 100644 --- a/workspaces/frontend/src/app/context/NamespaceContextProvider.tsx +++ b/workspaces/frontend/src/app/context/NamespaceContextProvider.tsx @@ -1,16 +1,10 @@ -import React, { ReactNode, useCallback, useContext, useMemo, useState } from 'react'; -import useMount from '~/app/hooks/useMount'; -import useNamespaces from '~/app/hooks/useNamespaces'; -import { useStorage } from './BrowserStorageContext'; +import React, { ReactNode, useContext, useMemo, useRef, useEffect } from 'react'; +import { useBrowserStorage, useNamespaceSelector } from 'mod-arch-core'; const storageKey = 'kubeflow.notebooks.namespace.lastUsed'; interface NamespaceContextType { - namespaces: string[]; selectedNamespace: string; - setSelectedNamespace: (namespace: string) => void; - lastUsedNamespace: string; - updateLastUsedNamespace: (value: string) => void; } const NamespaceContext = React.createContext(undefined); @@ -28,44 +22,77 @@ interface NamespaceContextProviderProps { } export const NamespaceContextProvider: React.FC = ({ children }) => { - const [namespaces, setNamespaces] = useState([]); - const [selectedNamespace, setSelectedNamespace] = useState(''); - const [namespacesData, loaded, loadError] = useNamespaces(); - const [lastUsedNamespace, setLastUsedNamespace] = useStorage(storageKey, ''); + const { + namespaces: namespacesModArc, + preferredNamespace, + updatePreferredNamespace, + namespacesLoaded, + } = useNamespaceSelector(); + const [lastUsedNamespace, setLastUsedNamespace] = useBrowserStorage(storageKey, ''); + const namespaces = useMemo(() => namespacesModArc.map((ns) => ns.name), [namespacesModArc]); - const fetchNamespaces = useCallback(() => { - if (loaded && namespacesData) { - const namespaceNames = namespacesData.map((ns) => ns.name); - setNamespaces(namespaceNames); - setSelectedNamespace(lastUsedNamespace.length ? lastUsedNamespace : namespaceNames[0]); - if (!lastUsedNamespace.length || !namespaceNames.includes(lastUsedNamespace)) { - setLastUsedNamespace(storageKey, namespaceNames[0]); + const isInitializedRef = useRef(false); + const previousPreferredNamespaceRef = useRef(undefined); + + const selectedNamespace = useMemo(() => { + const currentPreferredName = preferredNamespace?.name ?? ''; + + if (!isInitializedRef.current && namespacesLoaded) { + if (lastUsedNamespace && namespaces.includes(lastUsedNamespace)) { + return lastUsedNamespace; } - } else { - if (loadError) { - console.error('Error loading namespaces: ', loadError); - } - setNamespaces([]); - setSelectedNamespace(''); + return currentPreferredName; } - }, [loaded, namespacesData, lastUsedNamespace, setLastUsedNamespace, loadError]); - const updateLastUsedNamespace = useCallback( - (value: string) => setLastUsedNamespace(storageKey, value), - [setLastUsedNamespace], - ); + if (lastUsedNamespace && namespaces.includes(lastUsedNamespace)) { + return lastUsedNamespace; + } - useMount(fetchNamespaces); + return currentPreferredName; + }, [lastUsedNamespace, namespaces, preferredNamespace?.name, namespacesLoaded]); + + useEffect(() => { + if (isInitializedRef.current || !namespacesLoaded) { + return; + } + + isInitializedRef.current = true; + + if (lastUsedNamespace && namespaces.includes(lastUsedNamespace)) { + updatePreferredNamespace({ name: lastUsedNamespace }); + } else { + const fallbackNamespace = preferredNamespace?.name || ''; + setLastUsedNamespace(fallbackNamespace); + } + }, [ + namespacesLoaded, + lastUsedNamespace, + namespaces, + preferredNamespace?.name, + updatePreferredNamespace, + setLastUsedNamespace, + ]); + + useEffect(() => { + const currentPreferredName = preferredNamespace?.name; + const previousPreferredName = previousPreferredNamespaceRef.current; + + previousPreferredNamespaceRef.current = currentPreferredName; + + if ( + isInitializedRef.current && + currentPreferredName !== previousPreferredName && + currentPreferredName + ) { + setLastUsedNamespace(currentPreferredName); + } + }, [preferredNamespace?.name, setLastUsedNamespace]); const namespacesContextValues = useMemo( () => ({ - namespaces, selectedNamespace, - setSelectedNamespace, - lastUsedNamespace, - updateLastUsedNamespace, }), - [namespaces, selectedNamespace, lastUsedNamespace, updateLastUsedNamespace], + [selectedNamespace], ); return ( diff --git a/workspaces/frontend/src/app/context/NotebookContext.tsx b/workspaces/frontend/src/app/context/NotebookContext.tsx index ba26a04a..722f552e 100644 --- a/workspaces/frontend/src/app/context/NotebookContext.tsx +++ b/workspaces/frontend/src/app/context/NotebookContext.tsx @@ -1,6 +1,6 @@ import React, { ReactNode, useMemo } from 'react'; import EnsureAPIAvailability from '~/app/EnsureAPIAvailability'; -import { BFF_API_PREFIX, BFF_API_VERSION } from '~/shared/utilities/const'; +import { URL_PREFIX, BFF_API_VERSION } from '~/shared/utilities/const'; import useNotebookAPIState, { NotebookAPIState } from './useNotebookAPIState'; export type NotebookContextType = { @@ -19,22 +19,22 @@ interface NotebookContextProviderProps { } export const NotebookContextProvider: React.FC = ({ children }) => { - // Remove trailing slash from BFF_API_PREFIX to avoid double slashes - const cleanPrefix = BFF_API_PREFIX.replace(/\/$/, ''); + // Remove trailing slash from URL_PREFIX to avoid double slashes + const cleanPrefix = URL_PREFIX.replace(/\/$/, ''); const hostPath = `${cleanPrefix}/api/${BFF_API_VERSION}`; const [apiState, refreshAPIState] = useNotebookAPIState(hostPath); + const contextValue = useMemo( + () => ({ + apiState, + refreshAPIState, + }), + [apiState, refreshAPIState], + ); + return ( - ({ - apiState, - refreshAPIState, - }), - [apiState, refreshAPIState], - )} - > + {children} ); diff --git a/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx b/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx index 75a2a7ee..dfe06faf 100644 --- a/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx +++ b/workspaces/frontend/src/app/context/WorkspaceActionsContext.tsx @@ -4,6 +4,7 @@ import { DrawerContent, DrawerContentBody, } from '@patternfly/react-core/dist/esm/components/Drawer'; +import { useNotification } from 'mod-arch-core'; import { useNamespaceContext } from '~/app/context/NamespaceContextProvider'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { WorkspaceDetails } from '~/app/pages/Workspaces/Details/WorkspaceDetails'; @@ -62,6 +63,7 @@ export const WorkspaceActionsContextProvider: React.FC { const navigate = useTypedNavigate(); + const notification = useNotification(); const { api } = useNotebookAPI(); const { selectedNamespace } = useNamespaceContext(); const [activeWsAction, setActiveWsAction] = useState(null); @@ -115,14 +117,13 @@ export const WorkspaceActionsContextProvider: React.FC { if (!activeWsAction) { diff --git a/workspaces/frontend/src/app/hooks/useNamespaces.ts b/workspaces/frontend/src/app/hooks/useNamespaces.ts index 0e160713..1d8067d6 100644 --- a/workspaces/frontend/src/app/hooks/useNamespaces.ts +++ b/workspaces/frontend/src/app/hooks/useNamespaces.ts @@ -1,10 +1,7 @@ import { useCallback } from 'react'; +import { FetchState, FetchStateCallbackPromise, useFetchState } from 'mod-arch-core'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { ApiNamespaceListEnvelope } from '~/generated/data-contracts'; -import useFetchState, { - FetchState, - FetchStateCallbackPromise, -} from '~/shared/utilities/useFetchState'; const useNamespaces = (): FetchState => { const { api, apiAvailable } = useNotebookAPI(); diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts b/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts index 97429986..c446e5e1 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaceFormData.ts @@ -1,10 +1,7 @@ import { useCallback } from 'react'; +import { FetchState, FetchStateCallbackPromise, useFetchState } from 'mod-arch-core'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { WorkspaceFormData } from '~/app/types'; -import useFetchState, { - FetchState, - FetchStateCallbackPromise, -} from '~/shared/utilities/useFetchState'; export const EMPTY_FORM_DATA: WorkspaceFormData = { kind: undefined, diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceKindByName.ts b/workspaces/frontend/src/app/hooks/useWorkspaceKindByName.ts index b5ebac44..864d8940 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaceKindByName.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaceKindByName.ts @@ -1,8 +1,5 @@ import { useCallback } from 'react'; -import useFetchState, { - FetchState, - FetchStateCallbackPromise, -} from '~/shared/utilities/useFetchState'; +import { FetchState, FetchStateCallbackPromise, useFetchState } from 'mod-arch-core'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { ApiWorkspaceKindEnvelope } from '~/generated/data-contracts'; diff --git a/workspaces/frontend/src/app/hooks/useWorkspaceKinds.ts b/workspaces/frontend/src/app/hooks/useWorkspaceKinds.ts index 99d64e9b..935e39fd 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaceKinds.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaceKinds.ts @@ -1,8 +1,5 @@ +import { FetchState, FetchStateCallbackPromise, useFetchState } from 'mod-arch-core'; import { useCallback } from 'react'; -import useFetchState, { - FetchState, - FetchStateCallbackPromise, -} from '~/shared/utilities/useFetchState'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { ApiWorkspaceKindListEnvelope, diff --git a/workspaces/frontend/src/app/hooks/useWorkspaces.ts b/workspaces/frontend/src/app/hooks/useWorkspaces.ts index 5ce9d320..0228a6a6 100644 --- a/workspaces/frontend/src/app/hooks/useWorkspaces.ts +++ b/workspaces/frontend/src/app/hooks/useWorkspaces.ts @@ -1,8 +1,5 @@ +import { FetchState, FetchStateCallbackPromise, useFetchState } from 'mod-arch-core'; import { useCallback } from 'react'; -import useFetchState, { - FetchState, - FetchStateCallbackPromise, -} from '~/shared/utilities/useFetchState'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { ApiWorkspaceListEnvelope } from '~/generated/data-contracts'; diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx index 47209804..7ca0a271 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx @@ -7,6 +7,7 @@ import { Stack, StackItem } from '@patternfly/react-core/dist/esm/layouts/Stack' import { t_global_spacer_sm as SmallPadding } from '@patternfly/react-tokens'; import { ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; import { EmptyState, EmptyStateBody } from '@patternfly/react-core/dist/esm/components/EmptyState'; +import { useNotification } from 'mod-arch-core'; import { ValidationErrorAlert } from '~/app/components/ValidationErrorAlert'; import useWorkspaceKindByName from '~/app/hooks/useWorkspaceKindByName'; import { useTypedNavigate, useTypedParams } from '~/app/routerHelper'; @@ -16,8 +17,8 @@ import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { WorkspaceKindFormData } from '~/app/types'; import { safeApiCall } from '~/shared/api/apiUtils'; import { CONTENT_TYPE_KEY } from '~/shared/utilities/const'; -import { ApiValidationError, WorkspacekindsWorkspaceKind } from '~/generated/data-contracts'; import { ContentType } from '~/shared/utilities/types'; +import { ApiValidationError, WorkspacekindsWorkspaceKind } from '~/generated/data-contracts'; import { WorkspaceKindFileUpload } from './fileUpload/WorkspaceKindFileUpload'; import { WorkspaceKindFormProperties } from './properties/WorkspaceKindFormProperties'; import { WorkspaceKindFormImage } from './image/WorkspaceKindFormImage'; @@ -47,6 +48,7 @@ const convertToFormData = (initialData: WorkspacekindsWorkspaceKind): WorkspaceK export const WorkspaceKindForm: React.FC = () => { const navigate = useTypedNavigate(); + const notification = useNotification(); const { api } = useNotebookAPI(); // TODO: Detect mode by route const [yamlValue, setYamlValue] = useState(''); @@ -85,8 +87,9 @@ export const WorkspaceKindForm: React.FC = () => { ); if (createResult.ok) { - // TODO: alert user about success - console.info('New workspace kind created:', JSON.stringify(createResult.data)); + notification.success( + `Workspace kind '${createResult.data.data.name}' created successfully`, + ); navigate('workspaceKinds'); } else { const validationErrors = createResult.errorEnvelope.error.cause?.validation_errors; @@ -114,7 +117,7 @@ export const WorkspaceKindForm: React.FC = () => { } finally { setIsSubmitting(false); } - }, [api, mode, navigate, yamlValue]); + }, [api, mode, navigate, yamlValue, notification]); const canSubmit = useMemo( () => !isSubmitting && validated === 'success', diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx index 3f212a17..0ebef489 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/WorkspaceKinds.tsx @@ -45,6 +45,8 @@ import WithValidImage from '~/shared/components/WithValidImage'; import ImageFallback from '~/shared/components/ImageFallback'; import { useTypedNavigate } from '~/app/routerHelper'; import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts'; +import { LoadError } from '~/app/components/LoadError'; +import { LoadingSpinner } from '~/app/components/LoadingSpinner'; import { WorkspaceKindDetails } from './details/WorkspaceKindDetails'; export enum ActionType { @@ -316,11 +318,11 @@ export const WorkspaceKinds: React.FunctionComponent = () => { const DESCRIPTION_CHAR_LIMIT = 50; if (workspaceKindsError) { - return

Error loading workspace kinds: {workspaceKindsError.message}

; // TODO: UX for error state + return ; } if (!workspaceKindsLoaded) { - return

Loading...

; // TODO: UX for loading state + return ; } return ( diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryWrapper.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryWrapper.tsx index b40e733b..4de088bc 100644 --- a/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryWrapper.tsx +++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryWrapper.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { WorkspaceActionsContextProvider } from '~/app/context/WorkspaceActionsContext'; import WorkspaceKindSummary from '~/app/pages/WorkspaceKinds/summary/WorkspaceKindSummary'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx index f4cdb8b2..3219c3d8 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/WorkspaceForm.tsx @@ -19,6 +19,7 @@ import { DrawerPanelContent, } from '@patternfly/react-core/dist/esm/components/Drawer'; import { Title } from '@patternfly/react-core/dist/esm/components/Title'; +import { useNotification } from 'mod-arch-core'; import useGenericObjectState from '~/app/hooks/useGenericObjectState'; import { useNotebookAPI } from '~/app/hooks/useNotebookAPI'; import { WorkspaceFormImageSelection } from '~/app/pages/Workspaces/Form/image/WorkspaceFormImageSelection'; @@ -38,6 +39,8 @@ import { useWorkspaceFormLocationData } from '~/app/hooks/useWorkspaceFormLocati import { WorkspaceFormKindDetails } from '~/app/pages/Workspaces/Form/kind/WorkspaceFormKindDetails'; import { WorkspaceFormImageDetails } from '~/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails'; import { WorkspaceFormPodConfigDetails } from '~/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigDetails'; +import { LoadingSpinner } from '~/app/components/LoadingSpinner'; +import { LoadError } from '~/app/components/LoadError'; enum WorkspaceFormSteps { KindSelection, @@ -58,6 +61,7 @@ const stepDescriptions: { [key in WorkspaceFormSteps]?: string } = { const WorkspaceForm: React.FC = () => { const navigate = useTypedNavigate(); + const notification = useNotification(); const { api } = useNotebookAPI(); const { mode, namespace, workspaceName } = useWorkspaceFormLocationData(); @@ -182,8 +186,7 @@ const WorkspaceForm: React.FC = () => { const workspaceEnvelope = await api.workspaces.createWorkspace(namespace, { data: submitData, }); - // TODO: alert user about success - console.info('New workspace created:', JSON.stringify(workspaceEnvelope.data)); + notification.success(`Workspace '${workspaceEnvelope.data.name}' created successfully`); } navigate('workspaces'); @@ -193,7 +196,7 @@ const WorkspaceForm: React.FC = () => { } finally { setIsSubmitting(false); } - }, [data, mode, navigate, api, namespace]); + }, [data, mode, navigate, api, namespace, notification]); const cancel = useCallback(() => { navigate('workspaces'); @@ -257,11 +260,11 @@ const WorkspaceForm: React.FC = () => { }; if (initialFormDataError) { - return

Error loading workspace data: {initialFormDataError.message}

; // TODO: UX for error state + return ; } if (!initialFormDataLoaded) { - return

Loading...

; // TODO: UX for loading state + return ; } const panelContent = ( diff --git a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx index 30db818d..a564d257 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Form/kind/WorkspaceFormKindSelection.tsx @@ -3,6 +3,8 @@ import { Content } from '@patternfly/react-core/dist/esm/components/Content'; import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds'; import { WorkspaceFormKindList } from '~/app/pages/Workspaces/Form/kind/WorkspaceFormKindList'; import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts'; +import { LoadingSpinner } from '~/app/components/LoadingSpinner'; +import { LoadError } from '~/app/components/LoadError'; interface WorkspaceFormKindSelectionProps { selectedKind: WorkspacekindsWorkspaceKind | undefined; @@ -16,11 +18,11 @@ const WorkspaceFormKindSelection: React.FunctionComponentError loading workspace kinds: {error.message}

; // TODO: UX for error state + return ; } if (!loaded) { - return

Loading...

; // TODO: UX for loading state + return ; } return ( diff --git a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConfigDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConfigDetails.tsx index 83485ff1..ca62a74a 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConfigDetails.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceConfigDetails.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { DescriptionList, DescriptionListTerm, diff --git a/workspaces/frontend/src/app/pages/Workspaces/WorkspacePackageDetails.tsx b/workspaces/frontend/src/app/pages/Workspaces/WorkspacePackageDetails.tsx index 0424f312..541ab2ff 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/WorkspacePackageDetails.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/WorkspacePackageDetails.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { DescriptionList, DescriptionListTerm, diff --git a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceStorage.tsx b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceStorage.tsx index a6bd3da4..7581a980 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/WorkspaceStorage.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/WorkspaceStorage.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { DescriptionList, DescriptionListTerm, diff --git a/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx b/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx index 39ffae42..5e16debb 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { Content, ContentVariants } from '@patternfly/react-core/dist/esm/components/Content'; import { PageSection } from '@patternfly/react-core/dist/esm/components/Page'; import { Stack, StackItem } from '@patternfly/react-core/dist/esm/layouts/Stack'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/WorkspacesWrapper.tsx b/workspaces/frontend/src/app/pages/Workspaces/WorkspacesWrapper.tsx index 94bb7148..a4e605da 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/WorkspacesWrapper.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/WorkspacesWrapper.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { WorkspaceActionsContextProvider } from '~/app/context/WorkspaceActionsContext'; import { Workspaces } from '~/app/pages/Workspaces/Workspaces'; diff --git a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal.tsx b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal.tsx index 1bbfcaf4..6ad70865 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal.tsx @@ -7,6 +7,7 @@ import { ModalHeader, } from '@patternfly/react-core/dist/esm/components/Modal'; import { TabTitleText } from '@patternfly/react-core/dist/esm/components/Tabs'; +import { useNotification } from 'mod-arch-core'; import { WorkspaceRedirectInformationView } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView'; import { ActionButton } from '~/shared/components/ActionButton'; import { ApiWorkspaceActionPauseEnvelope, WorkspacesWorkspace } from '~/generated/data-contracts'; @@ -30,6 +31,7 @@ export const WorkspaceStartActionModal: React.FC = ({ onUpdateAndStart, onActionDone, }) => { + const notification = useNotification(); const [actionOnGoing, setActionOnGoing] = useState(null); const executeAction = useCallback( @@ -52,33 +54,31 @@ export const WorkspaceStartActionModal: React.FC = ({ const handleStart = useCallback(async () => { try { - const response = await executeAction({ action: 'start', callback: onStart }); - // TODO: alert user about success - console.info('Workspace started successfully:', JSON.stringify(response.data)); + await executeAction({ action: 'start', callback: onStart }); + notification.info(`Workspace '${workspace?.name}' started successfully`); onActionDone?.(); onClose(); } catch (error) { // TODO: alert user about error console.error('Error starting workspace:', error); } - }, [executeAction, onActionDone, onClose, onStart]); + }, [executeAction, onActionDone, onClose, onStart, notification, workspace]); // TODO: combine handleStart and handleUpdateAndStart if they end up being similar const handleUpdateAndStart = useCallback(async () => { try { - const response = await executeAction({ + await executeAction({ action: 'updateAndStart', callback: onUpdateAndStart, }); - // TODO: alert user about success - console.info('Workspace updated and started successfully:', JSON.stringify(response)); + notification.info(`Workspace '${workspace?.name}' updated and started successfully`); onActionDone?.(); onClose(); } catch (error) { // TODO: alert user about error console.error('Error updating and stopping workspace:', error); } - }, [executeAction, onActionDone, onClose, onUpdateAndStart]); + }, [executeAction, onActionDone, onClose, onUpdateAndStart, notification, workspace]); const shouldShowActionButton = useCallback( (action: StartAction) => !actionOnGoing || actionOnGoing === action, diff --git a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal.tsx b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal.tsx index 793efddc..0bb8d9af 100644 --- a/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal.tsx +++ b/workspaces/frontend/src/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal.tsx @@ -8,6 +8,7 @@ import { ModalHeader, } from '@patternfly/react-core/dist/esm/components/Modal'; import { TabTitleText } from '@patternfly/react-core/dist/esm/components/Tabs'; +import { useNotification } from 'mod-arch-core'; import { WorkspaceRedirectInformationView } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView'; import { ActionButton } from '~/shared/components/ActionButton'; import { ApiWorkspaceActionPauseEnvelope, WorkspacesWorkspace } from '~/generated/data-contracts'; @@ -31,6 +32,7 @@ export const WorkspaceStopActionModal: React.FC = ({ onUpdateAndStop, onActionDone, }) => { + const notification = useNotification(); const workspacePendingUpdate = workspace?.pendingRestart; const [actionOnGoing, setActionOnGoing] = useState(null); @@ -54,30 +56,28 @@ export const WorkspaceStopActionModal: React.FC = ({ const handleStop = useCallback(async () => { try { - const response = await executeAction({ action: 'stop', callback: onStop }); - // TODO: alert user about success - console.info('Workspace stopped successfully:', JSON.stringify(response.data)); + await executeAction({ action: 'stop', callback: onStop }); + notification.info(`Workspace '${workspace?.name}' stopped successfully`); onActionDone?.(); onClose(); } catch (error) { // TODO: alert user about error console.error('Error stopping workspace:', error); } - }, [executeAction, onActionDone, onClose, onStop]); + }, [executeAction, onActionDone, onClose, onStop, notification, workspace]); // TODO: combine handleStop and handleUpdateAndStop if they end up being similar const handleUpdateAndStop = useCallback(async () => { try { - const response = await executeAction({ action: 'updateAndStop', callback: onUpdateAndStop }); - // TODO: alert user about success - console.info('Workspace updated and stopped successfully:', JSON.stringify(response)); + await executeAction({ action: 'updateAndStop', callback: onUpdateAndStop }); + notification.info(`Workspace '${workspace?.name}' updated and stopped successfully`); onActionDone?.(); onClose(); } catch (error) { // TODO: alert user about error console.error('Error updating and stopping workspace:', error); } - }, [executeAction, onActionDone, onClose, onUpdateAndStop]); + }, [executeAction, onActionDone, onClose, onUpdateAndStop, notification, workspace]); const shouldShowActionButton = useCallback( (action: StopAction) => !actionOnGoing || actionOnGoing === action, diff --git a/workspaces/frontend/src/app/standalone/NavBar.tsx b/workspaces/frontend/src/app/standalone/NavBar.tsx new file mode 100644 index 00000000..77368d77 --- /dev/null +++ b/workspaces/frontend/src/app/standalone/NavBar.tsx @@ -0,0 +1,134 @@ +import React, { useState } from 'react'; +import { Brand } from '@patternfly/react-core/dist/esm/components/Brand'; +import { + Dropdown, + DropdownItem, + DropdownList, +} from '@patternfly/react-core/dist/esm/components/Dropdown'; +import { + Masthead, + MastheadBrand, + MastheadContent, + MastheadLogo, + MastheadMain, + MastheadToggle, +} from '@patternfly/react-core/dist/esm/components/Masthead'; +import { + MenuToggle, + MenuToggleElement, +} from '@patternfly/react-core/dist/esm/components/MenuToggle'; +import { PageToggleButton } from '@patternfly/react-core/dist/esm/components/Page'; +import { + Toolbar, + ToolbarContent, + ToolbarGroup, + ToolbarItem, +} from '@patternfly/react-core/dist/esm/components/Toolbar'; +import { SimpleSelect } from '@patternfly/react-templates'; +import { BarsIcon } from '@patternfly/react-icons/dist/esm/icons/bars-icon'; +import { useNamespaceSelector, useModularArchContext } from 'mod-arch-core'; +import { useThemeContext } from 'mod-arch-kubeflow'; +import { images as sharedImages } from 'mod-arch-shared'; + +interface NavBarProps { + username?: string; + onLogout: () => void; +} + +const NavBar: React.FC = ({ username, onLogout }) => { + const { namespaces, preferredNamespace, updatePreferredNamespace } = useNamespaceSelector(); + const { config } = useModularArchContext(); + const { isMUITheme } = useThemeContext(); + + const [userMenuOpen, setUserMenuOpen] = useState(false); + + // Check if mandatory namespace is configured + const isMandatoryNamespace = Boolean(config.mandatoryNamespace); + + const options = namespaces.map((namespace) => ({ + content: namespace.name, + value: namespace.name, + selected: namespace.name === preferredNamespace?.name, + })); + + const handleLogout = () => { + setUserMenuOpen(false); + onLogout(); + }; + + const userMenuItems = [ + + Log out + , + ]; + + return ( + + + + + + + + {!isMUITheme ? ( + + + + + + ) : null} + + + + + + + { + // Only allow selection if not mandatory namespace + if (!isMandatoryNamespace) { + updatePreferredNamespace({ name: String(selection) }); + } + }} + /> + + + {username && ( + + + setUserMenuOpen(isOpen)} + toggle={(toggleRef: React.Ref) => ( + setUserMenuOpen(!userMenuOpen)} + isExpanded={userMenuOpen} + > + {username} + + )} + isOpen={userMenuOpen} + > + {userMenuItems} + + + + )} + + + + + ); +}; + +export default NavBar; diff --git a/workspaces/frontend/src/app/NavSidebar.tsx b/workspaces/frontend/src/app/standalone/NavSidebar.tsx similarity index 86% rename from workspaces/frontend/src/app/NavSidebar.tsx rename to workspaces/frontend/src/app/standalone/NavSidebar.tsx index c1ef5238..f8c4adc5 100644 --- a/workspaces/frontend/src/app/NavSidebar.tsx +++ b/workspaces/frontend/src/app/standalone/NavSidebar.tsx @@ -8,9 +8,10 @@ import { NavList, } from '@patternfly/react-core/dist/esm/components/Nav'; import { PageSidebar, PageSidebarBody } from '@patternfly/react-core/dist/esm/components/Page'; +import { useThemeContext, images as kubeflowImages } from 'mod-arch-kubeflow'; +import { isNavDataGroup, NavDataHref, NavDataGroup } from '~/app/standalone/types'; import { useTypedLocation } from '~/app/routerHelper'; -import { isMUITheme, LOGO_LIGHT, URL_PREFIX } from '~/shared/utilities/const'; -import { useNavData, isNavDataGroup, NavDataHref, NavDataGroup } from './AppRoutes'; +import { useNavData } from '~/app/AppRoutes'; const NavHref: React.FC<{ item: NavDataHref }> = ({ item }) => { const location = useTypedLocation(); @@ -51,23 +52,21 @@ const NavGroup: React.FC<{ item: NavDataGroup }> = ({ item }) => { const NavSidebar: React.FC = () => { const navData = useNavData(); - + const { isMUITheme } = useThemeContext(); return (