From e07cc59e7767794f87e0d5f996057686f864e205 Mon Sep 17 00:00:00 2001 From: Bhakti Narvekar Date: Sat, 14 Jun 2025 20:15:45 -0700 Subject: [PATCH 01/71] 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 000000000..178700084 --- /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 000000000..2a94da4fa --- /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 000000000..fde72648b --- /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/71] 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 8e3e259ec..7ff1aa4fc 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 13a168ebf..ba65dbd28 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 000000000..fb14174fa --- /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 4b8ce9280..c153af0a6 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/71] chore(ws): Upgrade vulnerable package webpack-dev-server (#407) Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> --- workspaces/frontend/package-lock.json | 637 ++++++++++++++++++-------- workspaces/frontend/package.json | 2 +- 2 files changed, 452 insertions(+), 187 deletions(-) diff --git a/workspaces/frontend/package-lock.json b/workspaces/frontend/package-lock.json index 68a58be26..c50e6cbd0 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==", - "dev": true, - "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", + "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.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 c0dbdd1ef..c02da03f4 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/71] 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 b9db0aae3..40608d81f 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 ad6777772..e7a07f61d 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 7036486bf..afbaba37c 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/71] 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 757ef1098..ec6d60e44 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 49a169ad8..50ed31d9c 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 de78bc9bc..6184369ca 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/71] 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 | 96 +++++++++---------- .../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, 269 insertions(+), 219 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 bb2e65372..fc6719dd1 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 000000000..031e312fc --- /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 c02da03f4..e699465a2 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 d25093142..318d7de32 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 04d42de05..4f1718c49 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 ec6d60e44..e64f1878a 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 53dbeeaf2..9fc3c24aa 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 50ed31d9c..e9b7195b5 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 40634a64c..09781c4d8 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 df17db283..2f660c72e 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 50bf64a78..ac0e9136b 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 3a5b20d1a..29d8830c3 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 66d84a20b..61ac57a59 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 dce1252b9..ff19100b3 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 852aedb1c..a830cb7c7 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 d07c0ba1b..1f62afeb6 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 468ed6697..4cf620a72 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 7ff1aa4fc..be25d8e48 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 4dd755cf9..85b00a8b3 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 942f24b6e..1c575b242 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 4db6cb2a6..d654bd922 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 b6347f9d3..00df848da 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 75894c7f8..5c6483163 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 d2e2c04ee..8358899c1 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 handleAttributeMenuKeys = React.useCallback( + 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 = 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 ba65dbd28..4e059e5ca 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 dd847eeed..80f4e3c47 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 6e7d8cc4b..0a5f60520 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 389df7abe..6b67b3496 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 5fd2e38c2..aac97ffc2 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 bcf41b89d..be13e07b4 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 b038291f4..4f89d3839 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 11aaf74e1..83ac839ac 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 e7a07f61d..9eb3c84be 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 a4385f3f5..342a48256 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 a70244581..62d28ccad 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 0d49c1309..d3c047447 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 d5429e0a5..fcc03a1b5 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 0874c8630..9feb73744 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 fa87ea033..ab2decb24 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 afbaba37c..2dcb70981 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 cd277b278..a50f6d24b 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 aa012a88c..507d9fc6f 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 41a200b4c..976d5df72 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 eb00e425d..51560c7dc 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 3cd8113c9..e8a4c2db6 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 e6c7ec879..507ffa82e 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 95fa6a86a..b740a5616 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 0d88f47c5..e1c365b60 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 fa9d87fe9..f97d7f7db 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 64984ef76..b93714388 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/71] 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 c50e6cbd0..f95aa15e8 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/71] 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 | 431 +++++------ .../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, 2777 insertions(+), 1295 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 fc6719dd1..53455a9c1 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/**"] - } - ] - } + patterns: ['~/api/**'], + }, + ], + }, }, { - "files": ["./src/__tests__/cypress/**/*.ts"], - "parserOptions": { - "project": ["./src/__tests__/cypress/tsconfig.json"] + 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" - ] + 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/**'], + 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 031e312fc..6542702ac 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 f95aa15e8..dea71525b 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 e699465a2..438f59d28 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 b784a2e2a..ce5a4ba3b 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 62d0f005c..0444639b6 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 06307a5eb..e268984d7 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 1a2a53578..9b468b13c 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 e64f1878a..f87ad52ca 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 000000000..60114e99c --- /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 000000000..9750a3926 --- /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 000000000..20e20c6ea --- /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 6cdec1dd3..d0661b7f1 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 000000000..7a01737e6 --- /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 000000000..5f3c3704a --- /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 000000000..1f510224b --- /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 000000000..e5507fa85 --- /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 be25d8e48..ccc1fbca5 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 85b00a8b3..4a741b7a5 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 000000000..06190656b --- /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 00df848da..998f586c4 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 8358899c1..84f63f361 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 4e059e5ca..43e711e12 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 000000000..0b542fb7c --- /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 000000000..1f456b7f0 --- /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 000000000..6d3c9b93b --- /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 84f63f361..b16ecb659 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 0eab5cc5a..ee0cf4d5a 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 ecf41078a..ccc1523b6 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 6184369ca..ec9aa1579 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/71] 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 000000000..ab36af88c --- /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 dea71525b..a3b9b84d5 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 55680521d..ca879d9ef 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/71] 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 53455a9c1..09f4cb08c 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 000000000..7643244e7 --- /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 000000000..40b074484 --- /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 a3b9b84d5..feb1e0ebd 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 ca879d9ef..8e08b930d 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 e9b7195b5..f1deb8927 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 340778239..69da59cfb 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 4b600c469..332b473d2 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/71] 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 | 117 +++++++++++++----- .../webhook/workspace_webhook_test.go | 82 ++++++++++++ .../internal/webhook/workspacekind_webhook.go | 38 +++++- .../webhook/workspacekind_webhook_test.go | 36 ++++++ 5 files changed, 261 insertions(+), 34 deletions(-) diff --git a/workspaces/controller/internal/webhook/suite_test.go b/workspaces/controller/internal/webhook/suite_test.go index ba438e761..b0a652cf5 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 ca6664382..5143fc6ff 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 { +// 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 + } + + // 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 +} + +// 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 { - // imageConfig found - return nil + foundImageConfig = true + break } } - 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), - ) + 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 checks if the podConfig selected by a Workspace exists a WorkspaceKind -func (v *WorkspaceValidator) validatePodConfig(workspace *kubefloworgv1beta1.Workspace, workspaceKind *kubefloworgv1beta1.WorkspaceKind) *field.Error { +// 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 { - // podConfig found - return nil + foundPodConfig = 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 !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 e6efc213c..64ebd7e60 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 4c591731e..e7fd49a38 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 f53e34f2f..dd2c321ae 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/71] 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 959dbe3d3..fc0869b91 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 b19fc3276..c8e0b6afb 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 a64c04c70..2cf909fd4 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 f255e86d3..e0fc73e4e 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 ef67382c0..4f9fdeffa 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 9cae76367..000000000 --- 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/71] 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 1f935d2b3..5e3b5becc 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/71] 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 faf349621..ad3e518ad 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 2d846237d..7e7952a02 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 71ed533b4..d7bb858b2 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 f89afed2e..fbc5a5918 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 b0a652cf5..2eee78390 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/71] 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 000000000..48a83b387 --- /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 000000000..c52347743 --- /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 000000000..2b20df062 --- /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 f0904f519..656608ced 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/71] 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 000000000..ae48c79b4 --- /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 a091f2933..53d3ad948 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 fc0869b91..559259771 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/71] 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 1b09158c2..cccc87ebf 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 20e20c6ea..a2c118667 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 b16ecb659..f1927d1fa 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 bf50e0943..30240e622 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 000000000..2002cfa92 --- /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 000000000..3743b53ba --- /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/71] feat: refactor Form View to Edit only (#451) Signed-off-by: Charles Thao --- .../WorkspaceKinds/Form/WorkspaceKindForm.tsx | 92 +++++++++++-------- .../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, 102 insertions(+), 43 deletions(-) diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/WorkspaceKindForm.tsx index c95ef8a46..a744cd0f2 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(() => { + 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); // TODO: Complete handleCreate with API call to create a new WS kind - if (!Object.keys(data).length) { - return; + 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); } - setIsSubmitting(true); - }, [data]); + 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 3d90488b5..e1c5c6daa 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 f1beda3b8..ca42d6666 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 17aa6844f..d57a118f2 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 457a310a9..81fdbfcc2 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/71] 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 f1927d1fa..4b44ee5d7 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/71] 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 be13e07b4..cf377e9f9 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/71] 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 000000000..653b92634 --- /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 c26d7ed28..0251d74ed 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 aeab95f3b..5d7348c29 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 a744cd0f2..2666f54be 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 000000000..62137aea8 --- /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 eec7ccbca..eae9c918a 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 39ce8bf5a..aad5f6226 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 097300624..99a7a22b1 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 000000000..3c8ab7bb4 --- /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 000000000..400d1da04 --- /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 000000000..4d7ca6834 --- /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 000000000..19c8d0edd --- /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 ee0cf4d5a..8379d179f 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 ccc1523b6..d0dcaf9be 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 ec9aa1579..6084f158d 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 a732b4af1..ff07e661b 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 9efb05d0a..5017cf6ed 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/71] 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 8d7edca3e..5e7882116 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/71] 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 ef3fe7db1..2f76c2527 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 0076a0604..7583ea70b 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 cf7e1dd90..b9aadba33 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 ad3e518ad..ce03bdada 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 c8e0b6afb..85bd5a628 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 f37c58c98..198536767 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 2cf909fd4..97354b123 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 179b91b81..10f1b445a 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 3796e2976..02cd208b9 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 e0fc73e4e..ba09bcc6a 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 4f9fdeffa..31c2b5548 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/71] 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 f1deb8927..0bd4ac814 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 000000000..e2691b2ab --- /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 2666f54be..feb1ba68a 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 eae9c918a..bbd78ee29 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 168708903..099905e59 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 d84aa9a98..27a650b5a 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 e1c5c6daa..6993cc454 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 ca42d6666..e84ca6b5d 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 47046554c..9205a8f0b 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 81fdbfcc2..5cdd91bba 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 6f1ac507d..71389f369 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 91397c9e2..0bb63e16f 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 000000000..c4af8e1ac --- /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 6084f158d..ebc7263ba 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/71] 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 | 1077 ++++++++++++++++- 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, 1093 insertions(+), 70 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 09f4cb08c..29f4f8bb3 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 000000000..6b02547dc --- /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 000000000..9efe8460c --- /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 000000000..3a9e1a3d7 --- /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 feb1e0ebd..a283be974 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", @@ -7181,20 +7812,50 @@ "node": ">=8" } }, - "node_modules/cjs-module-lexer": { - "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 - }, - "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==", + "node_modules/cjs-module-lexer": { + "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 + }, + "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", + "engines": { + "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": ">=6" + "node": ">=8" } }, "node_modules/cli-boxes": { @@ -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 8e08b930d..7adc84709 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 5d7348c29..f738e9565 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 e8a4c2db6..9e376102e 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 f97d7f7db..1ac130679 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 002d12792..2ba0862ae 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 ebc7263ba..ec4c13961 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 8765f0941..a4fe30493 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/71] 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 7adc84709..a4e193924 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/71] 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 e268984d7..528ca287d 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 6e4011395..35a29d3e0 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 9b468b13c..748f4dd7e 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 a2c118667..6254ff1cb 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 5f3c3704a..ed7c5891a 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 164fee2c5..f09d1102a 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 80f4e3c47..9c2a87bfa 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 4d53caf88..bb122c4fc 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 000000000..b59873e26 --- /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 ab2decb24..efc59fe7d 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 000000000..1eb96feea --- /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 000000000..9109bdf0e --- /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 2ba0862ae..3efcad002 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 ec4c13961..a50ae5b25 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 ff07e661b..cc08a44ca 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/71] 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 a283be974..f8702895d 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 a4e193924..d79141af5 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/71] 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 cf377e9f9..89a29825f 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 feb1ba68a..0972c68d6 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 { kind } = useTypedParams<'workspaceKindEdit'>(); + const [initialFormData, initialFormDataLoaded, initialFormDataError] = + useWorkspaceKindByName(kind); + + const [data, setData, resetData, replaceData] = useGenericObjectState( + initialFormData ? convertToFormData(initialFormData) : EMPTY_WORKSPACE_KIND_FORM_DATA, + ); - const [data, setData, resetData] = useGenericObjectState({ - properties: { - displayName: '', - description: '', - deprecated: false, - deprecationMessage: '', - hidden: false, - icon: { url: '' }, - logo: { url: '' }, - }, - imageConfig: { - default: '', - values: [], - }, - podConfig: { - default: '', - values: [], - }, - }); + 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 000000000..d7887f2fe --- /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 aad5f6226..786670e7e 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 ca6255857..9f50e5a05 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 99a7a22b1..607d3aadf 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 3c8ab7bb4..5c0371b3a 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 400d1da04..5f18678f5 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 8127f3c40..08106117a 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 9feb73744..7fb0b1184 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 7929fdef3..ca6c76fb8 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 a461e76ae..7ca13f4ba 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 000000000..12b3b5971 --- /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/71] 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 89a29825f..78d4d709e 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 26221492e..1b6eb14da 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 83ac839ac..02fc9556a 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 6edbf7b0f..105a48902 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 342a48256..17bf4dd85 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 dee1dd204..bf7f3fb55 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 d3c047447..2aac1cff3 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/71] 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 | 27 ++++++++------- .../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, 391 insertions(+), 343 deletions(-) diff --git a/workspaces/frontend/.eslintrc.js b/workspaces/frontend/.eslintrc.js index 29f4f8bb3..b44290b06 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 3ff011f6d..239beb138 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 9fc3c24aa..53d16a1e8 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 0bd4ac814..bad77bd89 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 60114e99c..2ca06b987 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 9750a3926..945bf967f 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 967e2d9f1..ce82341a0 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 417969b9c..443908595 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 6254ff1cb..184e8408d 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 7a01737e6..d9a70d548 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 29d8830c3..e397abb90 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 61ac57a59..551c37679 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 ff19100b3..d3cee49ca 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 06190656b..4787107eb 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 5c6483163..3c59c3c7f 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 98b1d4dfe..a9858f9c3 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 d99bdb8e7..c17a78c25 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 d7887f2fe..c503c446d 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 bbd78ee29..bd0b5d1da 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 9f50e5a05..05c4982f9 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 607d3aadf..284b0fdb5 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 0b542fb7c..2f810000b 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 1f456b7f0..f250624f3 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 5c0371b3a..715cb39b9 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 5f18678f5..8e70a3d98 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 4d7ca6834..cd979a5c8 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 08106117a..b4f224ba1 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 a0307077e..a6c69821a 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 6d3c9b93b..652156a31 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 56026914c..daea4356b 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 7a73090fb..ebaf05316 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 02cca47c2..d5aa3e8f7 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 12b3b5971..5ee7bc160 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 f09d1102a..8a6e02dca 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 4aa85d6c5..a1c3bee87 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 9c2a87bfa..d509e0784 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 0a5f60520..226291c2d 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 6b67b3496..1c770dca7 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 befa43dc0..30601dcee 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 6b00b3833..55f5ae926 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 bb122c4fc..38c1b02a2 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 78d4d709e..ae39ca00d 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 { + ProgressStep, + ProgressStepper, +} from '@patternfly/react-core/dist/esm/components/ProgressStepper'; +import { Stack } from '@patternfly/react-core/dist/esm/layouts/Stack'; import { - Button, - Content, Drawer, + DrawerActions, + DrawerCloseButton, DrawerContent, DrawerContentBody, - DrawerPanelContent, DrawerHead, - DrawerActions, - DrawerCloseButton, DrawerPanelBody, - Flex, - FlexItem, - PageSection, - ProgressStep, - ProgressStepper, - Stack, - Title, -} from '@patternfly/react-core'; + 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 8338e981b..46c58c62b 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 1b6eb14da..d47a85d2a 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 bce20293a..08e392058 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 02fc9556a..aaf807c96 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 105a48902..2363bf974 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 30240e622..8530bcabf 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 17bf4dd85..9903abaf0 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 bf7f3fb55..d3a9ba8e4 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 810ac3dd0..06b9aa8b4 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 2aac1cff3..dfe4b8439 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 4f667bdb2..96f1db95f 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 fcc03a1b5..d3c64b834 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 7fb0b1184..780e4ca72 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 b59873e26..1270943c9 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 efc59fe7d..4b231354a 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 1eb96feea..48e187b8f 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 9109bdf0e..9f55940ac 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 cf611bfe4..e77497511 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 a50f6d24b..e3086a2f1 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 507d9fc6f..d2eccc40b 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 d25d8c476..bbd0c532f 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 12edb5a94..3375b6dd8 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 9e376102e..60f61be7b 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 b740a5616..ea813c925 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 e1c365b60..ef5bf2fbb 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 1ac130679..f5d8efd9f 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 5e7882116..a2c595701 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 2002cfa92..dae90fd88 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 039c27bae..0b6dce7f6 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 3743b53ba..f4f72f3d7 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/71] 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 2d7b91dc8..3e2c72b96 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/71] 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 106ad8fc5..f630eaba6 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 0251d74ed..6b0136b4b 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 184e8408d..8be07a03d 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 d9a70d548..aac7efd9c 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 652156a31..4aafe35ea 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 daea4356b..d0868e40f 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 ebaf05316..aaa7a150b 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 d5aa3e8f7..7a0b58d83 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 a1c3bee87..b5415036c 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/71] 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 ce03bdada..9c0c77fef 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 98c77f0f4..268b458ed 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 c7947ea8a..2996f4666 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 ba09bcc6a..7fa3f97dd 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 31c2b5548..b5c0e0857 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/71] 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 000000000..763301fc0 --- /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 000000000..32a729c6a --- /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 000000000..60ade5887 --- /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/71] 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 f5cbd683f..b9517b601 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 2f76c2527..e533eac8c 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 000000000..75e1001d0 --- /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 000000000..57b709585 --- /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 000000000..ce048d3f3 --- /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 000000000..6716b740e --- /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 82f3eada7..9b0743aab 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 7fa3f97dd..d4324874c 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 b5c0e0857..ff81e8bf2 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/71] 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 aac7efd9c..57349d354 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 ac0e9136b..6b2e512e7 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 e84ca6b5d..3f8330584 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 04a09f2a5..72a8d6bc1 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 d57a118f2..bf2f2bced 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 8bdaeccce..7df0890d1 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 3efcad002..e15d045c6 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 0bb63e16f..78767f033 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 6c6892954..8ff2e1811 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/71] 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 | 311 ++++++++++++------ .../pages/WorkspaceKinds/WorkspaceKinds.tsx | 273 ++++----------- .../summary/WorkspaceKindSummary.tsx | 15 +- .../WorkspaceKindSummaryExpandableCard.tsx | 9 +- .../frontend/src/shared/style/MUI-theme.scss | 34 +- 6 files changed, 327 insertions(+), 323 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 748f4dd7e..968d22697 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 8be07a03d..a29bf1bb2 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; + const testRegex = (value: string, searchValue: string) => { + if (!searchValue) { + return true; + } try { - searchValueInput = new RegExp(filter.value, 'i'); + return new RegExp(searchValue, 'i').test(value); } catch { - searchValueInput = new RegExp(filter.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'); + return new RegExp(searchValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i').test(value); } + }; + + const activeFilters = Object.entries(filters).filter(([, value]) => value); + if (activeFilters.length === 0) { + return workspaces; + } - 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]); + 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 d0868e40f..ea3bff295 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 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 onStatusSelect = ( + _event: React.MouseEvent | undefined, + value: string | number | undefined, + ) => { + if (typeof value === 'undefined') { + return; + } + setStatusSelection(value.toString()); + setIsStatusMenuOpen(false); + }; + + 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 c1f2b94a9..18367fca1 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 66e4af9d5..6373c0599 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 ddaf6135b..9d98e53ce 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/71] feat(ws): fix workspaces table pagination (#506) Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> --- .../src/app/components/WorkspaceTable.tsx | 238 +++++++++--------- 1 file changed, 120 insertions(+), 118 deletions(-) diff --git a/workspaces/frontend/src/app/components/WorkspaceTable.tsx b/workspaces/frontend/src/app/components/WorkspaceTable.tsx index a29bf1bb2..44c86006c 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)), - }} - /> - )} - {visibleColumnKeys.map((columnKey) => { - if (columnKey === 'connect') { - return ( - - - - ); - } + + {canExpandRows && ( + + setWorkspaceExpanded(workspace, !isWorkspaceExpanded(workspace)), + }} + /> + )} + {visibleColumnKeys.map((columnKey) => { + if (columnKey === 'connect') { + return ( + + + + ); + } + + if (columnKey === 'actions') { + return ( + + ({ + ...action, + 'data-testid': `action-${action.id || ''}`, + }))} + /> + + ); + } - if (columnKey === 'actions') { 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 + ?.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, + })} + + )} ); - } - - 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) && ( - - )} - - ))} + })} + + {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/71] 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 09781c4d8..c62a19811 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 000000000..ff7409700 --- /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 ccc1fbca5..3a5f766a3 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 5ee7bc160..58ed429b3 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 3f8330584..643c300a1 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 e15d045c6..8b6eecccd 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 8ff2e1811..fdbf7de08 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/71] 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 e533eac8c..0500e70f0 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 69a1b78ae..cdd7ff88a 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 9405c2e03..69773f2f0 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 75e1001d0..0b1f54fa9 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 85bd5a628..b995a4002 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 198536767..464659272 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 97354b123..a03f7c4ad 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 10f1b445a..23a35f671 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 d4324874c..616bb409f 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 ff81e8bf2..6260cf1b3 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/71] 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 559259771..d139d313f 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 ab546a803..ef15dbbc0 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 4e67123cf..8d0397b13 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 616bb409f..d0b290754 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 6260cf1b3..33669b8a0 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/71] 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 226291c2d..b5598a2fe 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/71] 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 | 77 +-- .../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, 2280 insertions(+), 1575 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 b43fbf1d2..c2477b62c 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 3e2c72b96..5687c07da 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 4654bceb0..a8e294686 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 000000000..c894fcd82 --- /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 000000000..21ab894be --- /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 6265e681b..2063b3f05 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 d4af2e359..18538b431 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 0444639b6..a3e2196ca 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 4d0a48615..d1dfa27ee 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 35a29d3e0..a593f4f62 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 40608d81f..068f19d7e 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 44c86006c..7c813f05d 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 57349d354..75a2a7ee9 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 6b2e512e7..0fc313acb 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 ff7409700..4e0ec931a 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 1f62afeb6..0e1607133 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 3a5f766a3..22c06d331 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 4a741b7a5..88b9acc61 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 1c575b242..b5ebac446 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 d654bd922..99d64e9b7 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 4787107eb..1b58f7742 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 998f586c4..5ce9d3202 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 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], - ); + const call = useCallback< + FetchStateCallbackPromise + >(async () => { + if (!apiAvailable) { + throw new Error('API not yet available'); + } + if (!kind) { + throw new Error('Workspace kind is required'); + } + + const envelope = await api.workspaces.listAllWorkspaces(); + + 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 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 a9858f9c3..5f52ea785 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 c17a78c25..449b49ce1 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 c503c446d..4d0ae134f 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 786670e7e..f768d7680 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 f250624f3..62cad6334 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 a6c69821a..7246a881c 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 ea3bff295..3f212a171 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 aaa7a150b..23eb91556 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 6822982a4..09e4cab33 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 ca6c76fb8..336bce1bf 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 7a0b58d83..c5bf8c1f5 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 7ca13f4ba..0abea53f8 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 6373c0599..7c3a82f5b 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 d509e0784..c9f27f696 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 b5598a2fe..9907f5d7d 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 17a4b4116..f47c0729e 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 55f5ae926..af902b20d 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 38c1b02a2..f8edeb800 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 ae39ca00d..f4cdb8b28 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 d47a85d2a..d40c45810 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 08e392058..e36d1d5fe 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 aaf807c96..6123161f8 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 2363bf974..7a161efc5 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 8530bcabf..bac0154fb 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 9903abaf0..30db818d9 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 6303747a5..8ab9586e6 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 d3a9ba8e4..fe829a4ee 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 06b9aa8b4..7c200ec75 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 dfe4b8439..a82a28704 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 96f1db95f..66e07ab46 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 d3c64b834..f1e6e3ae3 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 780e4ca72..4b44a76de 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 1270943c9..83485ff14 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 4b231354a..428a85025 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 9f55940ac..a6bd3da41 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 0c76e3731..3e461396e 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 72b2786f9..4d50ff5a9 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 3e3417017..8a641a5d4 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 bbd0c532f..1bbfcaf47 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 631724ba2..793efddce 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 b87f7f8a7..b9c68f39e 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 000000000..d8b109453 --- /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 000000000..f626fc920 --- /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 000000000..4b250e178 --- /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 000000000..4c616f6cf --- /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 000000000..12a6a45e3 --- /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 000000000..911e057f0 --- /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 099905e59..000000000 --- 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 27a650b5a..000000000 --- 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 09c925e8c..196f9ea9a 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; +function isApiErrorEnvelope(data: unknown): data is ApiErrorEnvelope { + if (typeof data !== 'object' || data === null || !('error' in data)) { + return false; } ->; - -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; + const { error } = data as { error: unknown }; - 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); + if (typeof error !== 'object' || error === null || !('message' in error)) { + 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; - }), - ); -}; - -export const restGET = ( - host: string, - path: string, - queryParams: Record = {}, - options?: APIOptions, -): Promise => - callRestJSON(host, path, mergeRequestInit(options, { method: 'GET' }), { - queryParams, - parseJSON: options?.parseJSON, - }); + const { message } = error as { message?: unknown }; -/** 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; - } - 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 { - // 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 643c300a1..000000000 --- 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 72a8d6bc1..000000000 --- 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 9205a8f0b..000000000 --- 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 000000000..14e7d1c44 --- /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 bf2f2bced..ba4efb0bb 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 7df0890d1..000000000 --- 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 7c2bad1a6..6685d1773 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 RequestData = { - data: T; +export type WithExperimental = TBase & { + experimental: TExperimental; }; + +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 8b6eecccd..ef4434b26 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 000000000..3947b6ee8 --- /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 78767f033..000000000 --- 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 fdbf7de08..8a2cf687a 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 c4af8e1ac..789ab2f6b 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 f1ee0bc97..8da8f10a1 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 6b3c5d9b2..1ddc2224b 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/71] 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 cfa03d366..bbbba4857 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 9283bfc15..0fb6bdee2 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 a8e294686..0855b3e81 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 349ff312b..3708b3d9d 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 ce5a4ba3b..4bbfcdc28 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 528ca287d..73648410e 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 bad77bd89..d4f5e272b 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 d6b28baab..e460f04e1 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 7bb65e462..10ba5a766 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/71] 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 eb6f453dd..b0c839eaa 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 0855b3e81..e6419766b 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 239beb138..df3210916 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 9d98e53ce..aacf6428b 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/71] 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 5e3b5becc..e34b7cfb3 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/71] 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 d4f5e272b..3fd906315 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 d0661b7f1..04f215bc0 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 2f660c72e..5cd350538 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 10ba5a766..3ad481cec 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/71] 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 e36d1d5fe..ddf5c4aa8 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 6123161f8..fc43e3789 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 8ab9586e6..bfe31b1f4 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 7c200ec75..87a36fece 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 a82a28704..f11b4b09e 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/71] 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 000000000..ae6e5a8d1 --- /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 000000000..cdee8730b --- /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 000000000..74c8618f7 --- /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 000000000..79d84ff50 --- /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 000000000..32e1bd1be --- /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 000000000..3f7ec1818 --- /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 000000000..88fd83394 --- /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 000000000..4637bb693 --- /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 000000000..16d9779bd --- /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 000000000..cd125d438 --- /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 88b9acc61..974299861 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 ef4434b26..bb539c0ff 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/71] 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 5687c07da..1b6c4ad9f 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 e6419766b..bf5e5ca4a 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/71] 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 18367fca1..7cf7d3de6 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/71] 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 d139d313f..ed264a7ad 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 000000000..db4a891cc --- /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 000000000..6162f9d3e --- /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 000000000..0076fabf9 --- /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 000000000..4a9cd586c --- /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 000000000..8189c39d0 --- /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 000000000..5e2117534 --- /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 000000000..2e2d28f43 --- /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 000000000..bcf14cc7b --- /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 000000000..4549f6fd3 --- /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 000000000..b6c91830d --- /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 000000000..a73f511a5 --- /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 000000000..1b25c6176 --- /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/71] 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 000000000..c57639858 --- /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 000000000..8ff7a35ff --- /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 000000000..0076fabf9 --- /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 000000000..34ba13dc6 --- /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 000000000..a80030c5d --- /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 000000000..29ced65b1 --- /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 000000000..d1ef05f11 --- /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 000000000..7ac2400f1 --- /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 000000000..edf17a55b --- /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 000000000..84f7c9594 --- /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/71] 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 | 379 +- workspaces/frontend/config/webpack.dev.js | 187 +- workspaces/frontend/config/webpack.prod.js | 83 +- workspaces/frontend/package-lock.json | 9668 ++++++++++------- workspaces/frontend/package.json | 150 +- .../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, 6401 insertions(+), 4578 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 000000000..112ddcadf --- /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 60f8faee6..b4c03de24 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 000000000..9fc5aa7af --- /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 000000000..a904f4a9f --- /dev/null +++ b/workspaces/frontend/.env.production @@ -0,0 +1 @@ +APP_ENV=production diff --git a/workspaces/frontend/.gitignore b/workspaces/frontend/.gitignore index 27c704f85..574a1040e 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 c2477b62c..da872f10d 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 000000000..009c234e1 --- /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 6b02547dc..f4d8a0ad5 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 000000000..9f245dbb9 --- /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 1f8a6b3f6..fb578e4f5 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 bbbba4857..f772c6592 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: /\.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', + }, + ], + }, + 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, }, { - 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: {}, - }, + from: path.join(SRC_DIR, 'favicons'), + to: path.join(DIST_DIR, 'favicons'), + noErrorOnMissing: true, }, { - 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]', - }, - }, - ], + from: path.join(SRC_DIR, 'images'), + to: path.join(DIST_DIR, 'images'), + noErrorOnMissing: true, }, { - 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', - ], + from: path.join(SRC_DIR, 'favicon.ico'), + to: path.join(DIST_DIR), + noErrorOnMissing: true, }, { - 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', - ), - ], + 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, }, ], + }), + ], + resolve: { + extensions: ['.js', '.ts', '.tsx', '.jsx'], + alias: { + '~': path.resolve(SRC_DIR), }, - 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', - }), - ], - resolve: { - extensions: ['.js', '.ts', '.tsx', '.jsx'], - plugins: [ - new TsconfigPathsPlugin({ - configFile: path.resolve(relativeDir, './tsconfig.json'), - }), - ], - symlinks: false, - cacheWithContext: false, - }, - }; -}; + symlinks: false, + cacheWithContext: false, + }, +}); diff --git a/workspaces/frontend/config/webpack.dev.js b/workspaces/frontend/config/webpack.dev.js index 0fb6bdee2..d3e00a9ee 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', - }, - open: [APP_PREFIX], - static: { - directory: path.resolve(relativeDir, 'dist'), - publicPath: APP_PREFIX, - }, - client: { - overlay: true, +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, + }), + ], }, - 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 b0c839eaa..a14ef40f4 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 1b6c4ad9f..9ad96a677 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", - "dependencies": { - "@babel/parser": "^7.26.2", - "@babel/types": "^7.26.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dependencies": { + "@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/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "optional": true, + "dependencies": { + "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.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "license": "MIT", - "peer": true, + "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.13.0", + "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.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.25.9" + "@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.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-plugin-utils": "^7.25.9" + "@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.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-plugin-utils": "^7.25.9" + "@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, + "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==", "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" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "eslint-visitor-keys": "^3.4.3" }, - "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,278 +5097,266 @@ "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": { - "@babel/runtime": "^7.14.6", - "@testing-library/dom": "^10.1.0" + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.23" }, "engines": { - "node": ">=12", - "npm": ">=6" + "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": { - "cypress": "^12.0.0 || ^13.0.0" + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } } }, - "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==", + "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, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=18" + "node": ">=10" } }, - "node_modules/@testing-library/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==", + "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, - "dependencies": { - "color-convert": "^2.0.1" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=10" } }, - "node_modules/@testing-library/dom/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@testing-library/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==", + "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, - "dependencies": { - "color-name": "~1.1.4" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=7.0.0" + "node": ">=10" } }, - "node_modules/@testing-library/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/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==", + "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": ">=8" + "node": ">=10" } }, - "node_modules/@testing-library/dom/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==", + "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, - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=10" } }, - "node_modules/@testing-library/dom/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==", + "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" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@testing-library/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": { - "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, - "dependencies": { - "@adobe/css-tools": "^4.0.1", - "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", - "aria-query": "^5.0.0", - "chalk": "^3.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.5.6", - "lodash": "^4.17.15", - "redent": "^3.0.0" - }, - "engines": { - "node": ">=8", - "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==", + "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, - "dependencies": { - "color-convert": "^2.0.1" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=10" } }, - "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==", + "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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8" + "node": ">=10" } }, - "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==", + "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, - "dependencies": { - "color-name": "~1.1.4" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=7.0.0" + "node": ">=10" } }, - "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==", + "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/@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==", + "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": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@swc/counter": "^0.1.3" } }, - "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, + "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.12.5", - "@testing-library/dom": "^9.0.0", - "@types/react-dom": "^18.0.0" + "@babel/runtime": "^7.14.6", + "@testing-library/dom": "^10.1.0" }, "engines": { - "node": ">=14" + "node": ">=12", + "npm": ">=6" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "cypress": "^12.0.0 || ^13.0.0 || ^14.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, + "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==", "license": "MIT", + "optional": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", - "aria-query": "5.1.3", + "aria-query": "5.3.0", "chalk": "^4.1.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "pretty-format": "^27.0.2" }, "engines": { - "node": ">=14" + "node": ">=18" } }, - "node_modules/@testing-library/react/node_modules/ansi-styles": { + "node_modules/@testing-library/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, - "license": "MIT", + "optional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -5208,22 +5367,11 @@ "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": { + "node_modules/@testing-library/dom/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" @@ -5235,12 +5383,11 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@testing-library/react/node_modules/color-convert": { + "node_modules/@testing-library/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, - "license": "MIT", + "optional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -5248,29 +5395,26 @@ "node": ">=7.0.0" } }, - "node_modules/@testing-library/react/node_modules/color-name": { + "node_modules/@testing-library/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, - "license": "MIT" + "optional": true }, - "node_modules/@testing-library/react/node_modules/has-flag": { + "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, - "license": "MIT", + "optional": true, "engines": { "node": ">=8" } }, - "node_modules/@testing-library/react/node_modules/pretty-format": { + "node_modules/@testing-library/dom/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", + "optional": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -5280,12 +5424,11 @@ "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": { + "node_modules/@testing-library/dom/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", + "optional": true, "engines": { "node": ">=10" }, @@ -5293,12 +5436,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@testing-library/react/node_modules/supports-color": { + "node_modules/@testing-library/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, - "license": "MIT", + "optional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -5306,11 +5448,63 @@ "node": ">=8" } }, + "node_modules/@testing-library/jest-dom": { + "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.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "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": "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" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@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" + }, + "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,47 +5749,64 @@ } }, "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/js-yaml": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", - "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", - "license": "MIT" - }, - "node_modules/@types/jsdom": { - "version": "20.0.1", + "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": { + "version": "20.0.1", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", "dev": true, @@ -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,23 +6133,39 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "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/tsconfig-utils": "^8.40.0", + "@typescript-eslint/types": "^8.40.0", + "debug": "^4.3.4" }, - "peerDependenciesMeta": { - "typescript": { - "optional": 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/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, + "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.17.0", - "@typescript-eslint/visitor-keys": "8.17.0" + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5917,17 +6175,33 @@ "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.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, + "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/typescript-estree": "8.17.0", - "@typescript-eslint/utils": "8.17.0", + "@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": "^1.3.0" + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5937,20 +6211,15 @@ "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/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/@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": { + "@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.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", - "dev": true, + "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.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@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==", - "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" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" + "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": { + "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": "^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/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": { + "stackframe": "^1.3.4" + } + }, "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", + "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.1", - "arraybuffer.prototype.slice": "^1.0.3", + "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" @@ -11388,167 +12020,426 @@ "node": ">=0.10" } }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "devOptional": true, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "devOptional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "devOptional": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "devOptional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "devOptional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "devOptional": true, + "engines": { + "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==", + "dev": 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", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter2": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", + "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", + "license": "MIT", + "optional": true + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", + "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", + "devOptional": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "devOptional": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "license": "MIT", + "optional": true, + "dependencies": { + "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/executable/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "devOptional": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "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": "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": "^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": ">=4.0" + "node": ">=8" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "devOptional": true, + "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": { - "estraverse": "^5.2.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=4.0" + "node": ">=7.0.0" } }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "devOptional": true, - "engines": { - "node": ">=4.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/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "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": ">=4.0" + "node": ">=8" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "devOptional": true, + "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": ">=0.10.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.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" + "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" }, - "funding": { - "url": "https://github.com/eta-dev/eta?sponsor=1" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "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, - "license": "MIT", + "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": ">= 0.6" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/eventemitter2": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", - "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", - "dev": true, - "license": "MIT" - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "node_modules/events": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", - "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", + "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": ">=0.8.x" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "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": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "@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": ">=10" + "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/sindresorhus/execa?sponsor=1" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/executable": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", - "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "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, - "license": "MIT", "dependencies": { - "pify": "^2.2.0" + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" }, "engines": { - "node": ">=4" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/executable/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "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, - "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } + "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": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "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": { - "@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" + "has-flag": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "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", @@ -13576,38 +14568,23 @@ }, "node_modules/ipaddr.js": { "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" - } - }, - "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==", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", "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": ">= 10" } }, "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": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "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==" + }, + "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": "^14 || ^16 || >=18.0" + "node": ">=18" }, - "peerDependencies": { - "postcss": "^8.2.15" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stylis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", - "devOptional": true - }, "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": { + "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/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.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", - "dev": true, + "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.20", + "@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,34 +24334,62 @@ "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", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=14.17" + "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_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": ">=18.0.0" } }, - "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 bf5e5ca4a..b1e52e3b4 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", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", "axios": "^1.10.0", "date-fns": "^4.1.0", - "eslint-plugin-local-rules": "^3.0.2", + "classnames": "^2.2.6", + "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" }, "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" } } diff --git a/workspaces/frontend/src/__tests__/cypress/cypress.config.ts b/workspaces/frontend/src/__tests__/cypress/cypress.config.ts index 3708b3d9d..319115b80 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 4bbfcdc28..ce5a4ba3b 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 73648410e..528ca287d 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 000000000..53d0c6c35 --- /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 05995b974..a157b6b77 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 df3210916..d51be6ea4 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 6b0136b4b..ea9a2ba9d 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 3fd906315..c1ef52385 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 ce82341a0..d916b8d1b 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 7c813f05d..b12db8cb7 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 04f215bc0..000000000 --- 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 5cd350538..ba26a04a0 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 0fc313acb..fa8db8b1e 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 449b49ce1..472098045 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 09e4cab33..1ed3e0106 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 3e461396e..39ffae42e 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 332b473d2..64446ff85 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 3ad481cec..4c20fbdc7 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 a590b848b..887c71148 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 1ddc2224b..1fcd382a3 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 000000000..dab53f8a5 --- /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 1a9726b8f..406515c8f 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/71] 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 ab36af88c..89cdd194e 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/71] 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 574a1040e..f059546c5 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 000000000..a7758ae50 --- /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/71] 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 | 3293 +++++++++++++---- 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 | 22 +- .../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, 3183 insertions(+), 2613 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 112ddcadf..1d21e59fe 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 b4c03de24..39acdc86a 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 9fc5aa7af..c1a92ed30 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 000000000..cc39dc843 --- /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 000000000..7ee8755d5 --- /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 000000000..f053ebf79 --- /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 f772c6592..d0c682ca6 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 d3e00a9ee..777d55e1d 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 a14ef40f4..4746bb8e5 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 01c94bcd0..37fbce0f2 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 9ad96a677..5178a6743 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,299 +3048,1189 @@ "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", "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "devOptional": true, "dependencies": { - "eslint-visitor-keys": "^3.4.3" + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", + "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", + "devOptional": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "devOptional": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "devOptional": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "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==", + "devOptional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "devOptional": true, + "engines": { + "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==", + "dev": true + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "devOptional": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "devOptional": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "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==", + "devOptional": true + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "devOptional": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "devOptional": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "devOptional": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "devOptional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "devOptional": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/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==", + "devOptional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "devOptional": 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/@jest/console/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==", + "devOptional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/console/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==", + "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==", + "devOptional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console/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==", + "devOptional": true, + "dependencies": { + "has-flag": "^4.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=8" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "devOptional": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", - "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", + "node_modules/@jest/core/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==", + "devOptional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "devOptional": 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/@jest/core/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==", + "devOptional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/core/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==", + "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==", + "devOptional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/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==", + "devOptional": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "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==", + "devOptional": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "devOptional": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "devOptional": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "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==", + "devOptional": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "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==", "devOptional": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "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": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "devOptional": true, "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/@jest/reporters/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==", "devOptional": true, "dependencies": { - "type-fest": "^0.20.2" + "color-convert": "^2.0.1" }, "engines": { "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "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==", + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "devOptional": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", - "devOptional": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "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==", - "dev": true - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "node_modules/@jest/reporters/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==", "devOptional": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "color-name": "~1.1.4" }, "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "devOptional": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=7.0.0" } }, - "node_modules/@humanwhocodes/object-schema": { - "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==", + "node_modules/@jest/reporters/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==", "devOptional": true }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "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==", "devOptional": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", + "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "devOptional": true, - "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=10" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "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==", + "node_modules/@jest/reporters/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "devOptional": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/@jest/reporters/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==", "devOptional": true, - "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=8" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "devOptional": true, - "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "devOptional": true, "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "devOptional": true, "dependencies": { - "sprintf-js": "~1.0.2" + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "devOptional": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "devOptional": true, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/console": { + "node_modules/@jest/transform": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "devOptional": true, "dependencies": { + "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", - "@types/node": "*", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", - "slash": "^3.0.0" + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/console/node_modules/ansi-styles": { + "node_modules/@jest/transform/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==", @@ -3351,7 +4245,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/console/node_modules/chalk": { + "node_modules/@jest/transform/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -3367,7 +4261,7 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/console/node_modules/color-convert": { + "node_modules/@jest/transform/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==", @@ -3379,13 +4273,19 @@ "node": ">=7.0.0" } }, - "node_modules/@jest/console/node_modules/color-name": { + "node_modules/@jest/transform/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==", "devOptional": true }, - "node_modules/@jest/console/node_modules/has-flag": { + "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==", + "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==", @@ -3394,7 +4294,7 @@ "node": ">=8" } }, - "node_modules/@jest/console/node_modules/supports-color": { + "node_modules/@jest/transform/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==", @@ -3406,54 +4306,24 @@ "node": ">=8" } }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "devOptional": true, "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } } }, - "node_modules/@jest/core/node_modules/ansi-styles": { + "node_modules/@jest/types/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==", @@ -3468,7 +4338,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/core/node_modules/chalk": { + "node_modules/@jest/types/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -3484,7 +4354,7 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/core/node_modules/color-convert": { + "node_modules/@jest/types/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==", @@ -3496,13 +4366,13 @@ "node": ">=7.0.0" } }, - "node_modules/@jest/core/node_modules/color-name": { + "node_modules/@jest/types/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==", "devOptional": true }, - "node_modules/@jest/core/node_modules/has-flag": { + "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==", @@ -3511,7 +4381,7 @@ "node": ">=8" } }, - "node_modules/@jest/core/node_modules/supports-color": { + "node_modules/@jest/types/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==", @@ -3523,182 +4393,226 @@ "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, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "devOptional": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=6.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==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "devOptional": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "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==", "devOptional": true, "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "devOptional": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6.0.0" } }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "devOptional": true, + "node_modules/@jridgewell/sourcemap-codec": { + "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.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@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" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "devOptional": true, - "dependencies": { - "jest-get-type": "^29.6.3" - }, + "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": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "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, + "node_modules/@leichtgewicht/ip-codec": { + "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/@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": { - "@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" + "@modern-js/utils": "2.65.1", + "@swc/helpers": "0.5.13", + "esbuild": "0.17.19" } }, - "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==", - "devOptional": true, + "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": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "tslib": "^2.4.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==", + "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, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "dependencies": { + "@swc/helpers": "0.5.13", + "caniuse-lite": "^1.0.30001520", + "lodash": "^4.17.21", + "rslog": "^1.1.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==", - "devOptional": true, + "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": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "tslib": "^2.4.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==", + "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": { - "@types/node": "*", - "jest-regex-util": "30.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "@module-federation/sdk": "0.13.1", + "@types/semver": "7.5.8", + "semver": "7.6.3" } }, - "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==", + "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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=10" } }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "devOptional": true, + "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": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "@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" }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "bin": { + "mf": "bin/mf.js" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { + "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==", - "devOptional": true, + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -3709,27 +4623,24 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, + "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": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8" } }, - "node_modules/@jest/reporters/node_modules/color-convert": { + "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==", - "devOptional": true, + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -3737,54 +4648,35 @@ "node": ">=7.0.0" } }, - "node_modules/@jest/reporters/node_modules/color-name": { + "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==", - "devOptional": true + "dev": true }, - "node_modules/@jest/reporters/node_modules/has-flag": { + "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==", - "devOptional": true, + "dev": true, "engines": { "node": ">=8" } }, - "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", - "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", - "devOptional": true, - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@jest/reporters/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "devOptional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@jest/reporters/node_modules/supports-color": { + "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==", - "devOptional": true, + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -3792,93 +4684,59 @@ "node": ">=8" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "devOptional": true, - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "devOptional": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "devOptional": true, - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "devOptional": true, + "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": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" + "@module-federation/runtime": "0.13.1", + "@module-federation/sdk": "0.13.1", + "fs-extra": "9.1.0" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "devOptional": true, + "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": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "@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" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "peerDependencies": { + "typescript": "^4.9.0 || ^5.0.0", + "vue-tsc": ">=1.0.24" + }, + "peerDependenciesMeta": { + "vue-tsc": { + "optional": true + } } }, - "node_modules/@jest/transform/node_modules/ansi-styles": { + "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==", - "devOptional": true, + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -3889,27 +4747,24 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, + "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": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8" } }, - "node_modules/@jest/transform/node_modules/color-convert": { + "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==", - "devOptional": true, + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -3917,32 +4772,26 @@ "node": ">=7.0.0" } }, - "node_modules/@jest/transform/node_modules/color-name": { + "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==", - "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==", - "devOptional": true + "dev": true }, - "node_modules/@jest/transform/node_modules/has-flag": { + "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==", - "devOptional": true, + "dev": true, "engines": { "node": ">=8" } }, - "node_modules/@jest/transform/node_modules/supports-color": { + "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==", - "devOptional": true, + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -3950,28 +4799,112 @@ "node": ">=8" } }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "devOptional": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, + "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": "^14.15.0 || ^16.10.0 || >=18.0.0" + "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/@jest/types/node_modules/ansi-styles": { + "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==", - "devOptional": true, + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -3982,27 +4915,24 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, + "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": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8" } }, - "node_modules/@jest/types/node_modules/color-convert": { + "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==", - "devOptional": true, + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4010,26 +4940,26 @@ "node": ">=7.0.0" } }, - "node_modules/@jest/types/node_modules/color-name": { + "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==", - "devOptional": true + "dev": true }, - "node_modules/@jest/types/node_modules/has-flag": { + "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==", - "devOptional": true, + "dev": true, "engines": { "node": ">=8" } }, - "node_modules/@jest/types/node_modules/supports-color": { + "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==", - "devOptional": true, + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -4037,137 +4967,109 @@ "node": ">=8" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "devOptional": true, + "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": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@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" }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "devOptional": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "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==", - "devOptional": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "devOptional": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "peerDependencies": { + "@rspack/core": ">=0.7", + "typescript": "^4.9.0 || ^5.0.0", + "vue-tsc": ">=1.0.24" }, - "engines": { - "node": ">=6.0.0" + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue-tsc": { + "optional": true + } } }, - "node_modules/@jridgewell/sourcemap-codec": { - "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.30", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", - "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "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": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@module-federation/error-codes": "0.13.1", + "@module-federation/runtime-core": "0.13.1", + "@module-federation/sdk": "0.13.1" } }, - "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/@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/@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==", + "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, - "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" + "@module-federation/runtime": "0.13.1", + "@module-federation/webpack-bundler-runtime": "0.13.1" } }, - "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==", + "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, - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" + "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" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" + "bin": { + "resolve": "bin/resolve" }, - "peerDependencies": { - "tslib": "2" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "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, - "license": "MIT" + "dependencies": { + "@module-federation/runtime": "0.13.1", + "@module-federation/sdk": "0.13.1" + } }, "node_modules/@monaco-editor/loader": { "version": "1.4.0", @@ -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", @@ -4974,71 +5867,334 @@ "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" + "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==", + "dev": true + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@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": ">= 10.13" + "node": ">=18.12.0" }, "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" + "@swc/helpers": ">=0.5.1" }, "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": { + "@swc/helpers": { "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==", + "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, - "engines": { - "node": ">= 12" + "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/@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==", - "dev": true + "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/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "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, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" + "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": { @@ -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 b1e52e3b4..368cce89a 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 000000000..a72bf8981 --- /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 4a9cfdca9..000000000 --- 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 ce5a4ba3b..52134087b 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 + 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 }), + body: mockBFFResponse([mockWorkspace1]), }).as('getWorkspaces'); - cy.visit('/'); + + home.visit(); + navBar.selectNamespace(mockNamespaces[0].name); + cy.wait('@getNamespaces'); cy.wait('@getWorkspaces'); - }); - it('Page not found should render', () => { - pageNotfound.visit(); - }); - - it('Home page should have primary button', () => { - home.visit(); 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 a593f4f62..6da7b7e62 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 968d22697..bab665362 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 53d0c6c35..f06798d71 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 d51be6ea4..77d657228 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 ea9a2ba9d..2de7b7c7b 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 2ca06b987..0ec8a800c 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 945bf967f..f26d46b26 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 d916b8d1b..d8375d2e7 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 d3f99c4d4..000000000 --- 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 c62a19811..3b86104ae 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 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 { + namespaces: namespacesModArc, + preferredNamespace, + updatePreferredNamespace, + namespacesLoaded, + } = useNamespaceSelector(); + const [lastUsedNamespace, setLastUsedNamespace] = useBrowserStorage(storageKey, ''); + const namespaces = useMemo(() => namespacesModArc.map((ns) => ns.name), [namespacesModArc]); + + 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; } + return currentPreferredName; + } + + if (lastUsedNamespace && namespaces.includes(lastUsedNamespace)) { + return lastUsedNamespace; + } + + 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 { - if (loadError) { - console.error('Error loading namespaces: ', loadError); - } - setNamespaces([]); - setSelectedNamespace(''); + const fallbackNamespace = preferredNamespace?.name || ''; + setLastUsedNamespace(fallbackNamespace); } - }, [loaded, namespacesData, lastUsedNamespace, setLastUsedNamespace, loadError]); + }, [ + namespacesLoaded, + lastUsedNamespace, + namespaces, + preferredNamespace?.name, + updatePreferredNamespace, + setLastUsedNamespace, + ]); - const updateLastUsedNamespace = useCallback( - (value: string) => setLastUsedNamespace(storageKey, value), - [setLastUsedNamespace], - ); + useEffect(() => { + const currentPreferredName = preferredNamespace?.name; + const previousPreferredName = previousPreferredNamespaceRef.current; + + previousPreferredNamespaceRef.current = currentPreferredName; - useMount(fetchNamespaces); + 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 ba26a04a0..722f552e4 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 75a2a7ee9..dfe06faf2 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 0e1607133..1d8067d6a 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 974299861..c446e5e17 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 b5ebac446..864d8940f 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 99d64e9b7..935e39fd4 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 5ce9d3202..0228a6a6d 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 472098045..7ca0a271d 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 3f212a171..0ebef489e 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 b40e733b4..4de088bcc 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 f4cdb8b28..3219c3d8b 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 30db818d9..a564d257b 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 83485ff14..ca62a74a4 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 0424f3120..541ab2ffd 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 a6bd3da41..7581a9805 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 39ffae42e..5e16debb5 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 94bb71483..a4e605da0 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 1bbfcaf47..6ad708659 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 793efddce..0bb8d9af7 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 000000000..77368d77b --- /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 c1ef52385..f8c4adc58 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 (