diff --git a/src/frontend/apps/impress/.env b/src/frontend/apps/impress/.env index 3cf0e897e..2aba89830 100644 --- a/src/frontend/apps/impress/.env +++ b/src/frontend/apps/impress/.env @@ -1,2 +1,4 @@ NEXT_PUBLIC_API_ORIGIN= NEXT_PUBLIC_SW_DEACTIVATED= +NEXT_PUBLIC_Y_PROVIDER_API_KEY= +NEXT_PUBLIC_Y_PROVIDER_API_BASE_URL= \ No newline at end of file diff --git a/src/frontend/apps/impress/.env.development b/src/frontend/apps/impress/.env.development index f26afb11b..a00ec2481 100644 --- a/src/frontend/apps/impress/.env.development +++ b/src/frontend/apps/impress/.env.development @@ -1,2 +1,4 @@ NEXT_PUBLIC_API_ORIGIN=http://localhost:8071 NEXT_PUBLIC_SW_DEACTIVATED=true +NEXT_PUBLIC_Y_PROVIDER_API_KEY=yprovider-api-key +NEXT_PUBLIC_Y_PROVIDER_API_BASE_URL=http://localhost:4444/api/ diff --git a/src/frontend/apps/impress/package.json b/src/frontend/apps/impress/package.json index e70dcc3c9..f9720a608 100644 --- a/src/frontend/apps/impress/package.json +++ b/src/frontend/apps/impress/package.json @@ -27,6 +27,7 @@ "i18next": "24.2.0", "i18next-browser-languagedetector": "8.0.2", "idb": "8.0.1", + "jszip": "^3.10.1", "lodash": "4.17.21", "luxon": "3.5.0", "next": "15.1.3", diff --git a/src/frontend/apps/impress/src/api/fetchYProviderAPI.ts b/src/frontend/apps/impress/src/api/fetchYProviderAPI.ts new file mode 100644 index 000000000..1346de0b2 --- /dev/null +++ b/src/frontend/apps/impress/src/api/fetchYProviderAPI.ts @@ -0,0 +1,43 @@ +const baseYProviderUrl = () => { + return process.env.NEXT_PUBLIC_Y_PROVIDER_API_BASE_URL || 'http://localhost:4444/api/'; +}; + +export const fetchYProvider = async ( + input: string, + init?: RequestInit, +) => { + const apiUrl = `${baseYProviderUrl()}${input}`; + const apiKey = process.env.NEXT_PUBLIC_Y_PROVIDER_API_KEY || 'yprovider-api-key'; + + const headers = { + 'Content-Type': 'application/json', + 'Authorization': apiKey, + ...init?.headers, + }; + + const response = await fetch(apiUrl, { + ...init, + headers, + }); + + return response; +}; + +interface ConversionResponse { + content: string; +} + +export const convertMarkdownToY = async (content: string): Promise => { + const response = await fetchYProvider('convert-markdown', { + method: 'POST', + body: JSON.stringify({ content }), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || 'Failed to convert markdown'); + } + + const data = (await response.json()) as ConversionResponse; + return data.content; +}; diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridContainer.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridContainer.tsx index 282c3e833..e36cd91af 100644 --- a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridContainer.tsx +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridContainer.tsx @@ -3,7 +3,7 @@ import { useRouter } from 'next/router'; import React from 'react'; import { useTranslation } from 'react-i18next'; -import { Box } from '@/components'; +import { Box, StyledLink } from '@/components'; import { useCreateDoc, useTrans } from '@/features/docs/doc-management/'; import { useResponsiveStore } from '@/stores'; @@ -28,10 +28,17 @@ export const DocsGridContainer = () => { return ( + + + diff --git a/src/frontend/apps/impress/src/features/docs/docs-import/components/DocsImport.tsx b/src/frontend/apps/impress/src/features/docs/docs-import/components/DocsImport.tsx new file mode 100644 index 000000000..a6cee42e4 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/docs-import/components/DocsImport.tsx @@ -0,0 +1,181 @@ +import { Button, Select } from "@openfun/cunningham-react"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { fetchAPI } from "@/api"; +import { Box } from "@/components"; +import styled from 'styled-components'; +import { useRouter } from 'next/router'; +import { DocToImport } from "../types"; +import { OutlineImport } from "./OutlineImport"; +import { PreviewDocsImport } from "./PreviewDocsImport"; + + +const ImportContainer = styled.div` + width: 100%; + max-width: 1000px; + margin: 0 auto; +`; + +type Source = 'outline'; + +type ImportState = 'idle' | 'importing' | 'completed'; + +export const DocsImport = () => { + const { t } = useTranslation(); + const router = useRouter(); + const [source, setSource] = useState('outline'); + const [extractedDocs, setExtractedDocs] = useState([]); + const [importState, setImportState] = useState('idle'); + + const updateDocState = ( + docs: DocToImport[], + updatedDoc: DocToImport, + parentPath: number[] = [] + ): DocToImport[] => { + return docs.map((doc, index) => { + if (parentPath[0] === index) { + if (parentPath.length === 1) { + return updatedDoc; + } + return { + ...doc, + children: updateDocState(doc.children || [], updatedDoc, parentPath.slice(1)) + }; + } + return doc; + }); + }; + + const importDocument = async ( + doc: DocToImport, + parentId?: string, + parentPath: number[] = [] + ): Promise => { + setExtractedDocs(prev => + updateDocState( + prev, + { ...doc, state: 'importing' as const }, + parentPath + ) + ); + + try { + const response = await fetchAPI('documents/', { + method: 'POST', + body: JSON.stringify({ + title: doc.doc.title, + content: doc.doc.content, + parent: parentId + }), + }); + + if (!response.ok) { + throw new Error(await response.text()); + } + + const { id } = await response.json(); + + const successDoc = { + ...doc, + state: 'success' as const, + doc: { ...doc.doc, id } + }; + + setExtractedDocs(prev => + updateDocState(prev, successDoc, parentPath) + ); + + if (doc.children?.length) { + const processedChildren = []; + for (let i = 0; i < doc.children.length; i++) { + const childDoc = await importDocument(doc.children[i], id, [...parentPath, i]); + processedChildren.push(childDoc); + } + successDoc.children = processedChildren; + } + + return successDoc; + } catch (error) { + const failedDoc = { + ...doc, + state: 'error' as const, + error: error instanceof Error ? error.message : 'Unknown error', + children: doc.children + }; + + setExtractedDocs(prev => + updateDocState(prev, failedDoc, parentPath) + ); + + return failedDoc; + } + }; + + const handleImport = async () => { + setImportState('importing'); + try { + await Promise.all( + extractedDocs.map((doc, index) => importDocument(doc, undefined, [index])) + ); + setImportState('completed'); + } catch (error) { + console.error('Import failed:', error); + setImportState('idle'); + } + }; + + const handleBackToDocs = () => { + router.push('/docs'); + }; + + const handleReset = () => { + setExtractedDocs([]); + setImportState('idle'); + }; + + return ( + +

