Merge v3.6 into master

This commit is contained in:
mmatur
2025-12-01 16:28:00 +01:00
59 changed files with 1399 additions and 460 deletions
+1
View File
@@ -1 +1,2 @@
nodeLinker: node-modules
enableScripts: false
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,131 @@
import { renderHook, waitFor } from '@testing-library/react'
import { ReactNode } from 'react'
import useHubUpgradeButton from './use-hub-upgrade-button'
import { VersionContext } from 'contexts/version'
import verifySignature from 'utils/workers/scriptVerification'
vi.mock('utils/workers/scriptVerification')
const mockVerifySignature = vi.mocked(verifySignature)
const createWrapper = (showHubButton: boolean) => {
return ({ children }: { children: ReactNode }) => (
<VersionContext.Provider value={{ showHubButton, version: '1.0.0' }}>{children}</VersionContext.Provider>
)
}
describe('useHubUpgradeButton Hook', () => {
let originalCreateObjectURL: typeof URL.createObjectURL
let originalRevokeObjectURL: typeof URL.revokeObjectURL
const mockBlobUrl = 'blob:http://localhost:3000/mock-blob-url'
beforeEach(() => {
originalCreateObjectURL = URL.createObjectURL
originalRevokeObjectURL = URL.revokeObjectURL
URL.createObjectURL = vi.fn(() => mockBlobUrl)
URL.revokeObjectURL = vi.fn()
})
afterEach(() => {
URL.createObjectURL = originalCreateObjectURL
URL.revokeObjectURL = originalRevokeObjectURL
vi.clearAllMocks()
})
it('should not verify script when showHubButton is false', async () => {
renderHook(() => useHubUpgradeButton(), {
wrapper: createWrapper(false),
})
await waitFor(() => {
expect(mockVerifySignature).not.toHaveBeenCalled()
})
})
it('should verify script and create blob URL when showHubButton is true and verification succeeds', async () => {
const mockScriptContent = new ArrayBuffer(8)
mockVerifySignature.mockResolvedValue({
verified: true,
scriptContent: mockScriptContent,
})
const { result } = renderHook(() => useHubUpgradeButton(), {
wrapper: createWrapper(true),
})
await waitFor(() => {
expect(mockVerifySignature).toHaveBeenCalledWith(
'https://traefik.github.io/traefiklabs-hub-button-app/main-v1.js',
'https://traefik.github.io/traefiklabs-hub-button-app/main-v1.js.sig',
'MCowBQYDK2VwAyEAY0OZFFE5kSuqYK6/UprTL5RmvQ+8dpPTGMCw1MiO/Gs=',
)
})
await waitFor(() => {
expect(result.current.signatureVerified).toBe(true)
})
expect(result.current.scriptBlobUrl).toBe(mockBlobUrl)
expect(URL.createObjectURL).toHaveBeenCalledWith(expect.any(Blob))
})
it('should set signatureVerified to false when verification fails', async () => {
mockVerifySignature.mockResolvedValue({
verified: false,
})
const { result } = renderHook(() => useHubUpgradeButton(), {
wrapper: createWrapper(true),
})
await waitFor(() => {
expect(mockVerifySignature).toHaveBeenCalled()
})
await waitFor(() => {
expect(result.current.signatureVerified).toBe(false)
})
expect(result.current.scriptBlobUrl).toBeNull()
expect(URL.createObjectURL).not.toHaveBeenCalled()
})
it('should handle verification errors gracefully', async () => {
mockVerifySignature.mockRejectedValue(new Error('Verification failed'))
const { result } = renderHook(() => useHubUpgradeButton(), {
wrapper: createWrapper(true),
})
await waitFor(() => {
expect(mockVerifySignature).toHaveBeenCalled()
})
await waitFor(() => {
expect(result.current.signatureVerified).toBe(false)
})
expect(result.current.scriptBlobUrl).toBeNull()
})
it('should create blob with correct MIME type', async () => {
const mockScriptContent = new ArrayBuffer(8)
mockVerifySignature.mockResolvedValue({
verified: true,
scriptContent: mockScriptContent,
})
renderHook(() => useHubUpgradeButton(), {
wrapper: createWrapper(true),
})
await waitFor(() => {
expect(URL.createObjectURL).toHaveBeenCalled()
})
const blobCall = vi.mocked(URL.createObjectURL).mock.calls[0][0] as Blob
expect(blobCall).toBeInstanceOf(Blob)
expect(blobCall.type).toBe('application/javascript')
})
})
@@ -0,0 +1,61 @@
import { useContext, useEffect, useState } from 'react'
import { VersionContext } from 'contexts/version'
import verifySignature from 'utils/workers/scriptVerification'
const HUB_BUTTON_URL = 'https://traefik.github.io/traefiklabs-hub-button-app/main-v1.js'
const PUBLIC_KEY = 'MCowBQYDK2VwAyEAY0OZFFE5kSuqYK6/UprTL5RmvQ+8dpPTGMCw1MiO/Gs='
const useHubUpgradeButton = () => {
const [signatureVerified, setSignatureVerified] = useState(false)
const [scriptBlobUrl, setScriptBlobUrl] = useState<string | null>(null)
const [isCustomElementDefined, setIsCustomElementDefined] = useState(false)
const { showHubButton } = useContext(VersionContext)
useEffect(() => {
if (showHubButton) {
if (customElements.get('hub-button-app')) {
setSignatureVerified(true)
setIsCustomElementDefined(true)
return
}
const verifyAndLoadScript = async () => {
try {
const { verified, scriptContent: content } = await verifySignature(
HUB_BUTTON_URL,
`${HUB_BUTTON_URL}.sig`,
PUBLIC_KEY,
)
if (!verified || !content) {
setSignatureVerified(false)
} else {
const blob = new Blob([content], { type: 'application/javascript' })
const blobUrl = URL.createObjectURL(blob)
setScriptBlobUrl(blobUrl)
setSignatureVerified(true)
}
} catch {
setSignatureVerified(false)
}
}
verifyAndLoadScript()
return () => {
setScriptBlobUrl((currentUrl) => {
if (currentUrl) {
URL.revokeObjectURL(currentUrl)
}
return null
})
}
}
}, [showHubButton])
return { signatureVerified, scriptBlobUrl, isCustomElementDefined }
}
export default useHubUpgradeButton
+13 -2
View File
@@ -1,9 +1,20 @@
import { Flex, Text } from '@traefiklabs/faency'
import { AriaTd, Flex, Text } from '@traefiklabs/faency'
import { FiAlertTriangle } from 'react-icons/fi'
export const EmptyPlaceholder = ({ message = 'No data available' }: { message?: string }) => (
type EmptyPlaceholderProps = {
message?: string
}
export const EmptyPlaceholder = ({ message = 'No data available' }: EmptyPlaceholderProps) => (
<Flex align="center" justify="center" css={{ py: '$5', color: '$primary' }}>
<FiAlertTriangle size={16} />
<Text css={{ pl: '$2' }}>{message}</Text>
</Flex>
)
export const EmptyPlaceholderTd = (props: EmptyPlaceholderProps) => {
return (
<AriaTd css={{ pointerEvents: 'none' }} fullColSpan>
<EmptyPlaceholder {...props} />
</AriaTd>
)
}
+2 -4
View File
@@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh'
import Tooltip from 'components/Tooltip'
import TooltipText from 'components/TooltipText'
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
import { parseMiddlewareType } from 'libs/parsers'
export const makeRowRender = (): RenderRowType => {
@@ -79,9 +79,7 @@ export const HttpMiddlewaresRender = ({
{(isEmpty || !!error) && (
<AriaTfoot>
<AriaTr>
<AriaTd fullColSpan>
<EmptyPlaceholder message={error ? 'Failed to fetch data' : 'No data available'} />
</AriaTd>
<EmptyPlaceholderTd message={error ? 'Failed to fetch data' : 'No data available'} />
</AriaTr>
</AriaTfoot>
)}
+2 -4
View File
@@ -16,7 +16,7 @@ import SortableTh from 'components/tables/SortableTh'
import Tooltip from 'components/Tooltip'
import TooltipText from 'components/TooltipText'
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
export const makeRowRender = (protocol = 'http'): RenderRowType => {
const HttpRoutersRenderRow = (row) => (
@@ -100,9 +100,7 @@ export const HttpRoutersRender = ({
{(isEmpty || !!error) && (
<AriaTfoot>
<AriaTr>
<AriaTd fullColSpan>
<EmptyPlaceholder message={error ? 'Failed to fetch data' : 'No data available'} />
</AriaTd>
<EmptyPlaceholderTd message={error ? 'Failed to fetch data' : 'No data available'} />
</AriaTr>
</AriaTfoot>
)}
+2 -4
View File
@@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh'
import Tooltip from 'components/Tooltip'
import TooltipText from 'components/TooltipText'
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
export const makeRowRender = (): RenderRowType => {
const HttpServicesRenderRow = (row) => (
@@ -78,9 +78,7 @@ export const HttpServicesRender = ({
{(isEmpty || !!error) && (
<AriaTfoot>
<AriaTr>
<AriaTd fullColSpan>
<EmptyPlaceholder message={error ? 'Failed to fetch data' : 'No data available'} />
</AriaTd>
<EmptyPlaceholderTd message={error ? 'Failed to fetch data' : 'No data available'} />
</AriaTr>
</AriaTfoot>
)}
@@ -1,12 +1,13 @@
import { waitFor } from '@testing-library/react'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { PUBLIC_KEY } from './constants'
import HubDashboard, { resetCache } from './HubDashboard'
import verifySignature from './workers/scriptVerification'
import { renderWithProviders } from 'utils/test'
import verifySignature from 'utils/workers/scriptVerification'
vi.mock('./workers/scriptVerification', () => ({
vi.mock('utils/workers/scriptVerification', () => ({
default: vi.fn(),
}))
@@ -34,7 +35,6 @@ describe('HubDashboard demo', () => {
beforeEach(() => {
vi.clearAllMocks()
// Mock URL.createObjectURL
mockCreateObjectURL = vi.fn(() => 'blob:mock-url')
globalThis.URL.createObjectURL = mockCreateObjectURL
})
@@ -45,7 +45,6 @@ describe('HubDashboard demo', () => {
describe('without cache', () => {
beforeEach(() => {
// Reset cache before each test suites
resetCache()
})
@@ -130,6 +129,7 @@ describe('HubDashboard demo', () => {
expect(mockVerifyScriptSignature).toHaveBeenCalledWith(
'https://assets.traefik.io/hub-ui-demo.js',
'https://assets.traefik.io/hub-ui-demo.js.sig',
PUBLIC_KEY,
)
})
})
+4 -2
View File
@@ -3,7 +3,9 @@ import { useMemo, useEffect, useState } from 'react'
import { Helmet } from 'react-helmet-async'
import { useParams } from 'react-router-dom'
import verifySignature from './workers/scriptVerification'
import verifySignature from '../../utils/workers/scriptVerification'
import { PUBLIC_KEY } from './constants'
import { SpinnerLoader } from 'components/SpinnerLoader'
import { useIsDarkMode } from 'hooks/use-theme'
@@ -42,7 +44,7 @@ const HubDashboard = ({ path }: { path: string }) => {
setVerificationInProgress(true)
try {
const { verified, scriptContent: content } = await verifySignature(SCRIPT_URL, `${SCRIPT_URL}.sig`)
const { verified, scriptContent: content } = await verifySignature(SCRIPT_URL, `${SCRIPT_URL}.sig`, PUBLIC_KEY)
if (!verified || !content) {
setScriptError(true)
+1
View File
@@ -0,0 +1 @@
export const PUBLIC_KEY = 'MCowBQYDK2VwAyEAWMBZ0pMBaL/s8gNXxpAPCIQ8bxjnuz6bQFwGYvjXDfg='
@@ -3,9 +3,10 @@ import { ReactNode } from 'react'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { useHubDemo } from './use-hub-demo'
import verifySignature from './workers/scriptVerification'
vi.mock('./workers/scriptVerification', () => ({
import verifySignature from 'utils/workers/scriptVerification'
vi.mock('utils/workers/scriptVerification', () => ({
default: vi.fn(),
}))
+8 -2
View File
@@ -1,9 +1,11 @@
import { ReactNode, useEffect, useMemo, useState } from 'react'
import { RouteObject } from 'react-router-dom'
import { PUBLIC_KEY } from './constants'
import HubDashboard from 'pages/hub-demo/HubDashboard'
import { ApiIcon, DashboardIcon, GatewayIcon, PortalIcon } from 'pages/hub-demo/icons'
import verifySignature from 'pages/hub-demo/workers/scriptVerification'
import verifySignature from 'utils/workers/scriptVerification'
const ROUTES_MANIFEST_URL = 'https://traefik.github.io/hub-ui-demo-app/config/routes.json'
@@ -20,7 +22,11 @@ const useHubDemoRoutesManifest = (): HubDemo.Manifest | null => {
useEffect(() => {
const fetchManifest = async () => {
try {
const { verified, scriptContent } = await verifySignature(ROUTES_MANIFEST_URL, `${ROUTES_MANIFEST_URL}.sig`)
const { verified, scriptContent } = await verifySignature(
ROUTES_MANIFEST_URL,
`${ROUTES_MANIFEST_URL}.sig`,
PUBLIC_KEY,
)
if (!verified || !scriptContent) {
setManifest(null)
+2 -4
View File
@@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh'
import Tooltip from 'components/Tooltip'
import TooltipText from 'components/TooltipText'
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
import { parseMiddlewareType } from 'libs/parsers'
export const makeRowRender = (): RenderRowType => {
@@ -79,9 +79,7 @@ export const TcpMiddlewaresRender = ({
{(isEmpty || !!error) && (
<AriaTfoot>
<AriaTr>
<AriaTd fullColSpan>
<EmptyPlaceholder message={error ? 'Failed to fetch data' : 'No data available'} />
</AriaTd>
<EmptyPlaceholderTd message={error ? 'Failed to fetch data' : 'No data available'} />
</AriaTr>
</AriaTfoot>
)}
+2 -4
View File
@@ -16,7 +16,7 @@ import SortableTh from 'components/tables/SortableTh'
import Tooltip from 'components/Tooltip'
import TooltipText from 'components/TooltipText'
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
export const makeRowRender = (): RenderRowType => {
const TcpRoutersRenderRow = (row) => (
@@ -96,9 +96,7 @@ export const TcpRoutersRender = ({
{(isEmpty || !!error) && (
<AriaTfoot>
<AriaTr>
<AriaTd fullColSpan>
<EmptyPlaceholder message={error ? 'Failed to fetch data' : 'No data available'} />
</AriaTd>
<EmptyPlaceholderTd message={error ? 'Failed to fetch data' : 'No data available'} />
</AriaTr>
</AriaTfoot>
)}
+2 -4
View File
@@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh'
import Tooltip from 'components/Tooltip'
import TooltipText from 'components/TooltipText'
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
export const makeRowRender = (): RenderRowType => {
const TcpServicesRenderRow = (row) => (
@@ -78,9 +78,7 @@ export const TcpServicesRender = ({
{(isEmpty || !!error) && (
<AriaTfoot>
<AriaTr>
<AriaTd fullColSpan>
<EmptyPlaceholder message={error ? 'Failed to fetch data' : 'No data available'} />
</AriaTd>
<EmptyPlaceholderTd message={error ? 'Failed to fetch data' : 'No data available'} />
</AriaTr>
</AriaTfoot>
)}
+2 -4
View File
@@ -15,7 +15,7 @@ import SortableTh from 'components/tables/SortableTh'
import Tooltip from 'components/Tooltip'
import TooltipText from 'components/TooltipText'
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
export const makeRowRender = (): RenderRowType => {
const UdpRoutersRenderRow = (row) => (
@@ -81,9 +81,7 @@ export const UdpRoutersRender = ({
{(isEmpty || !!error) && (
<AriaTfoot>
<AriaTr>
<AriaTd fullColSpan>
<EmptyPlaceholder message={error ? 'Failed to fetch data' : 'No data available'} />
</AriaTd>
<EmptyPlaceholderTd message={error ? 'Failed to fetch data' : 'No data available'} />
</AriaTr>
</AriaTfoot>
)}
+2 -4
View File
@@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh'
import Tooltip from 'components/Tooltip'
import TooltipText from 'components/TooltipText'
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
export const makeRowRender = (): RenderRowType => {
const UdpServicesRenderRow = (row) => (
@@ -78,9 +78,7 @@ export const UdpServicesRender = ({
{(isEmpty || !!error) && (
<AriaTfoot>
<AriaTr>
<AriaTd fullColSpan>
<EmptyPlaceholder message={error ? 'Failed to fetch data' : 'No data available'} />
</AriaTd>
<EmptyPlaceholderTd message={error ? 'Failed to fetch data' : 'No data available'} />
</AriaTr>
</AriaTfoot>
)}
@@ -2,6 +2,8 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import verifySignature from './scriptVerification'
const SCRIPT_PATH = 'https://example.com/script.js'
const MOCK_PUBLIC_KEY = 'MCowBQYDK2VwAyEAWH71OHphISjNK3mizCR/BawiDxc6IXT1vFHpBcxSIA0='
class MockWorker {
onmessage: ((event: MessageEvent) => void) | null = null
onerror: ((error: ErrorEvent) => void) | null = null
@@ -46,17 +48,14 @@ describe('verifySignature', () => {
})
it('should return true when verification succeeds', async () => {
const scriptPath = 'https://example.com/script.js'
const signaturePath = 'https://example.com/script.js.sig'
const promise = verifySignature(scriptPath, signaturePath)
const promise = verifySignature(SCRIPT_PATH, `${SCRIPT_PATH}.sig`, MOCK_PUBLIC_KEY)
await new Promise((resolve) => setTimeout(resolve, 0))
expect(mockWorkerInstance.postMessage).toHaveBeenCalledWith(
expect.objectContaining({
scriptUrl: scriptPath,
signatureUrl: signaturePath,
scriptUrl: SCRIPT_PATH,
signatureUrl: `${SCRIPT_PATH}.sig`,
requestId: expect.any(String),
}),
)
@@ -76,12 +75,9 @@ describe('verifySignature', () => {
})
it('should return false when verification fails', async () => {
const scriptPath = 'https://example.com/script.js'
const signaturePath = 'https://example.com/script.js.sig'
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
const promise = verifySignature(scriptPath, signaturePath)
const promise = verifySignature(SCRIPT_PATH, `${SCRIPT_PATH}.sig`, MOCK_PUBLIC_KEY)
await new Promise((resolve) => setTimeout(resolve, 0))
@@ -101,16 +97,12 @@ describe('verifySignature', () => {
})
it('should return false when worker throws an error', async () => {
const scriptPath = 'https://example.com/script.js'
const signaturePath = 'https://example.com/script.js.sig'
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
const promise = verifySignature(scriptPath, signaturePath)
const promise = verifySignature(SCRIPT_PATH, `${SCRIPT_PATH}.sig`, MOCK_PUBLIC_KEY)
await new Promise((resolve) => setTimeout(resolve, 0))
// Simulate worker onerror event
const error = new Error('Worker crashed')
mockWorkerInstance.simulateError(error)
@@ -3,12 +3,10 @@ export interface VerificationResult {
scriptContent?: ArrayBuffer
}
const PUBLIC_KEY = 'MCowBQYDK2VwAyEAWMBZ0pMBaL/s8gNXxpAPCIQ8bxjnuz6bQFwGYvjXDfg='
async function verifySignature(
contentPath: string,
signaturePath: string,
publicKey: string = PUBLIC_KEY,
publicKey: string,
): Promise<VerificationResult> {
return new Promise((resolve) => {
const requestId = Math.random().toString(36).substring(2)
+41 -41
View File
@@ -2871,24 +2871,24 @@ __metadata:
languageName: node
linkType: hard
"@microsoft/api-extractor-model@npm:7.31.3":
version: 7.31.3
resolution: "@microsoft/api-extractor-model@npm:7.31.3"
"@microsoft/api-extractor-model@npm:7.32.0":
version: 7.32.0
resolution: "@microsoft/api-extractor-model@npm:7.32.0"
dependencies:
"@microsoft/tsdoc": "npm:~0.15.1"
"@microsoft/tsdoc-config": "npm:~0.17.1"
"@microsoft/tsdoc": "npm:~0.16.0"
"@microsoft/tsdoc-config": "npm:~0.18.0"
"@rushstack/node-core-library": "npm:5.18.0"
checksum: 10c0/4e4a798c5d92b72fa664932019563f085153cf33f7745f8ea452901348a0021f19c7c3db55e5555b779a78df52d93ec10960349b5bc1ee53bf555e63c0fe1197
checksum: 10c0/e6d9c54a457c66dec53765522f411c8ca5d2bb8c51559b9f952fdd7bb88b10efe035a8dbdf8b672644c136d75a65f3446b3b18938fa50c0a766f83111cbda5eb
languageName: node
linkType: hard
"@microsoft/api-extractor@npm:^7.50.1":
version: 7.54.0
resolution: "@microsoft/api-extractor@npm:7.54.0"
version: 7.55.0
resolution: "@microsoft/api-extractor@npm:7.55.0"
dependencies:
"@microsoft/api-extractor-model": "npm:7.31.3"
"@microsoft/tsdoc": "npm:~0.15.1"
"@microsoft/tsdoc-config": "npm:~0.17.1"
"@microsoft/api-extractor-model": "npm:7.32.0"
"@microsoft/tsdoc": "npm:~0.16.0"
"@microsoft/tsdoc-config": "npm:~0.18.0"
"@rushstack/node-core-library": "npm:5.18.0"
"@rushstack/rig-package": "npm:0.6.0"
"@rushstack/terminal": "npm:0.19.3"
@@ -2902,26 +2902,26 @@ __metadata:
typescript: "npm:5.8.2"
bin:
api-extractor: bin/api-extractor
checksum: 10c0/e4708ac5edc3bb32988b632cc75e6f5e5b3afe7772c7229974db91f731d6b8d3c786c406d5437bfce893b78c6f23c64b084db52952a7d35294e2525171a465ee
checksum: 10c0/3211981b7aaf6ca7a36fe33dc9cab5014dc753c0c75d09ace46bef07db947a433ca9daecc843ea13b29fe7527ea3d357c7bd5051fedf10ae3b3db31d2a5de71f
languageName: node
linkType: hard
"@microsoft/tsdoc-config@npm:~0.17.1":
version: 0.17.1
resolution: "@microsoft/tsdoc-config@npm:0.17.1"
"@microsoft/tsdoc-config@npm:~0.18.0":
version: 0.18.0
resolution: "@microsoft/tsdoc-config@npm:0.18.0"
dependencies:
"@microsoft/tsdoc": "npm:0.15.1"
"@microsoft/tsdoc": "npm:0.16.0"
ajv: "npm:~8.12.0"
jju: "npm:~1.4.0"
resolve: "npm:~1.22.2"
checksum: 10c0/a686355796f492f27af17e2a17d615221309caf4d9f9047a5a8f17f8625c467c4c81e2a7923ddafd71b892631d5e5013c4b8cc49c5867d3cc1d260fd90c1413d
checksum: 10c0/6e2c3bfde3e5fa4c0360127c86fe016dcf1b09d0091d767c06ce916284d3f6aeea3617a33b855c5bb2615ab0f2840eeebd4c7f4a1f879f951828d213bf306cfd
languageName: node
linkType: hard
"@microsoft/tsdoc@npm:0.15.1, @microsoft/tsdoc@npm:~0.15.1":
version: 0.15.1
resolution: "@microsoft/tsdoc@npm:0.15.1"
checksum: 10c0/09948691fac56c45a0d1920de478d66a30371a325bd81addc92eea5654d95106ce173c440fea1a1bd5bb95b3a544b6d4def7bb0b5a846c05d043575d8369a20c
"@microsoft/tsdoc@npm:0.16.0, @microsoft/tsdoc@npm:~0.16.0":
version: 0.16.0
resolution: "@microsoft/tsdoc@npm:0.16.0"
checksum: 10c0/8883bb0ed22753af7360e9222687fda4eb448f0a574ea34b4596c11e320148b3ae0d24e00f8923df8ba7bc62a46a6f53b9343243a348640d923dfd55d52cd6bb
languageName: node
linkType: hard
@@ -6524,26 +6524,26 @@ __metadata:
languageName: node
linkType: hard
"@vue/compiler-core@npm:3.5.23":
version: 3.5.23
resolution: "@vue/compiler-core@npm:3.5.23"
"@vue/compiler-core@npm:3.5.24":
version: 3.5.24
resolution: "@vue/compiler-core@npm:3.5.24"
dependencies:
"@babel/parser": "npm:^7.28.5"
"@vue/shared": "npm:3.5.23"
"@vue/shared": "npm:3.5.24"
entities: "npm:^4.5.0"
estree-walker: "npm:^2.0.2"
source-map-js: "npm:^1.2.1"
checksum: 10c0/195c57b2eb8c6948bf3b1b3f65c2a5a9bf9e252376bcd22bd9b5e1787c4254abc4bffab5f15902c7820f5e607b26d44578cddeb39605ece37b611703c2d6152b
checksum: 10c0/d5b1421c0c0cfdff6b6ae2ef3d59b5901f0fec8ad2fa153f5ae1ec8487b898c92766353c661f68b892580ab0eacbc493632c946af8141045d6e76d67797b8a84
languageName: node
linkType: hard
"@vue/compiler-dom@npm:^3.5.0":
version: 3.5.23
resolution: "@vue/compiler-dom@npm:3.5.23"
version: 3.5.24
resolution: "@vue/compiler-dom@npm:3.5.24"
dependencies:
"@vue/compiler-core": "npm:3.5.23"
"@vue/shared": "npm:3.5.23"
checksum: 10c0/fb925b2d64de40c1b39852f5fd26fdec3238f8381ccc2b30a1bef372ef894fff4e6f0231f8a135a02d6a5c8b8254dc7018bcd136a689579a72a3a0e1ff211a89
"@vue/compiler-core": "npm:3.5.24"
"@vue/shared": "npm:3.5.24"
checksum: 10c0/d49cb715f2e1cb2272ede2e41901282fb3f6fbdf489c8aa737e60c68e21216e07b72942695a80430fee8f11e5933e36fc90615b146b189cac925bf32f2727c95
languageName: node
linkType: hard
@@ -6578,10 +6578,10 @@ __metadata:
languageName: node
linkType: hard
"@vue/shared@npm:3.5.23, @vue/shared@npm:^3.5.0":
version: 3.5.23
resolution: "@vue/shared@npm:3.5.23"
checksum: 10c0/0f051ea60a756520b0b0af3d5058587b47f1942476c7f2cee6f78589c97c246acabdea11c73e2f84f13ecfb36c1160aacecca37694144326ebec8c108103bb89
"@vue/shared@npm:3.5.24, @vue/shared@npm:^3.5.0":
version: 3.5.24
resolution: "@vue/shared@npm:3.5.24"
checksum: 10c0/4fd5665539fa5be3d12280c1921a8db3a707115fef54d22d83ce347ea06e3b1089dfe07292e0c46bbebf23553c7c1ec98010972ebccf10532db82422801288ff
languageName: node
linkType: hard
@@ -10099,9 +10099,9 @@ __metadata:
linkType: hard
"exsolve@npm:^1.0.7":
version: 1.0.7
resolution: "exsolve@npm:1.0.7"
checksum: 10c0/4479369d0bd84bb7e0b4f5d9bc18d26a89b6dbbbccd73f9d383d14892ef78ddbe159e01781055342f83dc00ebe90044036daf17ddf55cc21e2cac6609aa15631
version: 1.0.8
resolution: "exsolve@npm:1.0.8"
checksum: 10c0/65e44ae05bd4a4a5d87cfdbbd6b8f24389282cf9f85fa5feb17ca87ad3f354877e6af4cd99e02fc29044174891f82d1d68c77f69234410eb8f163530e6278c67
languageName: node
linkType: hard
@@ -18670,8 +18670,8 @@ __metadata:
linkType: hard
"vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0, vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0":
version: 7.2.1
resolution: "vite@npm:7.2.1"
version: 7.2.2
resolution: "vite@npm:7.2.2"
dependencies:
esbuild: "npm:^0.25.0"
fdir: "npm:^6.5.0"
@@ -18720,7 +18720,7 @@ __metadata:
optional: true
bin:
vite: bin/vite.js
checksum: 10c0/25fbcfc67b1598fa6152f3ed0a7355144a2ac203859ad3b6a2e466b0930ec1081c19cc6f0d83104897517ecf30c0aac3e4a50c4e5e2980d3659decb1d9e41a28
checksum: 10c0/9c76ee441f8dbec645ddaecc28d1f9cf35670ffa91cff69af7b1d5081545331603f0b1289d437b2fa8dc43cdc77b4d96b5bd9c9aed66310f490cb1a06f9c814c
languageName: node
linkType: hard