diff --git a/apps/backend/package.json b/apps/backend/package.json index a1b420a2..f6e61e5f 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -1,33 +1,39 @@ { "name": "backend", "version": "1.0.0", + "type": "module", "dependencies": { "@aws-sdk/client-cognito-identity-provider": "^3.496.0", "@aws-sdk/client-s3": "^3.496.0", + "archiver": "^7.0.0", "aws-sdk-mock": "^5.1.0", "axios": "^1.6.0", "body-parser": "^1.19.0", "cors": "^2.8.5", + "csv-writer": "^1.6.0", "express": "^4.17.1", "express-async-errors": "^3.1.1", "express-fileupload": "^1.2.0", + "file-type": "^21.0.0", "helmet": "^7.1.0", "join-images": "^1.1.5", "lodash": "^4.17.21", "loglevel": "^1.7.1", "mongodb-memory-server": "^7.4.0", - "mongoose": "^6.0.6", + "mongoose": "^6.13.8", "mongoose-encryption": "^2.1.0", "node-2fa": "^2.0.2", "omit-deep-lodash": "^1.1.5", "pad": "^3.2.0", "pdf2pic": "^3.1.3", + "sharp": "^0.34.3", "supertest": "^6.1.3", "twilio": "^3.71.1" }, "devDependencies": { "@3dp4me/types": "workspace:*", "@smithy/types": "^4.1.0", + "@types/archiver": "^6.0.0", "@types/body-parser": "^1.19.5", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", @@ -54,13 +60,13 @@ ] }, "license": "MIT", - "main": "index.js", + "exports": "./build/index.js", "scripts": { - "build": "webpack", + "build": "webpack --config webpack.prod.js", "clean": "rimraf .turbo build dist node_modules", "lint": "eslint --fix src/**/*.ts", "lint:check": "eslint src/**/*.ts", - "start": "rm -rf ./dist && tsc && doppler run -- node ./dist/src/index.js", + "start": "rm -rf ./dist && webpack --config webpack.dev.js && doppler run -- node ./build/bundle.js", "test": "cross-env S3_BUCKET_NAME=test jest --runInBand --forceExit" } } diff --git a/apps/backend/scripts/dataextraction.ts b/apps/backend/scripts/dataextraction.ts new file mode 100644 index 00000000..6462e850 --- /dev/null +++ b/apps/backend/scripts/dataextraction.ts @@ -0,0 +1,535 @@ +/* + * Combined Export Script - Steps 1, 2, and 3 + * STEP 1: Generate CSV of all basic patient info + * STEP 2: Generate CSV of all steps with string-convertible data: String, MultilineString, + * Number, Date, Phone, RadioButton, MultiSelect, Tags + * STEP 3: Export media files (File, Audio, Photo, Signature) from S3 to local filesystem (functions related to S3 are in awsS3Helpers.ts) + * STEP 4: Package everything into a ZIP file + * + * String CSV excludes: Header, Divider, File, Audio, Photo, Signature, Map + */ + +import fs from 'fs'; +import path from 'path'; +import mongoose from 'mongoose'; +import { createObjectCsvWriter } from 'csv-writer'; +import { initDB } from '../src/utils/initDb'; +import { PatientModel } from '../src/models/Patient'; +import { StepModel } from '../src/models/Metadata'; +import { FieldType } from '@3dp4me/types'; +import { downloadFile, fileExistsInS3, downloadAndSaveFileWithTypeDetection, sanitizeFilename } from '../src/utils/aws/awsS3Helpers'; +import archiver from 'archiver'; +import { fileTypeFromBuffer } from 'file-type'; + +mongoose.set('strictQuery', false); + +const EXPORT_DIR = path.join(__dirname, 'step_exports'); +const MEDIA_EXPORT_DIR = path.join(__dirname, 'patients'); +const ZIP_OUTPUT_DIR = path.join(__dirname, 'exports'); + +const INCLUDED_TYPES = [ + FieldType.STRING, + FieldType.MULTILINE_STRING, + FieldType.NUMBER, + FieldType.DATE, + FieldType.PHONE, + FieldType.RADIO_BUTTON, + FieldType.MULTI_SELECT, + FieldType.TAGS, +]; + +const IGNORED_FIELD_TYPES = [ + FieldType.FILE, + FieldType.AUDIO, + FieldType.PHOTO, + FieldType.SIGNATURE, + FieldType.MAP, + FieldType.DIVIDER, + FieldType.HEADER, +]; + +const MEDIA_FIELD_TYPES = [ + FieldType.FILE, + FieldType.AUDIO, + FieldType.PHOTO, + // FieldType.SIGNATURE, (not media, stored as an array of points on a canvas in mongo. generate an image of this signature and save it) +]; + +// What to export? +interface ExportOptions { + includeDeleted?: boolean; + includeHidden?: boolean; + zipFilename?: string; +} + +async function createZipArchive(zipFilename: string): Promise { + console.log('\n=== STEP 4: Creating ZIP Archive ==='); + + if (!fs.existsSync(ZIP_OUTPUT_DIR)) { + fs.mkdirSync(ZIP_OUTPUT_DIR, { recursive: true }); + } + + const zipPath = path.join(ZIP_OUTPUT_DIR, zipFilename); + const output = fs.createWriteStream(zipPath); + const archive = archiver('zip', { + zlib: { level: 9 } // Maximum compression + }); + + return new Promise((resolve, reject) => { + output.on('close', () => { + const sizeInMB = (archive.pointer() / 1024 / 1024).toFixed(2); + console.log(`ZIP archive created: ${zipPath}`); + console.log(`Archive size: ${sizeInMB} MB`); + resolve(zipPath); + }); + + // Good practice to catch warnings (ie stat failures and other non-blocking errors) + archive.on('warning', (err) => { + if (err.code === 'ENOENT') { + // Log warning for missing files but don't fail + console.warn('Archive warning - file not found:', err.message); + } else { + // Reject promise for other types of warnings as they indicate real issues + console.error('Archive warning (treating as error):', err); + reject(err); + } + }); + + archive.on('error', (err) => { + console.error('Error creating ZIP archive:', err); + reject(err); + }); + + archive.pipe(output); + + // Add patients.csv if it exists + const patientsCsvPath = path.join(__dirname, 'patients.csv'); + if (fs.existsSync(patientsCsvPath)) { + archive.file(patientsCsvPath, { name: 'patients.csv' }); + console.log('Added patients.csv to archive'); + } + + // Add step CSV files if directory exists + if (fs.existsSync(EXPORT_DIR)) { + archive.directory(EXPORT_DIR, 'step_csvs'); + console.log('Added step CSV files to archive'); + } + + // Add media files if directory exists + if (fs.existsSync(MEDIA_EXPORT_DIR)) { + archive.directory(MEDIA_EXPORT_DIR, 'patients'); + console.log('Added media files to archive'); + } + + archive.finalize(); + }); +} + +// STEP 1: Generate patient CSV (makes patients.csv) +async function generatePatientCSV() { + console.log('\n=== STEP 1: Generating Patient CSV ==='); + + const patients = await PatientModel.find(); + const patientRecords = patients.map(p => { + const obj = p.toObject(); + + return { + ...obj, + dateCreated: obj.dateCreated?.toISOString(), + lastEdited: obj.lastEdited?.toISOString(), + }; + }); + + const csvWriter = createObjectCsvWriter({ + path: path.join(__dirname, 'patients.csv'), + header: [ + { id: 'dateCreated', title: 'Date Created' }, + { id: 'orderId', title: 'Order ID' }, + { id: 'lastEdited', title: 'Last Edited' }, + { id: 'lastEditedBy', title: 'Last Edited By' }, + { id: 'status', title: 'Status' }, + { id: 'phoneNumber', title: 'Phone Number' }, + { id: 'orderYear', title: 'Order Year' }, + { id: 'firstName', title: 'First Name' }, + { id: 'fathersName', title: 'Father\'s Name' }, + { id: 'grandfathersName', title: 'Grandfather\'s Name' }, + { id: 'familyName', title: 'Family Name' }, + ] + }); + + await csvWriter.writeRecords(patientRecords); + console.log(`Generated patients.csv with ${patientRecords.length} records`); +} + +// STEP 2: Generate step CSVs (makes step_csvs/*.csv) +// Helper function to get filtered step definitions (moved to global scope) +async function getSteps(options: ExportOptions): Promise { + const { includeDeleted = false, includeHidden = false } = options; + + // Build query filter based on options + const stepFilter: any = {}; + if (!includeDeleted) { + stepFilter.isDeleted = { $ne: true }; + } + if (!includeHidden) { + stepFilter.isHidden = { $ne: true }; + } + + // Get step definitions based on filter + const stepDefinitions = await StepModel.find(stepFilter).lean(); + console.log(`Found ${stepDefinitions.length} step definitions`); + + return stepDefinitions; +} + +async function generateStepCSVs(options: ExportOptions = {}) { + const { includeDeleted = false, includeHidden = false } = options; // default not include hidden or deleted, can be changed + + console.log('\n=== STEP 2: Generating Step CSVs ==='); + console.log(`Options: includeDeleted=${includeDeleted}, includeHidden=${includeHidden}`); + + if (!fs.existsSync(EXPORT_DIR)) fs.mkdirSync(EXPORT_DIR); + + // Get step definitions using the global getSteps function + const stepDefinitions = await getSteps(options); + + const patients = await PatientModel.find().lean(); + console.log(`Found ${patients.length} patients`); + + for (const stepDef of stepDefinitions) { + const stepKey = stepDef.key; + console.log(`Processing step: ${stepKey}`); + + // Get the mongoose model for this step + let StepDataModel; + try { + StepDataModel = mongoose.model(stepKey); + } catch (error) { + console.log(`No model found for step ${stepKey}, skipping`); + continue; + } + + const records = []; + + for (const patient of patients) { + const stepDoc = await StepDataModel.findOne({ patientId: patient._id }); + if (!stepDoc) continue; + + const row: Record = { + patientId: patient.orderId, + }; + + // Process each field in the step definition + for (const field of stepDef.fields) { + // Skip hidden or deleted fields if not including them + if (!includeHidden && field.isHidden) continue; + if (!includeDeleted && field.isDeleted) continue; + + if (IGNORED_FIELD_TYPES.includes(field.fieldType)) continue; + + if (field.fieldType === FieldType.FIELD_GROUP && Array.isArray(field.subFields)) { + // Handle field groups (nested fields) + for (const subField of field.subFields) { + // Skip hidden or deleted subfields if not including them + if (!includeHidden && subField.isHidden) continue; + if (!includeDeleted && subField.isDeleted) continue; + + if (INCLUDED_TYPES.includes(subField.fieldType)) { + row[subField.key] = formatField(stepDoc[subField.key], subField.fieldType); + } + } + } else if (INCLUDED_TYPES.includes(field.fieldType)) { + // Handle regular fields + row[field.key] = formatField(stepDoc[field.key], field.fieldType); + } + } + + records.push(row); + } + + if (records.length > 0) { + // Create a mapping of field keys to their display names + const fieldDisplayNames = new Map(); + // Add patient ID display name + fieldDisplayNames.set('patientId', 'Patient ID'); + + // Map field keys to display names from step definition + for (const field of stepDef.fields) { + if (!includeHidden && field.isHidden) continue; + if (!includeDeleted && field.isDeleted) continue; + if (IGNORED_FIELD_TYPES.includes(field.fieldType)) continue; + + if (field.fieldType === FieldType.FIELD_GROUP && Array.isArray(field.subFields)) { + for (const subField of field.subFields) { + if (!includeHidden && subField.isHidden) continue; + if (!includeDeleted && subField.isDeleted) continue; + if (INCLUDED_TYPES.includes(subField.fieldType)) { + fieldDisplayNames.set(subField.key, subField.displayName?.EN || subField.key); + } + } + } else if (INCLUDED_TYPES.includes(field.fieldType)) { + fieldDisplayNames.set(field.key, field.displayName?.EN || field.key); + } + } + + const csvWriter = createObjectCsvWriter({ + path: path.join(EXPORT_DIR, `${stepKey}.csv`), + header: Object.keys(records[0]).map(key => ({ + id: key, + title: fieldDisplayNames.get(key) || key + })), + }); + + await csvWriter.writeRecords(records); + console.log(`Wrote ${records.length} records to ${stepKey}.csv`); + } else { + console.log(`No records found for step ${stepKey}, skip`); + } + } +} + +// STEP 3: Export media files (makes patients/*/*.jpg) +async function exportStepMedia(options: ExportOptions = {}) { + const { includeDeleted = false, includeHidden = false } = options; + + console.log('\n=== STEP 3: Exporting Media Files ==='); + console.log(`Options: includeDeleted=${includeDeleted}, includeHidden=${includeHidden}`); + + if (!fs.existsSync(MEDIA_EXPORT_DIR)) fs.mkdirSync(MEDIA_EXPORT_DIR); + + const stepDefinitions = await getSteps(options); + + const patients = await PatientModel.find(); + console.log(`Found ${patients.length} patients`); + + let totalFilesDownloaded = 0; + + for (const stepDef of stepDefinitions) { + const stepKey = stepDef.key; + console.log(`Processing step: ${stepKey}`); + + // Get the mongoose model for this step + let StepDataModel; + try { + StepDataModel = mongoose.model(stepKey); + } catch (error) { + console.log(`No model found for step ${stepKey}, skip`); + continue; + } + + for (const patient of patients) { + const stepDoc = await StepDataModel.findOne({ patientId: patient._id }); + if (!stepDoc) continue; + + const patientDir = path.join(MEDIA_EXPORT_DIR, patient.orderId); + const stepDir = path.join(patientDir, stepKey); + + // Track if we actually downloaded any files for this step + let hasDownloadedFiles = false; + + // Process regular fields + for (const field of stepDef.fields) { + // Check if field should be included based on options + if (!includeHidden && field.isHidden) continue; + + if (MEDIA_FIELD_TYPES.includes(field.fieldType)) { + const fileData = stepDoc[field.key]; + if (Array.isArray(fileData)) { + // Handle array of files + for (const file of fileData) { + if (file && file.filename) { + const s3Key = `${patient._id}/${stepKey}/${field.key}/${file.filename}`; + const fileExists = await fileExistsInS3(s3Key); + + if (fileExists) { + // Create directory only when we have actual files + if (!hasDownloadedFiles) { + if (!fs.existsSync(patientDir)) fs.mkdirSync(patientDir, { recursive: true }); + if (!fs.existsSync(stepDir)) fs.mkdirSync(stepDir, { recursive: true }); + hasDownloadedFiles = true; + } + + const sanitizedFilename = sanitizeFilename(file.filename); + const localPath = path.join(stepDir, sanitizedFilename); + const success = await downloadAndSaveFileWithTypeDetection(s3Key, localPath, file.filename); + + if (success) { + console.log(`Downloaded: ${localPath}`); + totalFilesDownloaded++; + } + } + } + } + } else if (fileData && fileData.filename) { + // Handle single file + const result = await downloadSingleFile( + patient, + stepKey, + field.key, + fileData, + patientDir, + stepDir, + hasDownloadedFiles + ); + + if (result.success) { + totalFilesDownloaded++; + } + hasDownloadedFiles = result.hasDownloadedFiles; + } + } + + } + } + } + + console.log(`Media export completed. Downloaded ${totalFilesDownloaded} files.`); +} + +// Function to format field values based on type +function formatField(value: any, type: FieldType): any { + if (!value) return ''; + if (type === FieldType.DATE) return new Date(value).toISOString(); + if (type === FieldType.MULTI_SELECT || type === FieldType.TAGS) { + return Array.isArray(value) ? value.join(', ') : value; + } + if (type === FieldType.MAP) { + // Format MAP data as "lat,lng" + if (value && typeof value === 'object') { + const lat = value.lat || value.latitude; + const lng = value.lng || value.longitude; + if (lat !== undefined && lng !== undefined) { + return `${lat},${lng}`; + } + } + return value; + } + return value; +} + +// Add this helper function near the top of the file +async function downloadSingleFile( + patient: any, + stepKey: string, + fieldKey: string, + fileData: any, + patientDir: string, + stepDir: string, + hasDownloadedFiles: boolean +): Promise<{ success: boolean; hasDownloadedFiles: boolean }> { + if (!fileData || !fileData.filename) { + return { success: false, hasDownloadedFiles }; + } + + const s3Key = `${patient._id}/${stepKey}/${fieldKey}/${fileData.filename}`; + const fileExists = await fileExistsInS3(s3Key); + + if (!fileExists) { + return { success: false, hasDownloadedFiles }; + } + + // Create directory only when we have actual files + if (!hasDownloadedFiles) { + if (!fs.existsSync(patientDir)) fs.mkdirSync(patientDir, { recursive: true }); + if (!fs.existsSync(stepDir)) fs.mkdirSync(stepDir, { recursive: true }); + hasDownloadedFiles = true; + } + + const sanitizedFilename = sanitizeFilename(fileData.filename); + const localPath = path.join(stepDir, sanitizedFilename); + const downloadSuccess = await downloadAndSaveFileWithTypeDetection(s3Key, localPath, fileData.filename); + + if (downloadSuccess) { + console.log(`Downloaded: ${localPath}`); + } + + return { success: downloadSuccess, hasDownloadedFiles }; +} + +// Combined export function +export async function runCombinedExport(options: ExportOptions = {}, shouldDisconnect = false) { + const { + includeDeleted = false, + includeHidden = false, + zipFilename = `3dp4me_export_${new Date().toISOString().slice(0, 19).replace(/[:-]/g, '')}.zip` + } = options; + + await initDB(); + console.log('Connected to DB'); + console.log('Export Configuration:', { includeDeleted, includeHidden, zipFilename }); + + try { + await generatePatientCSV(); + await generateStepCSVs({ includeDeleted, includeHidden }); + await exportStepMedia({ includeDeleted, includeHidden }); + + const zipPath = await createZipArchive(zipFilename); + + console.log('\nAll exports completed successfully'); + console.log(`ZIP archive: ${zipPath}`); + + return { zipPath, success: true }; + } catch (error) { + console.error('Error during export process:', error); + throw error; + } finally { + if (shouldDisconnect) { + await mongoose.disconnect(); + console.log('Disconnected from DB'); + } + } +} + +// Main function for command line usage +async function main() { + const includeDeleted = process.argv.includes('--include-deleted'); + const includeHidden = process.argv.includes('--include-hidden'); + + const zipFilenameArg = process.argv.find(arg => arg.startsWith('--zip-filename=')); + const customZipFilename = zipFilenameArg ? zipFilenameArg.split('=')[1] : undefined; + + await runCombinedExport({ + includeDeleted, + includeHidden, + zipFilename: customZipFilename + }, true); +} + + + +// Replace the detectFileTypeFromBuffer function +async function detectFileTypeFromBuffer(buffer: Buffer): Promise { + try { + const result = await fileTypeFromBuffer(buffer); + return result?.ext || null; + } catch (error) { + console.error('Error detecting file type:', error); // defaults to .png + return null; + } +} + +// Function to add the proper extension to a filename, default to .png if no type is detected +function addProperExtension(originalFilename: string, detectedType: string | null): string { + // If file already has an extension, keep it + const hasExtension = path.extname(originalFilename).length > 0; + if (hasExtension) { + return originalFilename; + } + + // If we detected a type, add the extension + if (detectedType) { + return `${originalFilename}.${detectedType}`; + } + + // Default fallback - most files are images + return `${originalFilename}.png`; +} + + +// Run if called directly +if (require.main === module) { + main().catch(err => { + console.error('Error in main function:', err); + process.exit(1); + }); +} \ No newline at end of file diff --git a/apps/backend/app.ts b/apps/backend/src/app.ts similarity index 80% rename from apps/backend/app.ts rename to apps/backend/src/app.ts index d92340e3..cd291d0d 100644 --- a/apps/backend/app.ts +++ b/apps/backend/src/app.ts @@ -1,6 +1,6 @@ import "express-async-errors" import path from "path" -import { router } from "./src/routes" +import { router } from "./routes" import log from "loglevel" import express, { NextFunction } from 'express' @@ -8,15 +8,15 @@ import fileUpload from "express-fileupload" import cors from "cors" import bodyParser from "body-parser" -import { requireAuthentication } from './src/middleware/authentication'; -import { initDB } from './src/utils/initDb'; +import { requireAuthentication } from './middleware/authentication'; +import { initDB } from './utils/initDb'; import { setResponseHeaders, configureHelment, -} from './src/middleware/responses' -import { logRequest } from './src/middleware/logging'; -import { ENV_TEST } from './src/utils/constants'; -import { errorHandler } from "./src/utils/errorHandler" +} from './middleware/responses' +import { logRequest } from './middleware/logging'; +import { ENV_TEST } from './utils/constants'; +import { errorHandler } from "./utils/errorHandler" import { Request, Response } from 'express'; const app = express(); diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts index b3d19e11..0ca3935b 100644 --- a/apps/backend/src/index.ts +++ b/apps/backend/src/index.ts @@ -1,9 +1,11 @@ +import { initDB } from './utils/initDb'; + /** * Module dependencies. */ -import app from '../app'; +import app from './app'; import http from 'http'; /** diff --git a/apps/backend/src/models/Metadata.ts b/apps/backend/src/models/Metadata.ts index 68affe3d..3ddc7198 100644 --- a/apps/backend/src/models/Metadata.ts +++ b/apps/backend/src/models/Metadata.ts @@ -143,6 +143,7 @@ const stepSchema = new mongoose.Schema({ }, }, isHidden: { type: Boolean, required: false, default: false }, + isDeleted: { type: Boolean, required: false, default: false }, }) diff --git a/apps/backend/src/routes/api/export.ts b/apps/backend/src/routes/api/export.ts new file mode 100644 index 00000000..529b733c --- /dev/null +++ b/apps/backend/src/routes/api/export.ts @@ -0,0 +1,44 @@ +import express, { Response } from 'express'; +import { AuthenticatedRequest } from '../../middleware/types'; +import { runCombinedExport } from '../../../scripts/dataextraction'; +import errorWrap from '../../utils/errorWrap'; +import path from 'path'; +import fs from 'fs'; + +export const router = express.Router(); + +router.get( + '/download', + errorWrap(async (req: AuthenticatedRequest, res: Response) => { + // Extract query parameters + const includeDeleted = req.query.includeDeleted === 'true'; + const includeHidden = req.query.includeHidden === 'true'; + + const { zipPath } = await runCombinedExport({ + includeDeleted, + includeHidden, + }); + + // Validate ZIP file exists and has content + await fs.promises.access(zipPath).catch(() => { + return res.status(500).send('ZIP file not found'); + }); + + const stats = await fs.promises.stat(zipPath); + if (stats.size === 0) { + return res.status(500).send('ZIP file is empty'); + } + + // Add a small delay to ensure file is fully written + await new Promise(resolve => setTimeout(resolve, 100)); + + res.download(zipPath, path.basename(zipPath), (err) => { + if (err) { + console.error('Error sending ZIP file:', err); + res.status(500).send('Export failed'); + } + }); + }), +); + +export default router; \ No newline at end of file diff --git a/apps/backend/src/routes/api/index.ts b/apps/backend/src/routes/api/index.ts index 2737d2bb..326e4af3 100644 --- a/apps/backend/src/routes/api/index.ts +++ b/apps/backend/src/routes/api/index.ts @@ -1,14 +1,23 @@ import express from 'express'; +import patients from './patients'; +import steps from './steps'; +import metadata from './metadata'; +import users from './users'; +import roles from './roles'; +import publicRoutes from './public'; +import exportRoutes from './export'; + export const router = express.Router(); // Put all routes here -router.use('/patients', require('./patients')); -router.use('/stages', require('./steps')); -router.use('/metadata', require('./metadata')); -router.use('/users', require('./users')); -router.use('/roles', require('./roles')); -router.use('/public', require('./public')); +router.use('/patients', patients); +router.use('/stages', steps); +router.use('/metadata', metadata); +router.use('/users', users); +router.use('/roles', roles); +router.use('/public', publicRoutes); +router.use('/export', exportRoutes); // for export button // Disable the Twilio stuff for now // router.use('/messages', require('./messages')); diff --git a/apps/backend/src/routes/api/messages.js b/apps/backend/src/routes/api/messages.js index 4e9455a3..b496732c 100644 --- a/apps/backend/src/routes/api/messages.js +++ b/apps/backend/src/routes/api/messages.js @@ -1,5 +1,5 @@ -const express = require('express'); -const { MessagingResponse } = require('twilio').twiml; +import express from 'express'; +import { MessagingResponse } from 'twilio'; const router = express.Router(); const accountSid = process.env.ACCOUNT_SID; diff --git a/apps/backend/src/routes/api/metadata.ts b/apps/backend/src/routes/api/metadata.ts index 4bbc797d..b9d83016 100644 --- a/apps/backend/src/routes/api/metadata.ts +++ b/apps/backend/src/routes/api/metadata.ts @@ -1,8 +1,7 @@ -import { Router, Request, Response } from 'express'; - -import mongoose = require('mongoose'); -import log = require('loglevel'); +import { Router, Response } from 'express'; +import mongoose from 'mongoose'; +import log from 'loglevel'; import { requireAdmin } from '../../middleware/authentication'; import { sendResponse } from '../../utils/response'; import { @@ -112,4 +111,4 @@ router.delete( }), ); -module.exports = router; +export default router; \ No newline at end of file diff --git a/apps/backend/src/routes/api/patients.ts b/apps/backend/src/routes/api/patients.ts index d4ae5984..cdac81fc 100644 --- a/apps/backend/src/routes/api/patients.ts +++ b/apps/backend/src/routes/api/patients.ts @@ -462,4 +462,4 @@ const updatePatientStepData = async (patientId: string, StepModel: typeof mongoo return patientStepData.save(); }; -module.exports = router; +export default router; \ No newline at end of file diff --git a/apps/backend/src/routes/api/public.ts b/apps/backend/src/routes/api/public.ts index 6793bb2f..267ee99c 100644 --- a/apps/backend/src/routes/api/public.ts +++ b/apps/backend/src/routes/api/public.ts @@ -55,4 +55,4 @@ const fileFromRequest = (req: AuthenticatedRequest): Nullish => { + try { + await downloadFile(s3Key); + return true; + } catch (error) { + return false; + } +}; + + +// Function to download and save a file from Step3 with type detection (uses package to detect type, defaults to .png if no type is detected) + +// Step 1: Download file from S3 to local temporary path using streaming +export const downloadFileToLocal = async ( + s3Key: string, + localPath: string +): Promise => { + const s3Stream = await downloadFile(s3Key); + const writeStream = fs.createWriteStream(localPath); + + return new Promise((resolve, reject) => { + s3Stream.pipe(writeStream) + .on('finish', () => { + console.log(`Downloaded to temporary location: ${localPath}`); + resolve(); + }) + .on('error', (error) => { + reject(error); + }); + }); +}; + +// Step 2: Determine file type from saved file on disk +const detectFileTypeFromFile = async (filePath: string): Promise => { + try { + const buffer = fs.readFileSync(filePath, { encoding: null }); // Read first 4KB for type detection + const fileTypeResult = await fileTypeFromBuffer(buffer); + return fileTypeResult?.ext || null; + } catch (error) { + console.error('Error detecting file type:', error); + return null; + } +}; + +// Step 3: Rename file with proper extension if needed +const renameFileWithProperExtension = async ( + currentPath: string, + originalFilename: string, + detectedType: string | null +): Promise => { + const properFilename = addProperExtension(originalFilename, detectedType); + const properLocalPath = path.join(path.dirname(currentPath), sanitizeFilename(properFilename)); + + // Only rename if the path is different + if (path.resolve(properLocalPath) !== path.resolve(currentPath)) { + fs.renameSync(currentPath, properLocalPath); + console.log(`Renamed file to: ${properLocalPath}`); + } + + return properLocalPath; +}; + +// Main function that orchestrates the three steps +export const downloadAndSaveFileWithTypeDetection = async ( + s3Key: string, + localPath: string, + originalFilename: string +): Promise => { + try { + // Create directory if it doesn't exist + const dir = path.dirname(localPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + // Step 1: Download to temporary location + const tempPath = `${localPath}.tmp`; + await downloadFileToLocal(s3Key, tempPath); + + // Step 2: Detect file type from saved file + const detectedType = await detectFileTypeFromFile(tempPath); + + // Step 3: Rename with proper extension + const finalPath = await renameFileWithProperExtension(tempPath, originalFilename, detectedType); + + if (detectedType) { + console.log(`Downloaded with detected type '${detectedType}': ${finalPath}`); + } else { + console.log(`No type detected, kept original name: ${finalPath}`); + } + + return true; + } catch (error) { + console.error('Error in downloadAndSaveFileWithTypeDetection:', error); + return false; + } +}; + +export const sanitizeFilename = (filename: string): string => { + const ext = path.extname(filename); + const name = path.basename(filename, ext); + const sanitizedName = name.replace(/[^a-z0-9.-]/gi, '_').toLowerCase(); + return sanitizedName + ext; +}; + +const detectFileTypeFromBuffer = async (buffer: Buffer): Promise => { + try { + const result = await fileTypeFromBuffer(buffer); + return result?.ext || null; + } catch (error) { + console.error('Error detecting file type:', error); // defaults to .png + return null; + } +}; + +// Function to add the proper extension to a filename, default to .png if no type is detected +const addProperExtension = (originalFilename: string, detectedType: string | null): string => { + // If file already has an extension, keep it + const hasExtension = path.extname(originalFilename).length > 0; + if (hasExtension) { + return originalFilename; + } + + // If we detected a type, add the extension + if (detectedType) { + return `${originalFilename}.${detectedType}`; + } + + // Default fallback - most files are images + return `${originalFilename}.png`; +}; \ No newline at end of file diff --git a/apps/backend/src/utils/initDb.ts b/apps/backend/src/utils/initDb.ts index 07a7c82f..8e55add6 100644 --- a/apps/backend/src/utils/initDb.ts +++ b/apps/backend/src/utils/initDb.ts @@ -2,6 +2,7 @@ import { Field, FieldType, PatientTagsField, + PatientTagSyria, ReservedStep, RootStep, RootStepFieldKeys, @@ -16,7 +17,6 @@ import encrypt from 'mongoose-encryption' import { StepModel } from '../models/Metadata' import { fileSchema } from '../schemas/fileSchema' import { signatureSchema } from '../schemas/signatureSchema' -import { PatientTagSyria } from '@3dp4me/types'; /** * Initalizes and connects to the DB. Should be called at app startup. @@ -47,17 +47,17 @@ const clearModels = async () => { // Migrations for root step const initReservedSteps = async () => { - log.info("Initializing the reserved step") + log.info('Initializing the reserved step') const rootStep = await StepModel.findOne({ key: ReservedStep.Root }).lean() if (!rootStep) { - log.info("Creating the reserved step") + log.info('Creating the reserved step') return StepModel.create(RootStep) } // Older version missing the tag field const tagField = rootStep.fields.find((f) => f.key === RootStepFieldKeys.Tags) if (!tagField) { - log.info("Tags is missing from reserved step, adding it") + log.info('Tags is missing from reserved step, adding it') return StepModel.updateOne( { key: ReservedStep.Root }, { $push: { fields: PatientTagsField } } @@ -67,17 +67,17 @@ const initReservedSteps = async () => { // Older version missing the syria option const syriaOption = tagField.options.find((o) => o.Question.EN === PatientTagSyria.Question.EN) if (!syriaOption) { - log.info("Syria is missing from tag options, adding it") + log.info('Syria is missing from tag options, adding it') return StepModel.updateOne( - { + { key: ReservedStep.Root, - "fields.key": RootStepFieldKeys.Tags + 'fields.key': RootStepFieldKeys.Tags, }, - { $push: { "fields.$.options": PatientTagSyria } } + { $push: { 'fields.$.options': PatientTagSyria } } ) } - log.info("Reserved step is up to date") + log.info('Reserved step is up to date') return null } diff --git a/apps/backend/tsconfig.json b/apps/backend/tsconfig.json index d422b029..88d8e945 100644 --- a/apps/backend/tsconfig.json +++ b/apps/backend/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "module": "commonjs", + "module": "ESNext", "esModuleInterop": true, "allowSyntheticDefaultImports": true, "target": "ESNext", diff --git a/apps/backend/webpack.config.js b/apps/backend/webpack.config.js deleted file mode 100644 index 9b089e4a..00000000 --- a/apps/backend/webpack.config.js +++ /dev/null @@ -1,27 +0,0 @@ -module.exports = { - entry: "./src/index.ts", - target: 'node', - module: { - rules: [ - { - test: /\.tsx?$/, - use: 'ts-loader', - exclude: /node_modules/, - }, - { - test: /\.node$/, - use: 'node-loader', - }, - ], - }, - node: { - __dirname: false, - }, - resolve: { - extensions: ['.tsx', '.ts', '.js', '.node'], - }, - output: { - filename: 'bundle.js', - path: __dirname + '/build', - }, -}; diff --git a/apps/backend/webpack.dev.js b/apps/backend/webpack.dev.js new file mode 100644 index 00000000..954664c4 --- /dev/null +++ b/apps/backend/webpack.dev.js @@ -0,0 +1,43 @@ +// webpack.config.js +import { ExpirationStatus } from '@aws-sdk/client-s3'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export default { + entry: "./src/index.ts", + mode: 'development', + target: 'node', + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + { + test: /\.node$/, + use: 'node-loader', + }, + ], + }, + node: { + __dirname: true, + }, + resolve: { + extensions: ['.tsx', '.ts', '.js', '.node'], + }, + output: { + path: path.resolve(__dirname, 'build'), + filename: 'bundle.js', + module: true, + }, + experiments: { + outputModule: true, + }, + externals: { + sharp: 'module sharp' + } +}; diff --git a/apps/backend/webpack.prod.js b/apps/backend/webpack.prod.js new file mode 100644 index 00000000..5aebd0a8 --- /dev/null +++ b/apps/backend/webpack.prod.js @@ -0,0 +1,43 @@ +// webpack.config.js +import { ExpirationStatus } from '@aws-sdk/client-s3'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export default { + entry: "./src/index.ts", + mode: 'production', + target: 'node', + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + { + test: /\.node$/, + use: 'node-loader', + }, + ], + }, + node: { + __dirname: true, + }, + resolve: { + extensions: ['.tsx', '.ts', '.js', '.node'], + }, + output: { + path: path.resolve(__dirname, 'build'), + filename: 'bundle.js', + module: true, + }, + experiments: { + outputModule: true, + }, + externals: { + sharp: 'module sharp' + } +}; diff --git a/apps/frontend/src/api/api.ts b/apps/frontend/src/api/api.ts index a394f431..f21ff693 100644 --- a/apps/frontend/src/api/api.ts +++ b/apps/frontend/src/api/api.ts @@ -321,3 +321,12 @@ export const getSelf = async (): Promise> => { return res.data } + +export const triggerExportDownload = async (includeDeleted: boolean, includeHidden: boolean) => { + const res = await instance.get('/export/download', { + params: { includeDeleted, includeHidden }, + responseType: 'blob', + }) + + fileDownload(res.data, '3dp4me_export.zip') +} diff --git a/apps/frontend/src/components/ExportButton/ExportButton.tsx b/apps/frontend/src/components/ExportButton/ExportButton.tsx new file mode 100644 index 00000000..25ecc8a0 --- /dev/null +++ b/apps/frontend/src/components/ExportButton/ExportButton.tsx @@ -0,0 +1,105 @@ +import React, { useState } from 'react' +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Checkbox, + FormControlLabel, + CircularProgress, +} from '@mui/material' +import { triggerExportDownload } from '../../api/api' + +interface ExportButtonProps { + buttonText?: string + onExportComplete?: () => void + onExportError?: (error: Error) => void +} + +const ExportButton: React.FC = ({ + buttonText = 'Export Data', + onExportComplete, + onExportError +}) => { + const [open, setOpen] = useState(false) + const [includeDeleted, setIncludeDeleted] = useState(false) + const [includeHidden, setIncludeHidden] = useState(false) + const [loading, setLoading] = useState(false) + + const handleDownload = async () => { + setLoading(true) + try { + await triggerExportDownload(includeDeleted, includeHidden) + setOpen(false) + onExportComplete?.() + } catch (error) { + console.error('Export failed:', error) + onExportError?.(error as Error) + } finally { + setLoading(false) + } + } + + return ( + <> + + + setOpen(false)} + aria-labelledby="export-dialog-title" + > + Export Options + + setIncludeDeleted(e.target.checked)} + disabled={loading} + /> + } + label="Include Deleted Steps" + /> + setIncludeHidden(e.target.checked)} + disabled={loading} + /> + } + label="Include Hidden Fields" + /> + + + + + + + + ) +} + +export default ExportButton diff --git a/apps/frontend/src/components/Navbar/Navbar.tsx b/apps/frontend/src/components/Navbar/Navbar.tsx index c0030c56..b7f249f9 100644 --- a/apps/frontend/src/components/Navbar/Navbar.tsx +++ b/apps/frontend/src/components/Navbar/Navbar.tsx @@ -11,6 +11,7 @@ import { useTranslations } from '../../hooks/useTranslations' import { Context } from '../../store/Store' import { Routes } from '../../utils/constants' import AccountDropdown from '../AccountDropdown/AccountDropdown' +import ExportButton from '../ExportButton/ExportButton' export interface NavbarProps { username: string @@ -141,6 +142,12 @@ const Navbar = ({ username, userEmail }: NavbarProps) => { {renderLinks()} + alert('Export successful!')} + onExportError={(err) => alert(`Export failed: ${err.message}`)} + /> + =21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.3': + resolution: {integrity: sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.0': + resolution: {integrity: sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.0': + resolution: {integrity: sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.0': + resolution: {integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.0': + resolution: {integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.0': + resolution: {integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.0': + resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.0': + resolution: {integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.0': + resolution: {integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.0': + resolution: {integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.3': + resolution: {integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.3': + resolution: {integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.3': + resolution: {integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.3': + resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.3': + resolution: {integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.3': + resolution: {integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.3': + resolution: {integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.3': + resolution: {integrity: sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.3': + resolution: {integrity: sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.3': + resolution: {integrity: sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.3': + resolution: {integrity: sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -3014,6 +3158,13 @@ packages: peerDependencies: react: ^18 || ^19 + '@tokenizer/inflate@0.2.7': + resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@tootallnate/once@1.1.2': resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} engines: {node: '>= 6'} @@ -3034,6 +3185,9 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@types/archiver@6.0.3': + resolution: {integrity: sha512-a6wUll6k3zX6qs5KlxIggs1P1JcYJaTCx2gnlr+f0S1yd2DoaEwoIK10HmBaLnZwWneBz+JBm0dwcZu0zECBcQ==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -3228,6 +3382,9 @@ packages: '@types/react@18.3.18': resolution: {integrity: sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==} + '@types/readdir-glob@1.1.5': + resolution: {integrity: sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==} + '@types/resolve@1.17.1': resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} @@ -3643,10 +3800,18 @@ packages: resolution: {integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==} engines: {node: '>= 10'} + archiver-utils@5.0.2: + resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} + engines: {node: '>= 14'} + archiver@5.3.2: resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==} engines: {node: '>= 10'} + archiver@7.0.1: + resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} + engines: {node: '>= 14'} + arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -3887,28 +4052,6 @@ packages: bare-events@2.5.4: resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==} - bare-fs@4.0.1: - resolution: {integrity: sha512-ilQs4fm/l9eMfWY2dY0WCIUplSUp7U0CT1vrqMg1MUdeZl4fypu5UP0XcDBK5WBQPJAKP1b7XEodISmekH/CEg==} - engines: {bare: '>=1.7.0'} - - bare-os@3.4.0: - resolution: {integrity: sha512-9Ous7UlnKbe3fMi7Y+qh0DwAup6A1JkYgPnjvMDNOlmnxNRQvQ/7Nst+OnUQKzk0iAT0m9BisbDVp9gCv8+ETA==} - engines: {bare: '>=1.6.0'} - - bare-path@3.0.0: - resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} - - bare-stream@2.6.4: - resolution: {integrity: sha512-G6i3A74FjNq4nVrrSTUz5h3vgXzBJnjmWAVlBWaZETkgu+LgKd7AiyOml3EDJY1AHlIbBHKDXE+TUT53Ff8OaA==} - peerDependencies: - bare-buffer: '*' - bare-events: '*' - peerDependenciesMeta: - bare-buffer: - optional: true - bare-events: - optional: true - base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -4017,6 +4160,7 @@ packages: bson@6.10.1: resolution: {integrity: sha512-P92xmHDQjSKPLHqFxefqMxASNq/aWJMEZugpCjf+AF/pgcUpMMQCg7t7+ewko0/u8AapvF3luf/FoehddEK+sA==} engines: {node: '>=16.20.1'} + deprecated: a critical bug affecting only useBigInt64=true deserialization usage is fixed in bson@6.10.3 btoa@1.2.1: resolution: {integrity: sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==} @@ -4032,6 +4176,10 @@ packages: buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-crc32@1.0.0: + resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} + engines: {node: '>=8.0.0'} + buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} @@ -4051,6 +4199,9 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + buffers@0.1.1: resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==} engines: {node: '>=0.2.0'} @@ -4175,9 +4326,6 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} - chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - chrome-launcher@0.15.2: resolution: {integrity: sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==} engines: {node: '>=12.13.0'} @@ -4325,6 +4473,10 @@ packages: resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==} engines: {node: '>= 10'} + compress-commons@6.0.2: + resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} + engines: {node: '>= 14'} + compressible@2.0.18: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} @@ -4436,6 +4588,10 @@ packages: resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==} engines: {node: '>= 10'} + crc32-stream@6.0.0: + resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} + engines: {node: '>= 14'} + create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -4603,6 +4759,9 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csv-writer@1.6.0: + resolution: {integrity: sha512-NOx7YDFWEsM/fTRAJjRpPp8t+MKRVvniAg9wQlUKx20MFrPs73WLJhFf5iteqrxNYnsy924K3Iroh3yNHeYd2g==} + cwebp-bin@7.0.1: resolution: {integrity: sha512-Ko5ADY74/dbfd8xG0+f+MUP9UKjCe1TG4ehpW0E5y4YlPdwDJlGrSzSR4/Yonxpm9QmZE1RratkIxFlKeyo3FA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -4677,10 +4836,6 @@ packages: resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} engines: {node: '>=4'} - decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - decompress-tar@4.1.1: resolution: {integrity: sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==} engines: {node: '>=4'} @@ -4704,10 +4859,6 @@ packages: dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} - deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -4766,8 +4917,8 @@ packages: engines: {node: '>=0.10'} hasBin: true - detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} detect-newline@3.1.0: @@ -5355,10 +5506,6 @@ packages: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} - expand-template@2.0.3: - resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} - engines: {node: '>=6'} - expect@27.5.1: resolution: {integrity: sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -5463,6 +5610,9 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -5481,6 +5631,10 @@ packages: resolution: {integrity: sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==} engines: {node: '>=8'} + file-type@21.0.0: + resolution: {integrity: sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==} + engines: {node: '>=20'} + file-type@3.9.0: resolution: {integrity: sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==} engines: {node: '>=0.10.0'} @@ -5626,6 +5780,7 @@ packages: formidable@2.1.2: resolution: {integrity: sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==} + deprecated: 'ACTION REQUIRED: SWITCH TO v3 - v1 and v2 are VULNERABLE! v1 is DEPRECATED FOR OVER 2 YEARS! Use formidable@latest or try formidable-mini for fresh projects' forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} @@ -5759,9 +5914,6 @@ packages: git-hooks-list@1.0.3: resolution: {integrity: sha512-Y7wLWcrLUXwk2noSka166byGCvhMtDRpgHdzCno1UQv/n/Hegp++a2xBWJL1lJarnKD3SWaljD+0z1ztqxuKyQ==} - github-from-package@0.0.0: - resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -5829,6 +5981,7 @@ packages: gm@1.25.0: resolution: {integrity: sha512-4kKdWXTtgQ4biIo7hZA396HT062nDVVHPjQcurNZ3o/voYN+o5FUC5kOwuORbpExp3XbTJ3SU7iRipiIhQtovw==} engines: {node: '>=14'} + deprecated: The gm module has been sunset. Please migrate to an alternative. https://github.com/aheckmann/gm?tab=readme-ov-file#2025-02-24-this-project-is-not-maintained gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} @@ -5851,6 +6004,7 @@ packages: graphql@14.0.0: resolution: {integrity: sha512-HGVcnO6B25YZcSt6ZsH6/N+XkYuPA7yMqJmlJ4JWxWlS4Tr8SHI56R1Ocs8Eor7V7joEZPRXPDH8RRdll1w44Q==} engines: {node: 6.x || 8.x || >= 10.x} + deprecated: 'No longer supported; please update to a newer version. Details: https://github.com/graphql/graphql-js#version-support' gzip-size@6.0.0: resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} @@ -7269,10 +7423,6 @@ packages: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} engines: {node: '>=4'} - mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - mini-css-extract-plugin@2.9.2: resolution: {integrity: sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==} engines: {node: '>= 12.13.0'} @@ -7312,9 +7462,6 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -7412,9 +7559,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - napi-build-utils@2.0.0: - resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} - natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} @@ -7451,16 +7595,9 @@ packages: node-2fa@2.0.3: resolution: {integrity: sha512-PQldrOhjuoZyoydMvMSctllPN1ZPZ1/NwkEcgYwY9faVqE/OymxR+3awPpbWZxm6acLKqvmNqQmdqTsqYyflFw==} - node-abi@3.73.0: - resolution: {integrity: sha512-z8iYzQGBu35ZkTQ9mtR8RqugJZ9RCLn8fv3d7LsgDBzOijGQP3RdKTX4LA7LXw03ZhU5z0l4xfhIMgSES31+cg==} - engines: {node: '>=10'} - node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} - node-addon-api@6.1.0: - resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} - node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} @@ -8399,11 +8536,6 @@ packages: resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} engines: {node: ^10 || ^12 || >=14} - prebuild-install@7.1.3: - resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} - engines: {node: '>=10'} - hasBin: true - prelude-ls@1.1.2: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} engines: {node: '>= 0.8.0'} @@ -8455,6 +8587,10 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + promise-polyfill@6.1.0: resolution: {integrity: sha512-g0LWaH0gFsxovsU7R5LrrhHhWAWiHRnh1GPrhXnPgYsDkIqjRYUYSZEsej/wtleDrz5xVSIDbeKfidztp2XHFQ==} @@ -8562,10 +8698,6 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} - rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true - react-app-env@1.2.3: resolution: {integrity: sha512-GmBiEvnHjJuSYBeEeSw/Kqp9r+tQDADnVe87z6yruJ5vohhH5x78WwnPnv7Lo/ygUDqddb7S0W+axOE1xvrbzQ==} hasBin: true @@ -8775,6 +8907,10 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + readdir-glob@1.1.3: resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} @@ -9141,6 +9277,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + send@0.19.0: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} @@ -9199,9 +9340,9 @@ packages: shallowequal@1.1.0: resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} - sharp@0.32.6: - resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==} - engines: {node: '>=14.15.0'} + sharp@0.34.3: + resolution: {integrity: sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} @@ -9252,12 +9393,6 @@ packages: signature_pad@2.3.2: resolution: {integrity: sha512-peYXLxOsIY6MES2TrRLDiNg2T++8gGbpP2yaC+6Ohtxr+a2dzoaqWosWDY9sWqTAAk6E/TyQO+LJw9zQwyu5kA==} - simple-concat@1.0.1: - resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - - simple-get@4.0.1: - resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} @@ -9349,6 +9484,7 @@ packages: source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} + deprecated: The work that was done in this beta branch won't be included in future versions sourcemap-codec@1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} @@ -9528,10 +9664,6 @@ packages: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} - strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -9543,6 +9675,10 @@ packages: strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + strtok3@10.3.4: + resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} + engines: {node: '>=18'} + style-inject@0.3.0: resolution: {integrity: sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==} @@ -9643,12 +9779,6 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} - tar-fs@2.1.2: - resolution: {integrity: sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==} - - tar-fs@3.0.8: - resolution: {integrity: sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==} - tar-stream@1.6.2: resolution: {integrity: sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==} engines: {node: '>= 0.8.0'} @@ -9781,6 +9911,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + token-types@6.1.1: + resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} + engines: {node: '>=14.16'} + tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} @@ -9976,6 +10110,10 @@ packages: engines: {node: '>=14.17'} hasBin: true + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + engines: {node: '>=18'} + ulid@2.3.0: resolution: {integrity: sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==} hasBin: true @@ -10556,6 +10694,10 @@ packages: resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} engines: {node: '>= 10'} + zip-stream@6.0.1: + resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} + engines: {node: '>= 14'} + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -13161,6 +13303,8 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@borewit/text-codec@0.1.1': {} + '@changesets/apply-release-plan@7.0.8': dependencies: '@changesets/config': 3.0.5 @@ -13394,6 +13538,11 @@ snapshots: effect: 3.5.7 fast-check: 3.20.0 + '@emnapi/runtime@1.5.0': + dependencies: + tslib: 2.8.1 + optional: true + '@emotion/babel-plugin@11.13.5': dependencies: '@babel/helper-module-imports': 7.25.9 @@ -13544,6 +13693,92 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} + '@img/sharp-darwin-arm64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.0 + optional: true + + '@img/sharp-darwin-x64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.0 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.0': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.0': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.0': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.0': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.0': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.0': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.0': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.0': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.0': + optional: true + + '@img/sharp-linux-arm64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.0 + optional: true + + '@img/sharp-linux-arm@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.0 + optional: true + + '@img/sharp-linux-ppc64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.0 + optional: true + + '@img/sharp-linux-s390x@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.0 + optional: true + + '@img/sharp-linux-x64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.0 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.0 + optional: true + + '@img/sharp-wasm32@0.34.3': + dependencies: + '@emnapi/runtime': 1.5.0 + optional: true + + '@img/sharp-win32-arm64@0.34.3': + optional: true + + '@img/sharp-win32-ia32@0.34.3': + optional: true + + '@img/sharp-win32-x64@0.34.3': + optional: true + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -14802,6 +15037,16 @@ snapshots: '@tanstack/query-core': 5.65.0 react: 18.3.1 + '@tokenizer/inflate@0.2.7': + dependencies: + debug: 4.4.0 + fflate: 0.8.2 + token-types: 6.1.1 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + '@tootallnate/once@1.1.2': {} '@trysound/sax@0.2.0': {} @@ -14814,6 +15059,10 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@types/archiver@6.0.3': + dependencies: + '@types/readdir-glob': 1.1.5 + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.26.7 @@ -15055,6 +15304,10 @@ snapshots: '@types/prop-types': 15.7.14 csstype: 3.1.3 + '@types/readdir-glob@1.1.5': + dependencies: + '@types/node': 20.17.16 + '@types/resolve@1.17.1': dependencies: '@types/node': 20.17.16 @@ -15386,12 +15639,12 @@ snapshots: '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.97.1)': dependencies: webpack: 5.97.1(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-dev-server@4.15.2)(webpack@5.97.1) + webpack-cli: 5.1.4(webpack@5.97.1) '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.97.1)': dependencies: webpack: 5.97.1(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-dev-server@4.15.2)(webpack@5.97.1) + webpack-cli: 5.1.4(webpack@5.97.1) '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack-dev-server@4.15.2)(webpack@5.97.1)': dependencies: @@ -15560,6 +15813,16 @@ snapshots: normalize-path: 3.0.0 readable-stream: 3.6.2 + archiver-utils@5.0.2: + dependencies: + glob: 10.4.5 + graceful-fs: 4.2.11 + is-stream: 2.0.1 + lazystream: 1.0.1 + lodash: 4.17.21 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + archiver@5.3.2: dependencies: archiver-utils: 2.1.0 @@ -15570,6 +15833,16 @@ snapshots: tar-stream: 2.2.0 zip-stream: 4.1.1 + archiver@7.0.1: + dependencies: + archiver-utils: 5.0.2 + async: 3.2.6 + buffer-crc32: 1.0.0 + readable-stream: 4.7.0 + readdir-glob: 1.1.3 + tar-stream: 3.1.7 + zip-stream: 6.0.1 + arg@4.1.3: {} arg@5.0.2: {} @@ -15941,30 +16214,6 @@ snapshots: bare-events@2.5.4: optional: true - bare-fs@4.0.1: - dependencies: - bare-events: 2.5.4 - bare-path: 3.0.0 - bare-stream: 2.6.4(bare-events@2.5.4) - transitivePeerDependencies: - - bare-buffer - optional: true - - bare-os@3.4.0: - optional: true - - bare-path@3.0.0: - dependencies: - bare-os: 3.4.0 - optional: true - - bare-stream@2.6.4(bare-events@2.5.4): - dependencies: - streamx: 2.21.1 - optionalDependencies: - bare-events: 2.5.4 - optional: true - base64-js@1.5.1: {} batch@0.6.1: {} @@ -16124,6 +16373,8 @@ snapshots: buffer-crc32@0.2.13: {} + buffer-crc32@1.0.0: {} + buffer-equal-constant-time@1.0.1: {} buffer-fill@1.0.0: @@ -16144,6 +16395,11 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + buffers@0.1.1: {} builtin-modules@3.3.0: {} @@ -16273,8 +16529,6 @@ snapshots: dependencies: readdirp: 4.1.1 - chownr@1.1.4: {} - chrome-launcher@0.15.2: dependencies: '@types/node': 20.17.16 @@ -16417,6 +16671,14 @@ snapshots: normalize-path: 3.0.0 readable-stream: 3.6.2 + compress-commons@6.0.2: + dependencies: + crc-32: 1.2.2 + crc32-stream: 6.0.0 + is-stream: 2.0.1 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + compressible@2.0.18: dependencies: mime-db: 1.53.0 @@ -16542,6 +16804,11 @@ snapshots: crc-32: 1.2.2 readable-stream: 3.6.2 + crc32-stream@6.0.0: + dependencies: + crc-32: 1.2.2 + readable-stream: 4.7.0 + create-require@1.1.1: {} cross-env@3.2.4: @@ -16725,6 +16992,8 @@ snapshots: csstype@3.1.3: {} + csv-writer@1.6.0: {} + cwebp-bin@7.0.1: dependencies: bin-build: 3.0.0 @@ -16788,10 +17057,6 @@ snapshots: mimic-response: 1.0.1 optional: true - decompress-response@6.0.0: - dependencies: - mimic-response: 3.1.0 - decompress-tar@4.1.1: dependencies: file-type: 5.2.0 @@ -16837,8 +17102,6 @@ snapshots: dedent@0.7.0: {} - deep-extend@0.6.0: {} - deep-is@0.1.4: {} deepmerge@4.3.1: {} @@ -16882,7 +17145,7 @@ snapshots: detect-libc@1.0.3: optional: true - detect-libc@2.0.3: {} + detect-libc@2.0.4: {} detect-newline@3.1.0: {} @@ -17683,8 +17946,6 @@ snapshots: exit@0.1.2: {} - expand-template@2.0.3: {} - expect@27.5.1: dependencies: '@jest/types': 27.5.1 @@ -17829,6 +18090,8 @@ snapshots: dependencies: pend: 1.2.0 + fflate@0.8.2: {} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -17844,6 +18107,15 @@ snapshots: file-type@12.4.2: {} + file-type@21.0.0: + dependencies: + '@tokenizer/inflate': 0.2.7 + strtok3: 10.3.4 + token-types: 6.1.1 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + file-type@3.9.0: optional: true @@ -18159,8 +18431,6 @@ snapshots: git-hooks-list@1.0.3: {} - github-from-package@0.0.0: {} - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -19495,10 +19765,10 @@ snapshots: jmespath@0.16.0: {} - join-images@1.1.5(sharp@0.32.6): + join-images@1.1.5(sharp@0.34.3): dependencies: is-plain-obj: 3.0.0 - sharp: 0.32.6 + sharp: 0.34.3 tslib: 2.8.1 js-cookie@2.2.1: {} @@ -20162,8 +20432,6 @@ snapshots: mimic-response@1.0.1: optional: true - mimic-response@3.1.0: {} - mini-css-extract-plugin@2.9.2(webpack@5.97.1): dependencies: schema-utils: 4.3.0 @@ -20198,8 +20466,6 @@ snapshots: minipass@7.1.2: {} - mkdirp-classic@0.5.3: {} - mkdirp@0.5.6: dependencies: minimist: 1.2.8 @@ -20333,8 +20599,6 @@ snapshots: nanoid@3.3.8: {} - napi-build-utils@2.0.0: {} - natural-compare-lite@1.4.0: {} natural-compare@1.4.0: {} @@ -20383,14 +20647,8 @@ snapshots: thirty-two: 1.0.2 tslib: 2.8.1 - node-abi@3.73.0: - dependencies: - semver: 7.6.3 - node-abort-controller@3.1.1: {} - node-addon-api@6.1.0: {} - node-addon-api@7.1.1: optional: true @@ -21324,21 +21582,6 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - prebuild-install@7.1.3: - dependencies: - detect-libc: 2.0.3 - expand-template: 2.0.3 - github-from-package: 0.0.0 - minimist: 1.2.8 - mkdirp-classic: 0.5.3 - napi-build-utils: 2.0.0 - node-abi: 3.73.0 - pump: 3.0.2 - rc: 1.2.8 - simple-get: 4.0.1 - tar-fs: 2.1.2 - tunnel-agent: 0.6.0 - prelude-ls@1.1.2: {} prelude-ls@1.2.1: {} @@ -21385,6 +21628,8 @@ snapshots: process-nextick-args@2.0.1: {} + process@0.11.10: {} + promise-polyfill@6.1.0: {} promise.series@0.2.0: {} @@ -21424,6 +21669,7 @@ snapshots: dependencies: end-of-stream: 1.4.4 once: 1.4.0 + optional: true punycode@1.3.2: {} @@ -21484,13 +21730,6 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - rc@1.2.8: - dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.8 - strip-json-comments: 2.0.1 - react-app-env@1.2.3: dependencies: cross-env: 3.2.4 @@ -21881,6 +22120,14 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + readdir-glob@1.1.3: dependencies: minimatch: 5.1.6 @@ -22266,6 +22513,8 @@ snapshots: semver@7.6.3: {} + semver@7.7.2: {} + send@0.19.0: dependencies: debug: 2.6.9 @@ -22355,18 +22604,34 @@ snapshots: shallowequal@1.1.0: {} - sharp@0.32.6: + sharp@0.34.3: dependencies: color: 4.2.3 - detect-libc: 2.0.3 - node-addon-api: 6.1.0 - prebuild-install: 7.1.3 - semver: 7.6.3 - simple-get: 4.0.1 - tar-fs: 3.0.8 - tunnel-agent: 0.6.0 - transitivePeerDependencies: - - bare-buffer + detect-libc: 2.0.4 + semver: 7.7.2 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.3 + '@img/sharp-darwin-x64': 0.34.3 + '@img/sharp-libvips-darwin-arm64': 1.2.0 + '@img/sharp-libvips-darwin-x64': 1.2.0 + '@img/sharp-libvips-linux-arm': 1.2.0 + '@img/sharp-libvips-linux-arm64': 1.2.0 + '@img/sharp-libvips-linux-ppc64': 1.2.0 + '@img/sharp-libvips-linux-s390x': 1.2.0 + '@img/sharp-libvips-linux-x64': 1.2.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 + '@img/sharp-libvips-linuxmusl-x64': 1.2.0 + '@img/sharp-linux-arm': 0.34.3 + '@img/sharp-linux-arm64': 0.34.3 + '@img/sharp-linux-ppc64': 0.34.3 + '@img/sharp-linux-s390x': 0.34.3 + '@img/sharp-linux-x64': 0.34.3 + '@img/sharp-linuxmusl-arm64': 0.34.3 + '@img/sharp-linuxmusl-x64': 0.34.3 + '@img/sharp-wasm32': 0.34.3 + '@img/sharp-win32-arm64': 0.34.3 + '@img/sharp-win32-ia32': 0.34.3 + '@img/sharp-win32-x64': 0.34.3 shebang-command@1.2.0: dependencies: @@ -22418,14 +22683,6 @@ snapshots: signature_pad@2.3.2: {} - simple-concat@1.0.1: {} - - simple-get@4.0.1: - dependencies: - decompress-response: 6.0.0 - once: 1.4.0 - simple-concat: 1.0.1 - simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 @@ -22756,8 +23013,6 @@ snapshots: strip-final-newline@2.0.0: {} - strip-json-comments@2.0.1: {} - strip-json-comments@3.1.1: {} strip-outer@1.0.1: @@ -22767,6 +23022,10 @@ snapshots: strnum@1.0.5: {} + strtok3@10.3.4: + dependencies: + '@tokenizer/token': 0.3.0 + style-inject@0.3.0: {} style-loader@3.3.4(webpack@5.97.1): @@ -22936,23 +23195,6 @@ snapshots: tapable@2.2.1: {} - tar-fs@2.1.2: - dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.2 - tar-stream: 2.2.0 - - tar-fs@3.0.8: - dependencies: - pump: 3.0.2 - tar-stream: 3.1.7 - optionalDependencies: - bare-fs: 4.0.1 - bare-path: 3.0.0 - transitivePeerDependencies: - - bare-buffer - tar-stream@1.6.2: dependencies: bl: 1.2.3 @@ -23086,6 +23328,12 @@ snapshots: toidentifier@1.0.1: {} + token-types@6.1.1: + dependencies: + '@borewit/text-codec': 0.1.1 + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + tough-cookie@4.1.4: dependencies: psl: 1.15.0 @@ -23181,6 +23429,7 @@ snapshots: tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 + optional: true turbo-darwin-64@1.13.4: optional: true @@ -23304,6 +23553,8 @@ snapshots: typescript@5.7.3: {} + uint8array-extras@1.5.0: {} + ulid@2.3.0: {} unbox-primitive@1.1.0: @@ -23652,7 +23903,7 @@ snapshots: watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: - webpack-cli: 5.1.4(webpack-dev-server@4.15.2)(webpack@5.97.1) + webpack-cli: 5.1.4(webpack@5.97.1) transitivePeerDependencies: - '@swc/core' - esbuild @@ -24023,3 +24274,9 @@ snapshots: archiver-utils: 3.0.4 compress-commons: 4.1.2 readable-stream: 3.6.2 + + zip-stream@6.0.1: + dependencies: + archiver-utils: 5.0.2 + compress-commons: 6.0.2 + readable-stream: 4.7.0