{t('Import documents')}

+ + onChange && onChange(e)} + data-testid="file-input" + /> + + ), +})); + +// Mock JSZip +jest.mock('jszip', () => { + return jest.fn().mockImplementation(() => ({ + loadAsync: jest.fn(), + })); +}); + +// Mock @blocknote/mantine +jest.mock('@blocknote/mantine', () => ({ + BlockNoteView: ({ editor }) =>
Mocked BlockNote View
+})); + +// Modifions les mocks pour mieux contrĂ´ler le comportement +let mockParseMarkdownResult = [{ type: 'paragraph', content: 'Test content' }]; +let mockParseMarkdownError: Error | null = null; + +jest.mock('@blocknote/react', () => ({ + useCreateBlockNote: () => ({ + tryParseMarkdownToBlocks: jest.fn().mockImplementation(async (text: string) => { + if (mockParseMarkdownError) { + return Promise.reject(mockParseMarkdownError); + } + return Promise.resolve(mockParseMarkdownResult); + }), + }), +})); + +describe('', () => { + const mockSetExtractedDocs = jest.fn(); + const mockOnNewUpload = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + mockParseMarkdownError = null; + mockParseMarkdownResult = [{ type: 'paragraph', content: 'Test content' }]; + }); + + it('handles successful file upload with nested structure', async () => { + const mockFiles = { + 'folder/doc1.md': { + async: () => Promise.resolve('# Doc 1'), + dir: false, + name: 'folder/doc1.md' + }, + 'folder/doc2.md': { + async: () => Promise.resolve('# Doc 2'), + dir: false, + name: 'folder/doc2.md' + }, + 'doc3.md': { + async: () => Promise.resolve('# Doc 3'), + dir: false, + name: 'doc3.md' + }, + }; + + const mockZipInstance = { + loadAsync: jest.fn().mockResolvedValue({ + files: mockFiles, + }), + }; + (JSZip as jest.Mock).mockImplementation(() => mockZipInstance); + + render( + + + + + + ); + + const file = new File(['content'], 'test.zip', { type: 'application/zip' }); + const input = screen.getByTestId('file-input'); + + fireEvent.change(input, { target: { files: [file] } }); + + await waitFor(() => { + expect(mockSetExtractedDocs).toHaveBeenCalled(); + const calls = mockSetExtractedDocs.mock.calls[0][0]; + expect(calls).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + doc: expect.objectContaining({ title: 'doc3' }), + }), + ]) + ); + }); + }); + + it('handles successful file upload with single file', async () => { + const mockFiles = { + 'doc1.md': { + async: () => Promise.resolve('# Single Doc'), + dir: false, + name: 'doc1.md' + }, + }; + + const mockZipInstance = { + loadAsync: jest.fn().mockResolvedValue({ + files: mockFiles, + }), + }; + (JSZip as jest.Mock).mockImplementation(() => mockZipInstance); + + render( + + + + + + ); + + const file = new File(['content'], 'test.zip', { type: 'application/zip' }); + const input = screen.getByTestId('file-input'); + + fireEvent.change(input, { target: { files: [file] } }); + + await waitFor(() => { + expect(mockSetExtractedDocs).toHaveBeenCalledWith([ + expect.objectContaining({ + doc: expect.objectContaining({ title: 'doc1' }), + }), + ]); + }); + }); + + it('handles error when markdown parsing fails', async () => { + mockParseMarkdownError = new Error('Invalid markdown'); + + const mockFiles = { + 'invalid.md': { + async: () => Promise.resolve('Invalid content'), + dir: false, + name: 'invalid.md' + }, + }; + + const mockZipInstance = { + loadAsync: jest.fn().mockResolvedValue({ + files: mockFiles, + }), + }; + (JSZip as jest.Mock).mockImplementation(() => mockZipInstance); + + render( + + + + + + ); + + const file = new File(['invalid content'], 'test.zip', { type: 'application/zip' }); + const input = screen.getByTestId('file-input'); + + fireEvent.change(input, { target: { files: [file] } }); + + await waitFor(() => { + const calls = mockSetExtractedDocs.mock.calls[0][0]; + expect(calls[0]).toEqual( + expect.objectContaining({ + state: 'error', + error: mockParseMarkdownError, + doc: expect.objectContaining({ + title: 'invalid' + }) + }) + ); + }, { timeout: 3000 }); + }); + + it('handles error when processing invalid file', async () => { + const mockZipInstance = { + loadAsync: jest.fn().mockRejectedValue(new Error('Invalid ZIP file')), + }; + (JSZip as jest.Mock).mockImplementation(() => mockZipInstance); + + render( + + + + + + ); + + const file = new File(['invalid content'], 'test.zip', { type: 'application/zip' }); + const input = screen.getByTestId('file-input'); + + fireEvent.change(input, { target: { files: [file] } }); + + await waitFor(() => { + expect(mockSetExtractedDocs).not.toHaveBeenCalled(); + expect(screen.getByTestId('file-uploader')).toHaveAttribute('data-state', 'error'); + }); + }); + + it('handles empty zip file', async () => { + const mockZipInstance = { + loadAsync: jest.fn().mockResolvedValue({ + files: {}, + }), + }; + (JSZip as jest.Mock).mockImplementation(() => mockZipInstance); + + render( + + + + + + ); + + const file = new File([''], 'empty.zip', { type: 'application/zip' }); + const input = screen.getByTestId('file-input'); + + fireEvent.change(input, { target: { files: [file] } }); + + await waitFor(() => { + expect(mockSetExtractedDocs).toHaveBeenCalledWith([]); + expect(screen.getByTestId('file-uploader')).toHaveAttribute('data-state', 'success'); + }); + }); +}); \ No newline at end of file diff --git a/src/frontend/apps/impress/src/features/docs/docs-import/components/__tests__/PreviewDocsImport.spec.tsx b/src/frontend/apps/impress/src/features/docs/docs-import/components/__tests__/PreviewDocsImport.spec.tsx new file mode 100644 index 000000000..91e975dcc --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/docs-import/components/__tests__/PreviewDocsImport.spec.tsx @@ -0,0 +1,59 @@ +import { render, screen } from '@testing-library/react'; +import { PreviewDocsImport } from '../PreviewDocsImport'; +import { DocToImport } from '../../types'; + +describe('', () => { + it('displays empty state message when no documents', () => { + render(); + + expect(screen.getByText('No documents to import')).toBeInTheDocument(); + expect(screen.queryByRole('progressbar')).not.toBeInTheDocument(); + }); + + it('renders a simple list of documents with their states', () => { + const docs: DocToImport[] = [ + { doc: { title: 'Doc 1' }, state: 'success' }, + { doc: { title: 'Doc 2' }, state: 'error', error: 'Failed to import' }, + { doc: { title: 'Doc 3' }, state: 'pending' } + ]; + + render(); + + expect(screen.getByText('Doc 1')).toBeInTheDocument(); + expect(screen.getByText('success')).toBeInTheDocument(); + expect(screen.getByText('Doc 2')).toBeInTheDocument(); + expect(screen.getByText('error')).toBeInTheDocument(); + expect(screen.getByText('Failed to import')).toBeInTheDocument(); + expect(screen.getByText('Doc 3')).toBeInTheDocument(); + expect(screen.getByText('pending')).toBeInTheDocument(); + }); + + it('renders nested document structure correctly', () => { + const docs: DocToImport[] = [{ + doc: { title: 'Parent Doc' }, + state: 'success', + children: [ + { doc: { title: 'Child Doc' }, state: 'success' } + ] + }]; + + render(); + + expect(screen.getByText('Parent Doc')).toBeInTheDocument(); + expect(screen.getByText('Child Doc')).toBeInTheDocument(); + }); + + it('calculates and displays progress correctly', () => { + const docs: DocToImport[] = [ + { doc: { title: 'Doc 1' }, state: 'success' }, + { doc: { title: 'Doc 2' }, state: 'error' }, + { doc: { title: 'Doc 3' }, state: 'pending' }, + { doc: { title: 'Doc 4' }, state: 'success' } + ]; + + render(); + + expect(screen.getByText('50% completed')).toBeInTheDocument(); + expect(screen.getByText('25% failed')).toBeInTheDocument(); + }); +}); diff --git a/src/frontend/apps/impress/src/features/docs/docs-import/types.ts b/src/frontend/apps/impress/src/features/docs/docs-import/types.ts new file mode 100644 index 000000000..93c53116c --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/docs-import/types.ts @@ -0,0 +1,10 @@ +import { Doc } from "../doc-management/types"; + +export type DocToImport = { + doc: Doc; + state: 'pending' | 'success' | 'error'; + error?: Error; + children?: DocToImport[]; +}; + +export type ImportState = 'idle' | 'importing' | 'completed'; \ No newline at end of file diff --git a/src/frontend/apps/impress/src/pages/import/index.tsx b/src/frontend/apps/impress/src/pages/import/index.tsx new file mode 100644 index 000000000..86dba4b81 --- /dev/null +++ b/src/frontend/apps/impress/src/pages/import/index.tsx @@ -0,0 +1,15 @@ +import type { ReactElement } from 'react'; + +import { MainLayout } from '@/layouts'; +import { NextPageWithLayout } from '@/types/next'; +import { DocsImportContainer } from '@/features/docs/docs-import/components/DocsImportContainer'; + +const Page: NextPageWithLayout = () => { + return ; +}; + +Page.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export default Page; diff --git a/src/frontend/package.json b/src/frontend/package.json index 21e7e1009..dfc969d5b 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -36,5 +36,6 @@ "react": "18.3.1", "react-dom": "18.3.1", "typescript": "5.7.2" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock index f44320b62..7fb87592e 100644 --- a/src/frontend/yarn.lock +++ b/src/frontend/yarn.lock @@ -4736,7 +4736,7 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@22.10.3": +"@types/node@*": version "22.10.3" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.3.tgz#cdc2a89bf6e5d5e593fad08e83f74d7348d5dd10" integrity sha512-DifAyw4BkrufCILvD3ucnuN8eydUfc/C1GlyrnI+LK6543w5/L3VeVgf05o3B4fqSXP1dKYLOZsKfutpxPzZrw== @@ -4798,7 +4798,7 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== -"@types/react-dom@*", "@types/react-dom@18.3.1": +"@types/react-dom@*": version "18.3.1" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.1.tgz#1e4654c08a9cdcfb6594c780ac59b55aad42fe07" integrity sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ== @@ -4943,7 +4943,7 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@*", "@typescript-eslint/eslint-plugin@8.19.0", "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": +"@typescript-eslint/eslint-plugin@*", "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": version "8.19.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.0.tgz#2b1e1b791e21d5fc27ddc93884db066444f597b5" integrity sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q== @@ -4958,7 +4958,7 @@ natural-compare "^1.4.0" ts-api-utils "^1.3.0" -"@typescript-eslint/parser@*", "@typescript-eslint/parser@8.19.0", "@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": +"@typescript-eslint/parser@*", "@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": version "8.19.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.19.0.tgz#f1512e6e5c491b03aabb2718b95becde22b15292" integrity sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw== @@ -7048,7 +7048,7 @@ eslint-visitor-keys@^4.2.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== -eslint@*, eslint@8.57.0: +eslint@*: version "8.57.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== @@ -8271,6 +8271,11 @@ ignore@^6.0.2: resolved "https://registry.yarnpkg.com/ignore/-/ignore-6.0.2.tgz#77cccb72a55796af1b6d2f9eb14fa326d24f4283" integrity sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A== +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -9258,6 +9263,16 @@ jsonpointer@^5.0.0: object.assign "^4.1.4" object.values "^1.1.6" +jszip@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" + integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + setimmediate "^1.0.5" + keyv@^4.5.3, keyv@^4.5.4: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -9322,6 +9337,13 @@ lib0@^0.2.42, lib0@^0.2.47, lib0@^0.2.85, lib0@^0.2.87, lib0@^0.2.98: dependencies: isomorphic.js "^0.2.4" +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + lilconfig@^3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" @@ -10426,6 +10448,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +pako@~1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -11939,6 +11966,11 @@ set-function-name@^2.0.2: functions-have-names "^1.2.3" has-property-descriptors "^1.0.2" +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -12930,7 +12962,7 @@ typed-array-length@^1.0.7: possible-typed-array-names "^1.0.0" reflect.getprototypeof "^1.0.6" -typescript@*, typescript@5.7.2, typescript@^5.0.4: +typescript@*, typescript@^5.0.4: version "5.7.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6" integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==