diff --git a/apps/client/src/common/api/db.ts b/apps/client/src/common/api/db.ts index 27a3b5e50..168072cb7 100644 --- a/apps/client/src/common/api/db.ts +++ b/apps/client/src/common/api/db.ts @@ -11,15 +11,15 @@ const dbPath = `${apiEntryUrl}/db`; /** * HTTP request to the current DB */ -async function getDb(): Promise> { - return axios.get(`${dbPath}/download`); +async function getDb(fileName?: string): Promise> { + return axios.post(`${dbPath}/download/`, { fileName }); } /** * Request download of the current project file * @param fileName */ -export async function downloadProject(fileName: string = 'ontime-project') { +export async function downloadProject(fileName: string) { try { const { data, name } = await fileDownload(fileName); @@ -152,7 +152,7 @@ export async function getInfo(): Promise { * @returns */ async function fileDownload(fileName: string): Promise<{ data: DatabaseModel; name: string }> { - const response = await getDb(); + const response = await getDb(fileName); const headerLine = response.headers['Content-Disposition']; diff --git a/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx b/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx index cb49b25d2..b08ea15b6 100644 --- a/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx +++ b/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx @@ -169,7 +169,7 @@ function ActionMenu({ Rename Duplicate Download - {current && Export CSV Rundown} + Export CSV Rundown Delete diff --git a/apps/server/package.json b/apps/server/package.json index 97e3d4364..f42ddd105 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -10,9 +10,8 @@ "cors": "^2.8.5", "dotenv": "^16.0.1", "express": "^4.18.2", - "express-session": "^1.17.3", "express-static-gzip": "^2.1.7", - "express-validator": "^6.14.2", + "express-validator": "^7.0.1", "fast-equals": "^5.0.1", "google-auth-library": "^9.4.2", "got": "^14.0.0", diff --git a/apps/server/src/api-data/db/db.controller.ts b/apps/server/src/api-data/db/db.controller.ts index 8afd6fbbc..451f51d2e 100644 --- a/apps/server/src/api-data/db/db.controller.ts +++ b/apps/server/src/api-data/db/db.controller.ts @@ -6,17 +6,19 @@ import { ProjectData, ProjectFileListResponse, } from 'ontime-types'; +import { getErrorMessage } from 'ontime-utils'; +import { join } from 'path'; +import { existsSync } from 'fs'; import type { Request, Response } from 'express'; import { failEmptyObjects } from '../../utils/routerUtils.js'; -import { resolveDbPath, resolveProjectsDirectory } from '../../setup/index.js'; +import { resolveDbDirectory, resolveProjectsDirectory } from '../../setup/index.js'; import * as projectService from '../../services/project-service/ProjectService.js'; import { ensureJsonExtension } from '../../utils/fileManagement.js'; import { generateUniqueFileName } from '../../utils/generateUniqueFilename.js'; import { appStateService } from '../../services/app-state-service/AppStateService.js'; -import { getErrorMessage } from 'ontime-utils'; export async function patchPartialProjectFile(req: Request, res: Response) { // all fields are optional in validation @@ -76,9 +78,30 @@ export async function createProjectFile(req: Request, res: Response<{ filename: } } -export async function projectDownload(_req: Request, res: Response) { - const fileTitle = projectService.getProjectTitle(); - res.download(resolveDbPath, `${fileTitle}.json`, (error) => { +/** + * Utility function finds the correct project file to download + */ +function selectProjectFile(fileName?: string) { + const projectsDirectory = resolveDbDirectory; + const fileToDownload = fileName ? ensureJsonExtension(fileName) : projectService.getProjectTitle(); + const pathToFile = join(projectsDirectory, fileToDownload); + + return { pathToFile, name: fileToDownload }; +} + +/** + * Allows downloading of a optionally given project files + * If no {filename} is provided, loaded file will be served + */ +export async function projectDownload(req: Request, res: Response) { + const { pathToFile, name } = selectProjectFile(req.body?.fileName); + + // Check if the file exists before attempting to download + if (!existsSync(pathToFile)) { + return res.status(404).send({ message: `Project ${name} not found.` }); + } + + res.download(pathToFile, name, (error) => { if (error) { const message = getErrorMessage(error); res.status(500).send({ message }); diff --git a/apps/server/src/api-data/db/db.router.ts b/apps/server/src/api-data/db/db.router.ts index f7fa36c53..eb660d49d 100644 --- a/apps/server/src/api-data/db/db.router.ts +++ b/apps/server/src/api-data/db/db.router.ts @@ -16,6 +16,7 @@ import { uploadProjectFile } from './db.middleware.js'; import { projectSanitiser, sanitizeProjectFilename, + validateDownloadProject, validateLoadProjectFile, validatePatchProjectFile, validateProjectDuplicate, @@ -24,7 +25,7 @@ import { export const router = express.Router(); -router.get('/download', projectDownload); +router.post('/download', validateDownloadProject, projectDownload); router.post('/upload', uploadProjectFile, postProjectFile); router.patch('/', validatePatchProjectFile, patchPartialProjectFile); diff --git a/apps/server/src/api-data/db/db.validation.ts b/apps/server/src/api-data/db/db.validation.ts index 250caebcf..1dedfd32c 100644 --- a/apps/server/src/api-data/db/db.validation.ts +++ b/apps/server/src/api-data/db/db.validation.ts @@ -104,3 +104,19 @@ export const validateProjectRename = [ next(); }, ]; + +/** + * @description Validates a download request which can include an optional project name. + */ +export const validateDownloadProject = [ + body('fileName').isString().optional(), + + (req: Request, res: Response, next: NextFunction) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(422).json({ errors: errors.array() }); + } + + next(); + }, +]; diff --git a/apps/server/src/utils/fileManagement.ts b/apps/server/src/utils/fileManagement.ts index 62e9da0f8..8911c3c5e 100644 --- a/apps/server/src/utils/fileManagement.ts +++ b/apps/server/src/utils/fileManagement.ts @@ -20,9 +20,7 @@ export function ensureDirectory(directory: string): void { * Ensures that a filename ends with .json extension */ export function ensureJsonExtension(filename: string): string { - if (!filename) return filename; - - return filename.includes('.json') ? filename : `${filename}.json`; + return filename.endsWith('.json') ? filename : `${filename}.json`; } /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cf7b1f4ff..9ea0ced01 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -146,7 +146,7 @@ importers: version: 13.4.0(react-dom@18.2.0)(react@18.2.0) '@testing-library/user-event': specifier: ^14.1.1 - version: 14.4.3(@testing-library/dom@9.3.4) + version: 14.4.3(@testing-library/dom@10.1.0) '@types/color': specifier: ^3.0.3 version: 3.0.3 @@ -261,15 +261,12 @@ importers: express: specifier: ^4.18.2 version: 4.18.2 - express-session: - specifier: ^1.17.3 - version: 1.17.3 express-static-gzip: specifier: ^2.1.7 version: 2.1.7 express-validator: - specifier: ^6.14.2 - version: 6.14.2 + specifier: ^7.0.1 + version: 7.0.1 fast-equals: specifier: ^5.0.1 version: 5.0.1 @@ -345,7 +342,7 @@ importers: version: 8.53.0 eslint-plugin-prettier: specifier: ^5.0.1 - version: 5.0.1(eslint-config-prettier@9.0.0)(eslint@8.53.0)(prettier@3.0.3) + version: 5.0.1(eslint@8.53.0)(prettier@3.0.3) nodemon: specifier: ^2.0.20 version: 2.0.20 @@ -369,7 +366,7 @@ importers: version: 5.4.3 vitest: specifier: ^1.2.2 - version: 1.2.2(@types/node@18.11.18)(jsdom@21.1.0)(sass@1.57.1) + version: 1.2.2(@types/node@18.11.18) packages/types: devDependencies: @@ -463,6 +460,14 @@ packages: '@babel/highlight': 7.23.4 chalk: 2.4.2 + /@babel/code-frame@7.24.2: + resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.24.5 + picocolors: 1.0.0 + dev: true + /@babel/compat-data@7.23.5: resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==} engines: {node: '>=6.9.0'} @@ -624,6 +629,11 @@ packages: resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} + /@babel/helper-validator-identifier@7.24.5: + resolution: {integrity: sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/helper-validator-option@7.23.5: resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} engines: {node: '>=6.9.0'} @@ -648,6 +658,16 @@ packages: chalk: 2.4.2 js-tokens: 4.0.0 + /@babel/highlight@7.24.5: + resolution: {integrity: sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.24.5 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.0 + dev: true + /@babel/parser@7.23.6: resolution: {integrity: sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==} engines: {node: '>=6.0.0'} @@ -697,8 +717,8 @@ packages: regenerator-runtime: 0.13.11 dev: false - /@babel/runtime@7.24.0: - resolution: {integrity: sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==} + /@babel/runtime@7.24.5: + resolution: {integrity: sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.1 @@ -3059,6 +3079,20 @@ packages: engines: {node: '>=12'} dev: false + /@testing-library/dom@10.1.0: + resolution: {integrity: sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA==} + engines: {node: '>=18'} + dependencies: + '@babel/code-frame': 7.24.2 + '@babel/runtime': 7.24.5 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + dev: true + /@testing-library/dom@8.19.1: resolution: {integrity: sha512-P6iIPyYQ+qH8CvGauAqanhVnjrnRe0IZFSYCeGkSRW9q3u8bdVn2NPI+lasFyVsEQn1J/IFmp5Aax41+dAP9wg==} engines: {node: '>=12'} @@ -3073,20 +3107,6 @@ packages: pretty-format: 27.5.1 dev: true - /@testing-library/dom@9.3.4: - resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==} - engines: {node: '>=14'} - dependencies: - '@babel/code-frame': 7.23.5 - '@babel/runtime': 7.24.0 - '@types/aria-query': 5.0.4 - aria-query: 5.1.3 - chalk: 4.1.2 - dom-accessibility-api: 0.5.16 - lz-string: 1.5.0 - pretty-format: 27.5.1 - dev: true - /@testing-library/jest-dom@5.16.5: resolution: {integrity: sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==} engines: {node: '>=8', npm: '>=6', yarn: '>=1'} @@ -3116,13 +3136,13 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /@testing-library/user-event@14.4.3(@testing-library/dom@9.3.4): + /@testing-library/user-event@14.4.3(@testing-library/dom@10.1.0): resolution: {integrity: sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==} engines: {node: '>=12', npm: '>=6'} peerDependencies: '@testing-library/dom': '>=7.21.4' dependencies: - '@testing-library/dom': 9.3.4 + '@testing-library/dom': 10.1.0 dev: true /@tootallnate/once@2.0.0: @@ -3975,7 +3995,7 @@ packages: lodash.isplainobject: 4.0.6 lodash.union: 4.6.0 normalize-path: 3.0.0 - readable-stream: 2.3.7 + readable-stream: 2.3.8 dev: true /archiver-utils@3.0.4: @@ -4053,6 +4073,12 @@ packages: deep-equal: 2.2.0 dev: true + /aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + dependencies: + dequal: 2.0.3 + dev: true + /array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} dev: false @@ -4655,11 +4681,6 @@ packages: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} dev: false - /cookie@0.4.2: - resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} - engines: {node: '>= 0.6'} - dev: false - /cookie@0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} @@ -4955,6 +4976,11 @@ packages: engines: {node: '>= 0.8'} dev: false + /dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + dev: true + /destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -5426,6 +5452,26 @@ packages: synckit: 0.8.5 dev: true + /eslint-plugin-prettier@5.0.1(eslint@8.53.0)(prettier@3.0.3): + resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '*' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + dependencies: + eslint: 8.53.0 + prettier: 3.0.3 + prettier-linter-helpers: 1.0.0 + synckit: 0.8.5 + dev: true + /eslint-plugin-react-hooks@4.6.0(eslint@8.53.0): resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} engines: {node: '>=10'} @@ -5682,22 +5728,6 @@ packages: jest-util: 29.3.1 dev: true - /express-session@1.17.3: - resolution: {integrity: sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==} - engines: {node: '>= 0.8.0'} - dependencies: - cookie: 0.4.2 - cookie-signature: 1.0.6 - debug: 2.6.9 - depd: 2.0.0 - on-headers: 1.0.2 - parseurl: 1.3.3 - safe-buffer: 5.2.1 - uid-safe: 2.1.5 - transitivePeerDependencies: - - supports-color - dev: false - /express-static-gzip@2.1.7: resolution: {integrity: sha512-QOCZUC+lhPPCjIJKpQGu1Oa61Axg9Mq09Qvit8Of7kzpMuwDeMSqjjQteQS3OVw/GkENBoSBheuQDWPlngImvw==} dependencies: @@ -5706,12 +5736,12 @@ packages: - supports-color dev: false - /express-validator@6.14.2: - resolution: {integrity: sha512-8XfAUrQ6Y7dIIuy9KcUPCfG/uCbvREctrxf5EeeME+ulanJ4iiW71lWmm9r4YcKKYOCBMan0WpVg7FtHu4Z4Wg==} + /express-validator@7.0.1: + resolution: {integrity: sha512-oB+z9QOzQIE8FnlINqyIFA8eIckahC6qc8KtqLdLJcU3/phVyuhXH3bA4qzcrhme+1RYaCSwrq+TlZ/kAKIARA==} engines: {node: '>= 8.0.0'} dependencies: lodash: 4.17.21 - validator: 13.7.0 + validator: 13.12.0 dev: false /express@4.18.2: @@ -7577,6 +7607,7 @@ packages: /on-headers@1.0.2: resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} engines: {node: '>= 0.8'} + dev: true /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -7960,11 +7991,6 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} - /random-bytes@1.0.0: - resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==} - engines: {node: '>= 0.8'} - dev: false - /range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -8178,6 +8204,18 @@ packages: string_decoder: 1.1.1 util-deprecate: 1.0.2 + /readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + dev: true + /readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -9179,13 +9217,6 @@ packages: resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==} dev: true - /uid-safe@2.1.5: - resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==} - engines: {node: '>= 0.8'} - dependencies: - random-bytes: 1.0.0 - dev: false - /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: @@ -9325,8 +9356,8 @@ packages: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} dev: true - /validator@13.7.0: - resolution: {integrity: sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==} + /validator@13.12.0: + resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} engines: {node: '>= 0.10'} dev: false @@ -9346,6 +9377,27 @@ packages: dev: true optional: true + /vite-node@1.2.2(@types/node@18.11.18): + resolution: {integrity: sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + pathe: 1.1.1 + picocolors: 1.0.0 + vite: 5.1.0(@types/node@18.11.18) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vite-node@1.2.2(@types/node@18.11.18)(sass@1.57.1): resolution: {integrity: sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -9408,6 +9460,42 @@ packages: - typescript dev: true + /vite@5.1.0(@types/node@18.11.18): + resolution: {integrity: sha512-STmSFzhY4ljuhz14bg9LkMTk3d98IO6DIArnTY6MeBwiD1Za2StcQtz7fzOUnRCqrHSD5+OS2reg4HOz1eoLnw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 18.11.18 + esbuild: 0.19.10 + postcss: 8.4.35 + rollup: 4.9.1 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /vite@5.1.0(@types/node@18.11.18)(sass@1.57.1): resolution: {integrity: sha512-STmSFzhY4ljuhz14bg9LkMTk3d98IO6DIArnTY6MeBwiD1Za2StcQtz7fzOUnRCqrHSD5+OS2reg4HOz1eoLnw==} engines: {node: ^18.0.0 || >=20.0.0} @@ -9445,6 +9533,63 @@ packages: fsevents: 2.3.3 dev: true + /vitest@1.2.2(@types/node@18.11.18): + resolution: {integrity: sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': ^1.0.0 + '@vitest/ui': ^1.0.0 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/node': 18.11.18 + '@vitest/expect': 1.2.2 + '@vitest/runner': 1.2.2 + '@vitest/snapshot': 1.2.2 + '@vitest/spy': 1.2.2 + '@vitest/utils': 1.2.2 + acorn-walk: 8.3.2 + cac: 6.7.14 + chai: 4.3.10 + debug: 4.3.4 + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.5 + pathe: 1.1.1 + picocolors: 1.0.0 + std-env: 3.6.0 + strip-literal: 1.3.0 + tinybench: 2.5.1 + tinypool: 0.8.2 + vite: 5.1.0(@types/node@18.11.18) + vite-node: 1.2.2(@types/node@18.11.18) + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vitest@1.2.2(@types/node@18.11.18)(jsdom@21.1.0)(sass@1.57.1): resolution: {integrity: sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==} engines: {node: ^18.0.0 || >=20.0.0}