From edb69b0b466612cb1d9162107f94d70c2f47a62d Mon Sep 17 00:00:00 2001 From: Mac Deluca <99926243+MacQSL@users.noreply.github.com> Date: Mon, 16 Sep 2024 10:48:18 -0700 Subject: [PATCH 1/3] feat: created migration to add NOT NULL constraint on all uuid colummns. (#1363) --- ...911000000_add_not_null_for_uuid_columns.ts | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 database/src/migrations/20240911000000_add_not_null_for_uuid_columns.ts diff --git a/database/src/migrations/20240911000000_add_not_null_for_uuid_columns.ts b/database/src/migrations/20240911000000_add_not_null_for_uuid_columns.ts new file mode 100644 index 0000000000..b650269ced --- /dev/null +++ b/database/src/migrations/20240911000000_add_not_null_for_uuid_columns.ts @@ -0,0 +1,77 @@ +import { Knex } from 'knex'; + +/** + * Migrate existing uuid columns that are NULL to have a UUID value. + * + * 1. Generate UUIDs for existing uuid columns that are NULL + * 2. Set uuid columns to NOT NULL + * + * @export + * @param {Knex} knex + * @return {*} {Promise} + */ +export async function up(knex: Knex): Promise { + await knex.raw(`--sql + + ---------------------------------------------------------------------------------------- + -- Drop Views + ---------------------------------------------------------------------------------------- + SET SEARCH_PATH=biohub_dapi_v1; + + DROP VIEW IF EXISTS survey; + DROP VIEW IF EXISTS project; + DROP VIEW IF EXISTS project_attachment; + DROP VIEW IF EXISTS project_report_attachment; + DROP VIEW IF EXISTS survey_attachment; + DROP VIEW IF EXISTS survey_report_attachment; + DROP VIEW IF EXISTS survey_summary_submission; + DROP VIEW IF EXISTS survey_telemetry_credential_attachment; + + + ---------------------------------------------------------------------------------------- + -- Generate UUIDs for existing uuid columns that are NULL + ---------------------------------------------------------------------------------------- + SET SEARCH_PATH=biohub; + + UPDATE survey SET uuid = public.gen_random_uuid() WHERE uuid IS NULL; + UPDATE project SET uuid = public.gen_random_uuid() WHERE uuid IS NULL; + UPDATE project_attachment SET uuid = public.gen_random_uuid() WHERE uuid IS NULL; + UPDATE project_report_attachment SET uuid = public.gen_random_uuid() WHERE uuid IS NULL; + UPDATE survey_attachment SET uuid = public.gen_random_uuid() WHERE uuid IS NULL; + UPDATE survey_report_attachment SET uuid = public.gen_random_uuid() WHERE uuid IS NULL; + UPDATE survey_summary_submission SET uuid = public.gen_random_uuid() WHERE uuid IS NULL; + UPDATE survey_telemetry_credential_attachment SET uuid = public.gen_random_uuid() WHERE uuid IS NULL; + + ---------------------------------------------------------------------------------------- + -- Set uuid columns to NOT NULL + ---------------------------------------------------------------------------------------- + + ALTER TABLE survey ALTER COLUMN uuid SET NOT NULL; + ALTER TABLE project ALTER COLUMN uuid SET NOT NULL; + ALTER TABLE project_attachment ALTER COLUMN uuid SET NOT NULL; + ALTER TABLE project_report_attachment ALTER COLUMN uuid SET NOT NULL; + ALTER TABLE survey_attachment ALTER COLUMN uuid SET NOT NULL; + ALTER TABLE survey_report_attachment ALTER COLUMN uuid SET NOT NULL; + ALTER TABLE survey_summary_submission ALTER COLUMN uuid SET NOT NULL; + ALTER TABLE survey_telemetry_credential_attachment ALTER COLUMN uuid SET NOT NULL; + + + ---------------------------------------------------------------------------------------- + -- Recreate Views + ---------------------------------------------------------------------------------------- + SET SEARCH_PATH=biohub_dapi_v1; + + CREATE OR REPLACE VIEW survey AS SELECT * FROM biohub.survey; + CREATE OR REPLACE VIEW project AS SELECT * FROM biohub.project; + CREATE OR REPLACE VIEW project_attachment AS SELECT * FROM biohub.project_attachment; + CREATE OR REPLACE VIEW project_report_attachment AS SELECT * FROM biohub.project_report_attachment; + CREATE OR REPLACE VIEW survey_attachment AS SELECT * FROM biohub.survey_attachment; + CREATE OR REPLACE VIEW survey_report_attachment AS SELECT * FROM biohub.survey_report_attachment; + CREATE OR REPLACE VIEW survey_summary_submission AS SELECT * FROM biohub.survey_summary_submission; + CREATE OR REPLACE VIEW survey_telemetry_credential_attachment AS SELECT * FROM biohub.survey_telemetry_credential_attachment; + `); +} + +export async function down(knex: Knex): Promise { + await knex.raw(``); +} From 556ba8dc22099adfa8f95b366c238c032cd00cbb Mon Sep 17 00:00:00 2001 From: Nick Phura Date: Mon, 16 Sep 2024 16:18:05 -0700 Subject: [PATCH 2/3] TechDebt: Centralize Virus Scan (#1365) * Centralize Virus scan --- api/src/app.ts | 23 +++++++-- .../attachments/report/upload.test.ts | 3 -- .../{projectId}/attachments/report/upload.ts | 10 +--- .../{projectId}/attachments/upload.test.ts | 3 -- .../project/{projectId}/attachments/upload.ts | 10 +--- .../attachments/report/upload.test.ts | 3 -- .../{surveyId}/attachments/report/upload.ts | 10 +--- .../attachments/telemetry/index.test.ts | 42 ---------------- .../{surveyId}/attachments/telemetry/index.ts | 9 +--- .../{surveyId}/attachments/upload.test.ts | 3 -- .../survey/{surveyId}/attachments/upload.ts | 10 +--- .../{surveyId}/critters/captures/import.ts | 9 ---- .../survey/{surveyId}/critters/import.test.ts | 48 ------------------- .../survey/{surveyId}/critters/import.ts | 9 ---- .../{surveyId}/critters/markings/import.ts | 9 ---- .../critters/measurements/import.ts | 9 ---- .../{surveyId}/observations/upload.test.ts | 24 ---------- .../survey/{surveyId}/observations/upload.ts | 10 +--- .../survey/{surveyId}/telemetry/upload.ts | 10 +--- 19 files changed, 27 insertions(+), 227 deletions(-) diff --git a/api/src/app.ts b/api/src/app.ts index 00eb5b57b9..a105802b8f 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -5,7 +5,7 @@ import { OpenAPIV3 } from 'openapi-types'; import path from 'path'; import swaggerUIExperss from 'swagger-ui-express'; import { defaultPoolConfig, initDBPool } from './database/db'; -import { ensureHTTPError, HTTP500 } from './errors/http-error'; +import { ensureHTTPError, HTTP400, HTTP500 } from './errors/http-error'; import { authorizeAndAuthenticateMiddleware, getCritterbaseProxyMiddleware, @@ -13,6 +13,7 @@ import { } from './middleware/critterbase-proxy'; import { rootAPIDoc } from './openapi/root-api-doc'; import { authenticateRequest, authenticateRequestOptional } from './request-handlers/security/authentication'; +import { scanFileForVirus } from './utils/file-utils'; import { getLogger } from './utils/logger'; const defaultLog = getLogger('app'); @@ -75,7 +76,7 @@ const openAPIFramework = initialize({ 'application/json': express.json({ limit: MAX_REQ_BODY_SIZE }), 'multipart/form-data': function (req, res, next) { const multerRequestHandler = multer({ - storage: multer.memoryStorage(), + storage: multer.memoryStorage(), // TODO change to local/PVC storage and stream file uploads to S3? limits: { fileSize: MAX_UPLOAD_FILE_SIZE } }).array('media', MAX_UPLOAD_NUM_FILES); @@ -89,11 +90,27 @@ const openAPIFramework = initialize({ * * @see https://www.npmjs.com/package/express-openapi#argsconsumesmiddleware */ - multerRequestHandler(req, res, (error?: any) => { + multerRequestHandler(req, res, async (error?: any) => { if (error) { return next(error); } + // Scan files for malicious content, if enabled + const virusScanPromises = (req.files as Express.Multer.File[]).map(async function (file) { + const isSafe = await scanFileForVirus(file); + + if (!isSafe) { + throw new HTTP400('Malicious file content detected.', [{ file_name: file.originalname }]); + } + }); + + try { + await Promise.all(virusScanPromises); + } catch (error) { + // If a virus is detected, return error and do not continue + return next(error); + } + // Ensure `req.files` or `req.body.media` is always set to an array const multerFiles = req.files ?? []; diff --git a/api/src/paths/project/{projectId}/attachments/report/upload.test.ts b/api/src/paths/project/{projectId}/attachments/report/upload.test.ts index 6b10293027..d37ed53080 100644 --- a/api/src/paths/project/{projectId}/attachments/report/upload.test.ts +++ b/api/src/paths/project/{projectId}/attachments/report/upload.test.ts @@ -46,8 +46,6 @@ describe('uploadMedia', () => { } }); - sinon.stub(file_utils, 'scanFileForVirus').resolves(true); - const expectedError = new Error('cannot process request'); sinon.stub(AttachmentService.prototype, 'upsertProjectReportAttachment').rejects(expectedError); @@ -69,7 +67,6 @@ describe('uploadMedia', () => { } }); - sinon.stub(file_utils, 'scanFileForVirus').resolves(true); sinon.stub(file_utils, 'uploadFileToS3').resolves(); const expectedResponse = { attachmentId: 1, revision_count: 1 }; diff --git a/api/src/paths/project/{projectId}/attachments/report/upload.ts b/api/src/paths/project/{projectId}/attachments/report/upload.ts index 046e07e851..150ebc1847 100644 --- a/api/src/paths/project/{projectId}/attachments/report/upload.ts +++ b/api/src/paths/project/{projectId}/attachments/report/upload.ts @@ -2,11 +2,10 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../constants/roles'; import { getDBConnection } from '../../../../../database/db'; -import { HTTP400 } from '../../../../../errors/http-error'; import { fileSchema } from '../../../../../openapi/schemas/file'; import { authorizeRequestHandler } from '../../../../../request-handlers/security/authorization'; import { AttachmentService } from '../../../../../services/attachment-service'; -import { scanFileForVirus, uploadFileToS3 } from '../../../../../utils/file-utils'; +import { uploadFileToS3 } from '../../../../../utils/file-utils'; import { getLogger } from '../../../../../utils/logger'; import { getFileFromRequest } from '../../../../../utils/request'; @@ -158,13 +157,6 @@ export function uploadMedia(): RequestHandler { try { await connection.open(); - // Scan file for viruses using ClamAV - const virusScanResult = await scanFileForVirus(rawMediaFile); - - if (!virusScanResult) { - throw new HTTP400('Malicious content detected, upload cancelled'); - } - const attachmentService = new AttachmentService(connection); //Upsert a report attachment diff --git a/api/src/paths/project/{projectId}/attachments/upload.test.ts b/api/src/paths/project/{projectId}/attachments/upload.test.ts index a4ea2e5529..932ee0f8f4 100644 --- a/api/src/paths/project/{projectId}/attachments/upload.test.ts +++ b/api/src/paths/project/{projectId}/attachments/upload.test.ts @@ -44,8 +44,6 @@ describe('uploadMedia', () => { } }); - sinon.stub(file_utils, 'scanFileForVirus').resolves(true); - const expectedError = new Error('cannot process request'); sinon.stub(AttachmentService.prototype, 'upsertProjectAttachment').rejects(expectedError); @@ -67,7 +65,6 @@ describe('uploadMedia', () => { } }); - sinon.stub(file_utils, 'scanFileForVirus').resolves(true); sinon.stub(file_utils, 'uploadFileToS3').resolves(); const expectedResponse = { attachmentId: 1, revision_count: 1 }; diff --git a/api/src/paths/project/{projectId}/attachments/upload.ts b/api/src/paths/project/{projectId}/attachments/upload.ts index 7e58246084..aecadf410d 100644 --- a/api/src/paths/project/{projectId}/attachments/upload.ts +++ b/api/src/paths/project/{projectId}/attachments/upload.ts @@ -3,11 +3,10 @@ import { Operation } from 'express-openapi'; import { ATTACHMENT_TYPE } from '../../../../constants/attachments'; import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../constants/roles'; import { getDBConnection } from '../../../../database/db'; -import { HTTP400 } from '../../../../errors/http-error'; import { fileSchema } from '../../../../openapi/schemas/file'; import { authorizeRequestHandler } from '../../../../request-handlers/security/authorization'; import { AttachmentService } from '../../../../services/attachment-service'; -import { scanFileForVirus, uploadFileToS3 } from '../../../../utils/file-utils'; +import { uploadFileToS3 } from '../../../../utils/file-utils'; import { getLogger } from '../../../../utils/logger'; import { getFileFromRequest } from '../../../../utils/request'; @@ -133,13 +132,6 @@ export function uploadMedia(): RequestHandler { try { await connection.open(); - // Scan file for viruses using ClamAV - const virusScanResult = await scanFileForVirus(rawMediaFile); - - if (!virusScanResult) { - throw new HTTP400('Malicious content detected, upload cancelled'); - } - const attachmentService = new AttachmentService(connection); const upsertResult = await attachmentService.upsertProjectAttachment( diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/report/upload.test.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/report/upload.test.ts index cb3475a276..fd56d310a9 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/report/upload.test.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/report/upload.test.ts @@ -40,8 +40,6 @@ describe('uploadMedia', () => { } } as any; - sinon.stub(file_utils, 'scanFileForVirus').resolves(true); - const expectedError = new Error('cannot process request'); sinon.stub(AttachmentService.prototype, 'upsertSurveyReportAttachment').rejects(expectedError); @@ -59,7 +57,6 @@ describe('uploadMedia', () => { const dbConnectionObj = getMockDBConnection(); sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); - sinon.stub(file_utils, 'scanFileForVirus').resolves(true); sinon.stub(file_utils, 'uploadFileToS3').resolves(); const mockReq = { diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/report/upload.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/report/upload.ts index b30bf68961..b058a597c7 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/report/upload.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/report/upload.ts @@ -2,11 +2,10 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../../database/db'; -import { HTTP400 } from '../../../../../../../errors/http-error'; import { fileSchema } from '../../../../../../../openapi/schemas/file'; import { authorizeRequestHandler } from '../../../../../../../request-handlers/security/authorization'; import { AttachmentService } from '../../../../../../../services/attachment-service'; -import { scanFileForVirus, uploadFileToS3 } from '../../../../../../../utils/file-utils'; +import { uploadFileToS3 } from '../../../../../../../utils/file-utils'; import { getLogger } from '../../../../../../../utils/logger'; import { getFileFromRequest } from '../../../../../../../utils/request'; @@ -175,13 +174,6 @@ export function uploadMedia(): RequestHandler { try { await connection.open(); - // Scan file for viruses using ClamAV - const virusScanResult = await scanFileForVirus(rawMediaFile); - - if (!virusScanResult) { - throw new HTTP400('Malicious content detected, upload cancelled'); - } - const attachmentService = new AttachmentService(connection); const upsertResult = await attachmentService.upsertSurveyReportAttachment( diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/telemetry/index.test.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/telemetry/index.test.ts index fe0d0f1a56..d0db0b5aa2 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/telemetry/index.test.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/telemetry/index.test.ts @@ -19,46 +19,10 @@ describe('postSurveyTelemetryCredentialAttachment', () => { sinon.restore(); }); - it('should throw an error when file has malicious content', async () => { - const dbConnectionObj = getMockDBConnection(); - sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); - - sinon.stub(file_utils, 'scanFileForVirus').resolves(false); // fail virus scan - - const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); - - mockReq.keycloak_token = {} as KeycloakUserInformation; - mockReq.params = { - projectId: '1', - surveyId: '2' - }; - mockReq.files = [ - { - fieldname: 'media', - originalname: 'test.keyx', - encoding: '7bit', - mimetype: 'text/plain', - size: 340 - } - ] as Express.Multer.File[]; - - const requestHandler = postSurveyTelemetryCredentialAttachment(); - - try { - await requestHandler(mockReq, mockRes, mockNext); - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal('Malicious content detected, upload cancelled'); - } - }); - it('should throw an error when file type is invalid', async () => { const dbConnectionObj = getMockDBConnection(); sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); - sinon.stub(file_utils, 'scanFileForVirus').resolves(true); - const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); mockReq.keycloak_token = {} as KeycloakUserInformation; @@ -93,8 +57,6 @@ describe('postSurveyTelemetryCredentialAttachment', () => { const dbConnectionObj = getMockDBConnection(); sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); - sinon.stub(file_utils, 'scanFileForVirus').resolves(true); - const upsertSurveyTelemetryCredentialAttachmentStub = sinon .stub(AttachmentService.prototype, 'upsertSurveyTelemetryCredentialAttachment') .resolves({ survey_telemetry_credential_attachment_id: 44, key: 'path/to/file/test.keyx' }); @@ -134,8 +96,6 @@ describe('postSurveyTelemetryCredentialAttachment', () => { const dbConnectionObj = getMockDBConnection(); sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); - sinon.stub(file_utils, 'scanFileForVirus').resolves(true); - const upsertSurveyTelemetryCredentialAttachmentStub = sinon .stub(AttachmentService.prototype, 'upsertSurveyTelemetryCredentialAttachment') .resolves({ survey_telemetry_credential_attachment_id: 44, key: 'path/to/file/test.keyx' }); @@ -175,8 +135,6 @@ describe('postSurveyTelemetryCredentialAttachment', () => { const dbConnectionObj = getMockDBConnection(); sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); - sinon.stub(file_utils, 'scanFileForVirus').resolves(true); - const upsertSurveyTelemetryCredentialAttachmentStub = sinon .stub(AttachmentService.prototype, 'upsertSurveyTelemetryCredentialAttachment') .resolves({ survey_telemetry_credential_attachment_id: 44, key: 'path/to/file/test.keyx' }); diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/telemetry/index.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/telemetry/index.ts index f798f3dfab..fa99abc18c 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/telemetry/index.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/telemetry/index.ts @@ -9,7 +9,7 @@ import { authorizeRequestHandler } from '../../../../../../../request-handlers/s import { AttachmentService } from '../../../../../../../services/attachment-service'; import { BctwKeyxService } from '../../../../../../../services/bctw-service/bctw-keyx-service'; import { getBctwUser } from '../../../../../../../services/bctw-service/bctw-service'; -import { scanFileForVirus, uploadFileToS3 } from '../../../../../../../utils/file-utils'; +import { uploadFileToS3 } from '../../../../../../../utils/file-utils'; import { getLogger } from '../../../../../../../utils/logger'; import { isValidTelementryCredentialFile } from '../../../../../../../utils/media/media-utils'; import { getFileFromRequest } from '../../../../../../../utils/request'; @@ -139,13 +139,6 @@ export function postSurveyTelemetryCredentialAttachment(): RequestHandler { files: { ...rawMediaFile, buffer: 'Too big to print' } }); - // Scan file for viruses using ClamAV - const virusScanResult = await scanFileForVirus(rawMediaFile); - - if (!virusScanResult) { - throw new HTTP400('Malicious content detected, upload cancelled'); - } - const isTelemetryCredentialFile = isValidTelementryCredentialFile(rawMediaFile); if (isTelemetryCredentialFile.error) { diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/upload.test.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/upload.test.ts index d15dd36d2a..c68197ad0a 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/upload.test.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/upload.test.ts @@ -44,8 +44,6 @@ describe('uploadMedia', () => { } }); - sinon.stub(file_utils, 'scanFileForVirus').resolves(true); - const expectedError = new Error('cannot process request'); sinon.stub(AttachmentService.prototype, 'upsertSurveyAttachment').rejects(expectedError); @@ -67,7 +65,6 @@ describe('uploadMedia', () => { } }); - sinon.stub(file_utils, 'scanFileForVirus').resolves(true); sinon.stub(file_utils, 'uploadFileToS3').resolves(); const expectedResponse = { attachmentId: 1, revision_count: 1 }; diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/upload.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/upload.ts index acf61c8409..98ef6123a3 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/upload.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/upload.ts @@ -3,11 +3,10 @@ import { Operation } from 'express-openapi'; import { ATTACHMENT_TYPE } from '../../../../../../constants/attachments'; import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../database/db'; -import { HTTP400 } from '../../../../../../errors/http-error'; import { fileSchema } from '../../../../../../openapi/schemas/file'; import { authorizeRequestHandler } from '../../../../../../request-handlers/security/authorization'; import { AttachmentService } from '../../../../../../services/attachment-service'; -import { scanFileForVirus, uploadFileToS3 } from '../../../../../../utils/file-utils'; +import { uploadFileToS3 } from '../../../../../../utils/file-utils'; import { getLogger } from '../../../../../../utils/logger'; import { getFileFromRequest } from '../../../../../../utils/request'; @@ -124,13 +123,6 @@ export function uploadMedia(): RequestHandler { try { await connection.open(); - // Scan file for viruses using ClamAV - const virusScanResult = await scanFileForVirus(rawMediaFile); - - if (!virusScanResult) { - throw new HTTP400('Malicious content detected, upload cancelled'); - } - const attachmentService = new AttachmentService(connection); const upsertResult = await attachmentService.upsertSurveyAttachment( diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/critters/captures/import.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/critters/captures/import.ts index c03e54f881..ec6142349d 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/critters/captures/import.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/critters/captures/import.ts @@ -2,12 +2,10 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../../database/db'; -import { HTTP400 } from '../../../../../../../errors/http-error'; import { csvFileSchema } from '../../../../../../../openapi/schemas/file'; import { authorizeRequestHandler } from '../../../../../../../request-handlers/security/authorization'; import { ImportCapturesStrategy } from '../../../../../../../services/import-services/capture/import-captures-strategy'; import { importCSV } from '../../../../../../../services/import-services/import-csv'; -import { scanFileForVirus } from '../../../../../../../utils/file-utils'; import { getLogger } from '../../../../../../../utils/logger'; import { parseMulterFile } from '../../../../../../../utils/media/media-utils'; import { getFileFromRequest } from '../../../../../../../utils/request'; @@ -135,13 +133,6 @@ export function importCsv(): RequestHandler { try { await connection.open(); - // Check for viruses / malware - const virusScanResult = await scanFileForVirus(rawFile); - - if (!virusScanResult) { - throw new HTTP400('Malicious content detected, import cancelled.'); - } - const importCsvCaptures = new ImportCapturesStrategy(connection, surveyId); // Pass CSV file and importer as dependencies diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/critters/import.test.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/critters/import.test.ts index e9fe818e6f..f31ba7d465 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/critters/import.test.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/critters/import.test.ts @@ -1,9 +1,7 @@ import { expect } from 'chai'; import sinon from 'sinon'; import * as db from '../../../../../../database/db'; -import { HTTP400 } from '../../../../../../errors/http-error'; import * as strategy from '../../../../../../services/import-services/import-csv'; -import * as fileUtils from '../../../../../../utils/file-utils'; import { getMockDBConnection, getRequestHandlerMocks } from '../../../../../../__mocks__/db'; import { importCsv } from './import'; @@ -16,7 +14,6 @@ describe('importCsv', () => { const mockDBConnection = getMockDBConnection({ open: sinon.stub(), commit: sinon.stub(), release: sinon.stub() }); const getDBConnectionStub = sinon.stub(db, 'getDBConnection').returns(mockDBConnection); const mockImportCSV = sinon.stub(strategy, 'importCSV').resolves([1, 2]); - const mockFileScan = sinon.stub(fileUtils, 'scanFileForVirus').resolves(true); const mockFile = { originalname: 'test.csv', mimetype: 'test.csv', buffer: Buffer.alloc(1) } as Express.Multer.File; @@ -31,8 +28,6 @@ describe('importCsv', () => { expect(mockDBConnection.open).to.have.been.calledOnce; - expect(mockFileScan).to.have.been.calledOnceWithExactly(mockFile); - expect(getDBConnectionStub).to.have.been.calledOnce; expect(mockImportCSV).to.have.been.calledOnce; @@ -42,47 +37,4 @@ describe('importCsv', () => { expect(mockDBConnection.commit).to.have.been.calledOnce; expect(mockDBConnection.release).to.have.been.calledOnce; }); - - it('should catch error and rollback if file contains malware', async () => { - const mockDBConnection = getMockDBConnection({ - open: sinon.stub(), - commit: sinon.stub(), - release: sinon.stub(), - rollback: sinon.stub() - }); - const getDBConnectionStub = sinon.stub(db, 'getDBConnection').returns(mockDBConnection); - - const mockFileScan = sinon.stub(fileUtils, 'scanFileForVirus').resolves(false); - - const mockFile = { - originalname: 'test.csv', - mimetype: 'test.csv', - buffer: Buffer.alloc(1) - } as Express.Multer.File; - - const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); - - mockReq.files = [mockFile]; - mockReq.params.surveyId = '1'; - - const requestHandler = importCsv(); - - try { - await requestHandler(mockReq, mockRes, mockNext); - expect.fail(); - } catch (err: any) { - expect(err).to.be.instanceof(HTTP400); - expect(err.message).to.be.contains('Malicious content detected'); - } - - expect(mockDBConnection.open).to.have.been.calledOnce; - expect(mockFileScan).to.have.been.calledOnceWithExactly(mockFile); - - expect(getDBConnectionStub).to.have.been.calledOnce; - expect(mockRes.json).to.not.have.been.called; - - expect(mockDBConnection.rollback).to.have.been.called; - expect(mockDBConnection.commit).to.not.have.been.called; - expect(mockDBConnection.release).to.have.been.called; - }); }); diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/critters/import.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/critters/import.ts index 01a71c02aa..01ef07460a 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/critters/import.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/critters/import.ts @@ -2,12 +2,10 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../database/db'; -import { HTTP400 } from '../../../../../../errors/http-error'; import { csvFileSchema } from '../../../../../../openapi/schemas/file'; import { authorizeRequestHandler } from '../../../../../../request-handlers/security/authorization'; import { ImportCrittersStrategy } from '../../../../../../services/import-services/critter/import-critters-strategy'; import { importCSV } from '../../../../../../services/import-services/import-csv'; -import { scanFileForVirus } from '../../../../../../utils/file-utils'; import { getLogger } from '../../../../../../utils/logger'; import { parseMulterFile } from '../../../../../../utils/media/media-utils'; import { getFileFromRequest } from '../../../../../../utils/request'; @@ -137,13 +135,6 @@ export function importCsv(): RequestHandler { try { await connection.open(); - // Check for viruses / malware - const virusScanResult = await scanFileForVirus(rawFile); - - if (!virusScanResult) { - throw new HTTP400('Malicious content detected, import cancelled.'); - } - // Critter CSV import strategy - child of CSVImportStrategy const importCsvCritters = new ImportCrittersStrategy(connection, surveyId); diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/critters/markings/import.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/critters/markings/import.ts index 4b5126bc5b..31336e8c0d 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/critters/markings/import.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/critters/markings/import.ts @@ -2,12 +2,10 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../../database/db'; -import { HTTP400 } from '../../../../../../../errors/http-error'; import { csvFileSchema } from '../../../../../../../openapi/schemas/file'; import { authorizeRequestHandler } from '../../../../../../../request-handlers/security/authorization'; import { importCSV } from '../../../../../../../services/import-services/import-csv'; import { ImportMarkingsStrategy } from '../../../../../../../services/import-services/marking/import-markings-strategy'; -import { scanFileForVirus } from '../../../../../../../utils/file-utils'; import { getLogger } from '../../../../../../../utils/logger'; import { parseMulterFile } from '../../../../../../../utils/media/media-utils'; import { getFileFromRequest } from '../../../../../../../utils/request'; @@ -135,13 +133,6 @@ export function importCsv(): RequestHandler { try { await connection.open(); - // Check for viruses / malware - const virusScanResult = await scanFileForVirus(rawFile); - - if (!virusScanResult) { - throw new HTTP400('Malicious content detected, import cancelled.'); - } - const importCsvMarkingsStrategy = new ImportMarkingsStrategy(connection, surveyId); // Pass CSV file and importer as dependencies diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/critters/measurements/import.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/critters/measurements/import.ts index 9988b6bb00..affd0813a4 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/critters/measurements/import.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/critters/measurements/import.ts @@ -2,12 +2,10 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../../database/db'; -import { HTTP400 } from '../../../../../../../errors/http-error'; import { csvFileSchema } from '../../../../../../../openapi/schemas/file'; import { authorizeRequestHandler } from '../../../../../../../request-handlers/security/authorization'; import { importCSV } from '../../../../../../../services/import-services/import-csv'; import { ImportMeasurementsStrategy } from '../../../../../../../services/import-services/measurement/import-measurements-strategy'; -import { scanFileForVirus } from '../../../../../../../utils/file-utils'; import { getLogger } from '../../../../../../../utils/logger'; import { parseMulterFile } from '../../../../../../../utils/media/media-utils'; import { getFileFromRequest } from '../../../../../../../utils/request'; @@ -135,13 +133,6 @@ export function importCsv(): RequestHandler { try { await connection.open(); - // Check for viruses / malware - const virusScanResult = await scanFileForVirus(rawFile); - - if (!virusScanResult) { - throw new HTTP400('Malicious content detected, import cancelled.'); - } - const importCsvMeasurementsStrategy = new ImportMeasurementsStrategy(connection, surveyId); // Pass CSV file and importer as dependencies diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/observations/upload.test.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/observations/upload.test.ts index 0cde24b909..4188512033 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/observations/upload.test.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/observations/upload.test.ts @@ -36,27 +36,6 @@ describe('uploadMedia', () => { body: {} } as any; - it('should throw an error when file has malicious content', async () => { - sinon.stub(db, 'getDBConnection').returns({ - ...dbConnectionObj, - systemUserId: () => { - return 20; - } - }); - - sinon.stub(file_utils, 'scanFileForVirus').resolves(false); - - try { - const result = upload.uploadMedia(); - - await result(mockReq, null as unknown as any, null as unknown as any); - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal('Malicious content detected, upload cancelled'); - } - }); - it('should throw an error if failure occurs', async () => { sinon.stub(db, 'getDBConnection').returns({ ...dbConnectionObj, @@ -65,8 +44,6 @@ describe('uploadMedia', () => { } }); - sinon.stub(file_utils, 'scanFileForVirus').resolves(true); - const expectedError = new Error('cannot process request'); sinon.stub(ObservationService.prototype, 'insertSurveyObservationSubmission').rejects(expectedError); @@ -88,7 +65,6 @@ describe('uploadMedia', () => { } }); - sinon.stub(file_utils, 'scanFileForVirus').resolves(true); sinon.stub(file_utils, 'uploadFileToS3').resolves(); const expectedResponse = { submissionId: 1 }; diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/observations/upload.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/observations/upload.ts index b61cd5d4bd..6ed875b96e 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/observations/upload.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/observations/upload.ts @@ -2,11 +2,10 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../database/db'; -import { HTTP400 } from '../../../../../../errors/http-error'; import { csvFileSchema } from '../../../../../../openapi/schemas/file'; import { authorizeRequestHandler } from '../../../../../../request-handlers/security/authorization'; import { ObservationService } from '../../../../../../services/observation-service'; -import { scanFileForVirus, uploadFileToS3 } from '../../../../../../utils/file-utils'; +import { uploadFileToS3 } from '../../../../../../utils/file-utils'; import { getLogger } from '../../../../../../utils/logger'; import { getFileFromRequest } from '../../../../../../utils/request'; @@ -121,13 +120,6 @@ export function uploadMedia(): RequestHandler { try { await connection.open(); - // Scan file for viruses using ClamAV - const virusScanResult = await scanFileForVirus(rawMediaFile); - - if (!virusScanResult) { - throw new HTTP400('Malicious content detected, upload cancelled'); - } - // Insert a new record in the `survey_observation_submission` table const observationService = new ObservationService(connection); const { submission_id: submissionId, key } = await observationService.insertSurveyObservationSubmission( diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/telemetry/upload.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/telemetry/upload.ts index 338aadd4a2..71dc705741 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/telemetry/upload.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/telemetry/upload.ts @@ -2,11 +2,10 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../database/db'; -import { HTTP400 } from '../../../../../../errors/http-error'; import { csvFileSchema } from '../../../../../../openapi/schemas/file'; import { authorizeRequestHandler } from '../../../../../../request-handlers/security/authorization'; import { TelemetryService } from '../../../../../../services/telemetry-service'; -import { scanFileForVirus, uploadFileToS3 } from '../../../../../../utils/file-utils'; +import { uploadFileToS3 } from '../../../../../../utils/file-utils'; import { getLogger } from '../../../../../../utils/logger'; import { getFileFromRequest } from '../../../../../../utils/request'; @@ -120,13 +119,6 @@ export function uploadMedia(): RequestHandler { try { await connection.open(); - // Scan file for viruses using ClamAV - const virusScanResult = await scanFileForVirus(rawMediaFile); - - if (!virusScanResult) { - throw new HTTP400('Malicious content detected, upload cancelled'); - } - // Insert a new record in the `survey_telemetry_submission` table const service = new TelemetryService(connection); const { submission_id: submissionId, key } = await service.insertSurveyTelemetrySubmission( From 1c26829d3a969fedf7ec75d3d5fb700adb21a60f Mon Sep 17 00:00:00 2001 From: Nick Phura Date: Tue, 17 Sep 2024 16:20:46 -0700 Subject: [PATCH 3/3] SIMSBIOHUB-562: Export Survey Data (#1273) * Initial commit * Updates * Initial survey export UIs * Copy changes from biohub * Experimental stream * Updates * Updates * Update export backend * Fix unit test * ignore-skip * Tweaks * Updates to support export stream configs * Update config * Working with api strams * Revert virus scan change * ignore-skip * styling for export dialog * Remove debug log * Tweaks * Fix error message * Update error logging, add unit tests. * Add more unit tests --------- Co-authored-by: Macgregor Aubertin-Young <108430771+mauberti-bc@users.noreply.github.com> Co-authored-by: Macgregor Aubertin-Young --- Makefile | 27 +- api/package-lock.json | 2498 ++++++++++++----- api/package.json | 4 + api/src/__mocks__/db.ts | 8 +- api/src/database/db.test.ts | 46 - api/src/database/db.ts | 37 +- api/src/models/animal-view.ts | 31 + api/src/models/telemetry-view.ts | 31 + .../survey/{surveyId}/export/index.test.ts | 98 + .../survey/{surveyId}/export/index.ts | 184 ++ .../repositories/survey-critter-repository.ts | 4 + .../export-services/export-service.test.ts | 120 + .../export-services/export-service.ts | 145 + .../export-services/export-strategy.ts | 75 + .../export-services/export-utils.test.ts | 72 + .../services/export-services/export-utils.ts | 101 + .../export-observation-strategy.test.ts | 27 + .../export-observation-strategy.ts | 73 + .../export-survey-metadata-strategy.test.ts | 32 + .../survey/export-survey-metadata-strategy.ts | 69 + .../survey/export-survey-strategy.test.ts | 147 + .../survey/export-survey-strategy.ts | 132 + .../export-telemetry-strategy.test.ts | 27 + .../telemetry/export-telemetry-strategy.ts | 95 + api/src/utils/file-utils.test.ts | 10 +- api/src/utils/file-utils.ts | 85 +- api/src/utils/sql-utils.test.ts | 99 - api/src/utils/sql-utils.ts | 73 - app/package-lock.json | 23 - app/package.json | 1 - app/src/constants/i18n.ts | 6 + .../features/surveys/view/SurveyHeader.tsx | 30 +- .../view/survey-export/SurveyExportDialog.tsx | 107 + .../view/survey-export/SurveyExportForm.tsx | 228 ++ app/src/hooks/api/useSurveyApi.test.ts | 19 + app/src/hooks/api/useSurveyApi.ts | 22 +- package-lock.json | 6 - 37 files changed, 3776 insertions(+), 1016 deletions(-) create mode 100644 api/src/paths/project/{projectId}/survey/{surveyId}/export/index.test.ts create mode 100644 api/src/paths/project/{projectId}/survey/{surveyId}/export/index.ts create mode 100644 api/src/services/export-services/export-service.test.ts create mode 100644 api/src/services/export-services/export-service.ts create mode 100644 api/src/services/export-services/export-strategy.ts create mode 100644 api/src/services/export-services/export-utils.test.ts create mode 100644 api/src/services/export-services/export-utils.ts create mode 100644 api/src/services/export-services/observation/export-observation-strategy.test.ts create mode 100644 api/src/services/export-services/observation/export-observation-strategy.ts create mode 100644 api/src/services/export-services/survey/export-survey-metadata-strategy.test.ts create mode 100644 api/src/services/export-services/survey/export-survey-metadata-strategy.ts create mode 100644 api/src/services/export-services/survey/export-survey-strategy.test.ts create mode 100644 api/src/services/export-services/survey/export-survey-strategy.ts create mode 100644 api/src/services/export-services/telemetry/export-telemetry-strategy.test.ts create mode 100644 api/src/services/export-services/telemetry/export-telemetry-strategy.ts delete mode 100644 api/src/utils/sql-utils.test.ts delete mode 100644 api/src/utils/sql-utils.ts create mode 100644 app/src/features/surveys/view/survey-export/SurveyExportDialog.tsx create mode 100644 app/src/features/surveys/view/survey-export/SurveyExportForm.tsx delete mode 100644 package-lock.json diff --git a/Makefile b/Makefile index 8c18d71028..618d73f591 100644 --- a/Makefile +++ b/Makefile @@ -26,8 +26,7 @@ backend: | close build-backend run-backend ## Performs all commands necessary to web: | close build-web check-env run-web ## Performs all commands necessary to run all backend+web projects (db, api, app) in docker db-setup: | build-db-setup run-db-setup ## Performs all commands necessary to run the database migrations and seeding -db-migrate: | build-db-migrate run-db-migrate ## Performs all commands necessary to run the database migrations -db-rollback: | build-db-rollback run-db-rollback ## Performs all commands necessary to rollback the latest database migrations + clamav: | build-clamav run-clamav ## Performs all commands necessary to run clamav fix: | lint-fix format-fix ## Performs both lint-fix and format-fix commands @@ -158,30 +157,6 @@ run-db-setup: ## Run the database migrations and seeding @echo "===============================================" @docker compose up db_setup -build-db-migrate: ## Build the db knex migrations image - @echo "===============================================" - @echo "Make: build-db-migrate - building db knex migrate image" - @echo "===============================================" - @docker compose build db_migrate - -run-db-migrate: ## Run the database migrations - @echo "===============================================" - @echo "Make: run-db-migrate - running database migrations" - @echo "===============================================" - @docker compose up db_migrate - -build-db-rollback: ## Build the db knex rollback image - @echo "===============================================" - @echo "Make: build-db-rollback - building db knex rollback image" - @echo "===============================================" - @docker compose build db_rollback - -run-db-rollback: ## Rollback the latest database migrations - @echo "===============================================" - @echo "Make: run-db-rollback - rolling back the latest database migrations" - @echo "===============================================" - @docker compose up db_rollback - ## ------------------------------------------------------------------------------ ## clamav commands ## ------------------------------------------------------------------------------ diff --git a/api/package-lock.json b/api/package-lock.json index 6ac5daca69..132e69f877 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -10,6 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-s3": "^3.583.0", + "@aws-sdk/lib-storage": "^3.621.0", "@aws-sdk/s3-request-presigner": "^3.583.0", "@turf/bbox": "^6.5.0", "@turf/circle": "^6.5.0", @@ -17,6 +18,7 @@ "@turf/meta": "^6.5.0", "adm-zip": "0.5.12", "ajv": "^8.12.0", + "archiver": "^7.0.1", "axios": "^1.6.7", "clamscan": "^2.2.1", "dayjs": "^1.11.10", @@ -38,6 +40,7 @@ "mime": "^3.0.0", "multer": "^1.4.5-lts.1", "pg": "^8.7.1", + "pg-query-stream": "^4.5.5", "qs": "^6.10.1", "sql-template-strings": "^2.2.2", "swagger-ui-express": "^4.3.0", @@ -52,6 +55,7 @@ "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.1", "@types/adm-zip": "^0.4.34", + "@types/archiver": "^6.0.2", "@types/chai": "^4.3.14", "@types/clamscan": "^2.0.8", "@types/express": "^4.17.13", @@ -114,192 +118,320 @@ } }, "node_modules/@aws-crypto/crc32": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", - "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", "dependencies": { - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-crypto/crc32/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, "node_modules/@aws-crypto/crc32c": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-3.0.0.tgz", - "integrity": "sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", "dependencies": { - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" + "tslib": "^2.6.2" } }, - "node_modules/@aws-crypto/crc32c/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@aws-crypto/ie11-detection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", - "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", "dependencies": { - "tslib": "^1.11.1" + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@aws-crypto/sha1-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-3.0.0.tgz", - "integrity": "sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==", + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "dependencies": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@aws-crypto/sha1-browser/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/@aws-crypto/sha256-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", - "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", "dependencies": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/sha256-js": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/@aws-crypto/sha256-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", - "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", "dependencies": { - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, "node_modules/@aws-crypto/supports-web-crypto": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", - "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", "dependencies": { - "tslib": "^1.11.1" + "tslib": "^2.6.2" } }, - "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, "node_modules/@aws-crypto/util": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", - "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", "dependencies": { "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@aws-crypto/util/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.590.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.590.0.tgz", - "integrity": "sha512-so+pNua0ihsHaSdskw8HCwruoYTAfYSEs3ix4GD1++83C96KaJp3udAutYiCA+84JXg9zitFa7eK7ORJAVZmTw==", - "dependencies": { - "@aws-crypto/sha1-browser": "3.0.0", - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sso-oidc": "3.590.0", - "@aws-sdk/client-sts": "3.590.0", - "@aws-sdk/core": "3.588.0", - "@aws-sdk/credential-provider-node": "3.590.0", - "@aws-sdk/middleware-bucket-endpoint": "3.587.0", - "@aws-sdk/middleware-expect-continue": "3.577.0", - "@aws-sdk/middleware-flexible-checksums": "3.587.0", - "@aws-sdk/middleware-host-header": "3.577.0", - "@aws-sdk/middleware-location-constraint": "3.577.0", - "@aws-sdk/middleware-logger": "3.577.0", - "@aws-sdk/middleware-recursion-detection": "3.577.0", - "@aws-sdk/middleware-sdk-s3": "3.587.0", - "@aws-sdk/middleware-signing": "3.587.0", - "@aws-sdk/middleware-ssec": "3.577.0", - "@aws-sdk/middleware-user-agent": "3.587.0", - "@aws-sdk/region-config-resolver": "3.587.0", - "@aws-sdk/signature-v4-multi-region": "3.587.0", - "@aws-sdk/types": "3.577.0", - "@aws-sdk/util-endpoints": "3.587.0", - "@aws-sdk/util-user-agent-browser": "3.577.0", - "@aws-sdk/util-user-agent-node": "3.587.0", - "@aws-sdk/xml-builder": "3.575.0", - "@smithy/config-resolver": "^3.0.1", - "@smithy/core": "^2.1.1", - "@smithy/eventstream-serde-browser": "^3.0.0", - "@smithy/eventstream-serde-config-resolver": "^3.0.0", - "@smithy/eventstream-serde-node": "^3.0.0", - "@smithy/fetch-http-handler": "^3.0.1", - "@smithy/hash-blob-browser": "^3.0.0", - "@smithy/hash-node": "^3.0.0", - "@smithy/hash-stream-node": "^3.0.0", - "@smithy/invalid-dependency": "^3.0.0", - "@smithy/md5-js": "^3.0.0", - "@smithy/middleware-content-length": "^3.0.0", - "@smithy/middleware-endpoint": "^3.0.1", - "@smithy/middleware-retry": "^3.0.3", - "@smithy/middleware-serde": "^3.0.0", - "@smithy/middleware-stack": "^3.0.0", - "@smithy/node-config-provider": "^3.1.0", - "@smithy/node-http-handler": "^3.0.0", - "@smithy/protocol-http": "^4.0.0", - "@smithy/smithy-client": "^3.1.1", - "@smithy/types": "^3.0.0", - "@smithy/url-parser": "^3.0.0", + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.621.0.tgz", + "integrity": "sha512-YhGkd2HQTM4HCYJIAVWvfbUMpOF7XUr1W/e2LN3CFP0WTF4zcCJKesJ2iNHrExqC0Ek1+qarMxiXBK95itfjYQ==", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.621.0", + "@aws-sdk/client-sts": "3.621.0", + "@aws-sdk/core": "3.621.0", + "@aws-sdk/credential-provider-node": "3.621.0", + "@aws-sdk/middleware-bucket-endpoint": "3.620.0", + "@aws-sdk/middleware-expect-continue": "3.620.0", + "@aws-sdk/middleware-flexible-checksums": "3.620.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-location-constraint": "3.609.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-sdk-s3": "3.621.0", + "@aws-sdk/middleware-signing": "3.620.0", + "@aws-sdk/middleware-ssec": "3.609.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/signature-v4-multi-region": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@aws-sdk/xml-builder": "3.609.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.1", + "@smithy/eventstream-serde-browser": "^3.0.5", + "@smithy/eventstream-serde-config-resolver": "^3.0.3", + "@smithy/eventstream-serde-node": "^3.0.4", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-blob-browser": "^3.1.2", + "@smithy/hash-node": "^3.0.3", + "@smithy/hash-stream-node": "^3.1.2", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/md5-js": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.13", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.3", - "@smithy/util-defaults-mode-node": "^3.0.3", - "@smithy/util-endpoints": "^2.0.1", - "@smithy/util-retry": "^3.0.0", - "@smithy/util-stream": "^3.0.1", + "@smithy/util-defaults-mode-browser": "^3.0.13", + "@smithy/util-defaults-mode-node": "^3.0.13", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.621.0.tgz", + "integrity": "sha512-CJrQrtKylcqvyPkRR16JmPZkHroCkWwLErQrg30ZcBPNNok8xbfX6cYqG16XDTnu4lSYzv2Yqc4w4oOBv8xerQ==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.621.0.tgz", + "integrity": "sha512-u+ulCaHFveqHaTxgiYrEAyfBVP6GRKjnmDut67CtjhjslshPWYpo/ndtlCW1zc0RDne3uUeK13Pqp7dp7p1d6g==", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/signature-v4": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", + "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-uri-escape": "^3.0.0", "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.0.0", "tslib": "^2.6.2" }, "engines": { @@ -307,46 +439,46 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.590.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.590.0.tgz", - "integrity": "sha512-6xbC6oQVJKBRTyXyR3C15ksUsPOyW4p+uCj7dlKYWGJvh4vGTV8KhZKS53oPG8t4f1+OMJWjr5wKuXRoaFsmhQ==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.588.0", - "@aws-sdk/middleware-host-header": "3.577.0", - "@aws-sdk/middleware-logger": "3.577.0", - "@aws-sdk/middleware-recursion-detection": "3.577.0", - "@aws-sdk/middleware-user-agent": "3.587.0", - "@aws-sdk/region-config-resolver": "3.587.0", - "@aws-sdk/types": "3.577.0", - "@aws-sdk/util-endpoints": "3.587.0", - "@aws-sdk/util-user-agent-browser": "3.577.0", - "@aws-sdk/util-user-agent-node": "3.587.0", - "@smithy/config-resolver": "^3.0.1", - "@smithy/core": "^2.1.1", - "@smithy/fetch-http-handler": "^3.0.1", - "@smithy/hash-node": "^3.0.0", - "@smithy/invalid-dependency": "^3.0.0", - "@smithy/middleware-content-length": "^3.0.0", - "@smithy/middleware-endpoint": "^3.0.1", - "@smithy/middleware-retry": "^3.0.3", - "@smithy/middleware-serde": "^3.0.0", - "@smithy/middleware-stack": "^3.0.0", - "@smithy/node-config-provider": "^3.1.0", - "@smithy/node-http-handler": "^3.0.0", - "@smithy/protocol-http": "^4.0.0", - "@smithy/smithy-client": "^3.1.1", - "@smithy/types": "^3.0.0", - "@smithy/url-parser": "^3.0.0", + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.621.0.tgz", + "integrity": "sha512-xpKfikN4u0BaUYZA9FGUMkkDmfoIP0Q03+A86WjqDWhcOoqNA1DkHsE4kZ+r064ifkPUfcNuUvlkVTEoBZoFjA==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.621.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.1", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.13", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.3", - "@smithy/util-defaults-mode-node": "^3.0.3", - "@smithy/util-endpoints": "^2.0.1", - "@smithy/util-middleware": "^3.0.0", - "@smithy/util-retry": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.13", + "@smithy/util-defaults-mode-node": "^3.0.13", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -355,98 +487,124 @@ } }, "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.590.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.590.0.tgz", - "integrity": "sha512-3yCLPjq6WFfDpdUJKk/gSz4eAPDTjVknXaveMPi2QoVBCshneOnJsV16uNKlpVF1frTHrrDRfKYmbaVh6nFBvQ==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.590.0", - "@aws-sdk/core": "3.588.0", - "@aws-sdk/credential-provider-node": "3.590.0", - "@aws-sdk/middleware-host-header": "3.577.0", - "@aws-sdk/middleware-logger": "3.577.0", - "@aws-sdk/middleware-recursion-detection": "3.577.0", - "@aws-sdk/middleware-user-agent": "3.587.0", - "@aws-sdk/region-config-resolver": "3.587.0", - "@aws-sdk/types": "3.577.0", - "@aws-sdk/util-endpoints": "3.587.0", - "@aws-sdk/util-user-agent-browser": "3.577.0", - "@aws-sdk/util-user-agent-node": "3.587.0", - "@smithy/config-resolver": "^3.0.1", - "@smithy/core": "^2.1.1", - "@smithy/fetch-http-handler": "^3.0.1", - "@smithy/hash-node": "^3.0.0", - "@smithy/invalid-dependency": "^3.0.0", - "@smithy/middleware-content-length": "^3.0.0", - "@smithy/middleware-endpoint": "^3.0.1", - "@smithy/middleware-retry": "^3.0.3", - "@smithy/middleware-serde": "^3.0.0", - "@smithy/middleware-stack": "^3.0.0", - "@smithy/node-config-provider": "^3.1.0", - "@smithy/node-http-handler": "^3.0.0", - "@smithy/protocol-http": "^4.0.0", - "@smithy/smithy-client": "^3.1.1", - "@smithy/types": "^3.0.0", - "@smithy/url-parser": "^3.0.0", + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.621.0.tgz", + "integrity": "sha512-mMjk3mFUwV2Y68POf1BQMTF+F6qxt5tPu6daEUCNGC9Cenk3h2YXQQoS4/eSyYzuBiYk3vx49VgleRvdvkg8rg==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.621.0", + "@aws-sdk/credential-provider-node": "3.621.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.1", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.13", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.3", - "@smithy/util-defaults-mode-node": "^3.0.3", - "@smithy/util-endpoints": "^2.0.1", - "@smithy/util-middleware": "^3.0.0", - "@smithy/util-retry": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.13", + "@smithy/util-defaults-mode-node": "^3.0.13", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, "engines": { "node": ">=16.0.0" } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.590.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.590.0.tgz", - "integrity": "sha512-f4R1v1LSn4uLYZ5qj4DyL6gp7PXXzJeJsm2seheiJX+53LSF5L7XSDnQVtX1p9Tevv0hp2YUWUTg6QYwIVSuGg==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sso-oidc": "3.590.0", - "@aws-sdk/core": "3.588.0", - "@aws-sdk/credential-provider-node": "3.590.0", - "@aws-sdk/middleware-host-header": "3.577.0", - "@aws-sdk/middleware-logger": "3.577.0", - "@aws-sdk/middleware-recursion-detection": "3.577.0", - "@aws-sdk/middleware-user-agent": "3.587.0", - "@aws-sdk/region-config-resolver": "3.587.0", - "@aws-sdk/types": "3.577.0", - "@aws-sdk/util-endpoints": "3.587.0", - "@aws-sdk/util-user-agent-browser": "3.577.0", - "@aws-sdk/util-user-agent-node": "3.587.0", - "@smithy/config-resolver": "^3.0.1", - "@smithy/core": "^2.1.1", - "@smithy/fetch-http-handler": "^3.0.1", - "@smithy/hash-node": "^3.0.0", - "@smithy/invalid-dependency": "^3.0.0", - "@smithy/middleware-content-length": "^3.0.0", - "@smithy/middleware-endpoint": "^3.0.1", - "@smithy/middleware-retry": "^3.0.3", - "@smithy/middleware-serde": "^3.0.0", - "@smithy/middleware-stack": "^3.0.0", - "@smithy/node-config-provider": "^3.1.0", - "@smithy/node-http-handler": "^3.0.0", - "@smithy/protocol-http": "^4.0.0", - "@smithy/smithy-client": "^3.1.1", - "@smithy/types": "^3.0.0", - "@smithy/url-parser": "^3.0.0", + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.621.0.tgz", + "integrity": "sha512-707uiuReSt+nAx6d0c21xLjLm2lxeKc7padxjv92CIrIocnQSlJPxSCM7r5zBhwiahJA6MNQwmTl2xznU67KgA==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.621.0", + "@aws-sdk/core": "3.621.0", + "@aws-sdk/credential-provider-node": "3.621.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.1", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.13", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.3", - "@smithy/util-defaults-mode-node": "^3.0.3", - "@smithy/util-endpoints": "^2.0.1", - "@smithy/util-middleware": "^3.0.0", - "@smithy/util-retry": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.13", + "@smithy/util-defaults-mode-node": "^3.0.13", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -454,52 +612,75 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core": { - "version": "3.588.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.588.0.tgz", - "integrity": "sha512-O1c2+9ce46Z+iiid+W3iC1IvPbfIo5ev9CBi54GdNB9SaI8/3+f8MJcux0D6c9toCF0ArMersN/gp8ek57e9uQ==", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", "dependencies": { - "@smithy/core": "^2.1.1", - "@smithy/protocol-http": "^4.0.0", - "@smithy/signature-v4": "^3.0.0", - "@smithy/smithy-client": "^3.1.1", - "@smithy/types": "^3.0.0", - "fast-xml-parser": "4.2.5", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/fast-xml-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", - "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", - "funding": [ - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - }, - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], + "node_modules/@aws-sdk/core": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.621.0.tgz", + "integrity": "sha512-CtOwWmDdEiINkGXD93iGfXjN0WmCp9l45cDWHHGa8lRgEDyhuL7bwd/pH5aSzj0j8SiQBG2k0S7DHbd5RaqvbQ==", + "dependencies": { + "@smithy/core": "^2.3.1", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/signature-v4": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", + "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", "dependencies": { - "strnum": "^1.0.5" + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, - "bin": { - "fxparser": "src/cli/cli.js" + "engines": { + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.587.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.587.0.tgz", - "integrity": "sha512-Hyg/5KFECIk2k5o8wnVEiniV86yVkhn5kzITUydmNGCkXdBFHMHRx6hleQ1bqwJHbBskyu8nbYamzcwymmGwmw==", + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", + "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", "dependencies": { - "@aws-sdk/types": "3.577.0", - "@smithy/property-provider": "^3.1.0", - "@smithy/types": "^3.0.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -507,18 +688,30 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.587.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.587.0.tgz", - "integrity": "sha512-Su1SRWVRCuR1e32oxX3C1V4c5hpPN20WYcRfdcr2wXwHqSvys5DrnmuCC+JoEnS/zt3adUJhPliTqpfKgSdMrA==", + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.621.0.tgz", + "integrity": "sha512-/jc2tEsdkT1QQAI5Dvoci50DbSxtJrevemwFsm0B73pwCcOQZ5ZwwSdVqGsPutzYzUVx3bcXg3LRL7jLACqRIg==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", "dependencies": { - "@aws-sdk/types": "3.577.0", - "@smithy/fetch-http-handler": "^3.0.1", - "@smithy/node-http-handler": "^3.0.0", - "@smithy/property-provider": "^3.1.0", - "@smithy/protocol-http": "^4.0.0", - "@smithy/smithy-client": "^3.1.1", - "@smithy/types": "^3.0.0", - "@smithy/util-stream": "^3.0.1", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -526,45 +719,69 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.590.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.590.0.tgz", - "integrity": "sha512-Y5cFciAK38VIvRgZeND7HvFNR32thGtQb8Xop6cMn33FC78uwcRIu9Hc9699XTclCZqz4+Xl1WU+dZ+rnFn2AA==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.587.0", - "@aws-sdk/credential-provider-http": "3.587.0", - "@aws-sdk/credential-provider-process": "3.587.0", - "@aws-sdk/credential-provider-sso": "3.590.0", - "@aws-sdk/credential-provider-web-identity": "3.587.0", - "@aws-sdk/types": "3.577.0", - "@smithy/credential-provider-imds": "^3.1.0", - "@smithy/property-provider": "^3.1.0", - "@smithy/shared-ini-file-loader": "^3.1.0", - "@smithy/types": "^3.0.0", + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.621.0.tgz", + "integrity": "sha512-0EWVnSc+JQn5HLnF5Xv405M8n4zfdx9gyGdpnCmAmFqEDHA8LmBdxJdpUk1Ovp/I5oPANhjojxabIW5f1uU0RA==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.621.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.621.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.590.0" + "@aws-sdk/client-sts": "^3.621.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.590.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.590.0.tgz", - "integrity": "sha512-Ky38mNFoXobGrDQ11P3dU1e+q1nRJ7eZl8l15KUpvZCe/hOudbxQi/epQrCazD/gRYV2fTyczdLlZzB5ZZ8DhQ==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.587.0", - "@aws-sdk/credential-provider-http": "3.587.0", - "@aws-sdk/credential-provider-ini": "3.590.0", - "@aws-sdk/credential-provider-process": "3.587.0", - "@aws-sdk/credential-provider-sso": "3.590.0", - "@aws-sdk/credential-provider-web-identity": "3.587.0", - "@aws-sdk/types": "3.577.0", - "@smithy/credential-provider-imds": "^3.1.0", - "@smithy/property-provider": "^3.1.0", - "@smithy/shared-ini-file-loader": "^3.1.0", - "@smithy/types": "^3.0.0", + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.621.0.tgz", + "integrity": "sha512-4JqpccUgz5Snanpt2+53hbOBbJQrSFq7E1sAAbgY6BKVQUsW5qyXqnjvSF32kDeKa5JpBl3bBWLZl04IadcPHw==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.621.0", + "@aws-sdk/credential-provider-ini": "3.621.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.621.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -572,14 +789,26 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.587.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.587.0.tgz", - "integrity": "sha512-V4xT3iCqkF8uL6QC4gqBJg/2asd/damswP1h9HCfqTllmPWzImS+8WD3VjgTLw5b0KbTy+ZdUhKc0wDnyzkzxg==", + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", + "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", "dependencies": { - "@aws-sdk/types": "3.577.0", - "@smithy/property-provider": "^3.1.0", - "@smithy/shared-ini-file-loader": "^3.1.0", - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -587,16 +816,28 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.590.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.590.0.tgz", - "integrity": "sha512-v+0j/I+je9okfwXsgmLppmwIE+TuMp5WqLz7r7PHz9KjzLyKaKTDvfllFD+8oPpBqnmOWiJ9qTGPkrfhB7a/fQ==", + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.621.0.tgz", + "integrity": "sha512-Kza0jcFeA/GEL6xJlzR2KFf1PfZKMFnxfGzJzl5yN7EjoGdMijl34KaRyVnfRjnCWcsUpBWKNIDk9WZVMY9yiw==", + "dependencies": { + "@aws-sdk/client-sso": "3.621.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", "dependencies": { - "@aws-sdk/client-sso": "3.590.0", - "@aws-sdk/token-providers": "3.587.0", - "@aws-sdk/types": "3.577.0", - "@smithy/property-provider": "^3.1.0", - "@smithy/shared-ini-file-loader": "^3.1.0", - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -604,32 +845,73 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.587.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.587.0.tgz", - "integrity": "sha512-XqIx/I2PG7kyuw3WjAP9wKlxy8IvFJwB8asOFT1xPFoVfZYKIogjG9oLP5YiRtfvDkWIztHmg5MlVv3HdJDGRw==", + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", + "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", "dependencies": { - "@aws-sdk/types": "3.577.0", - "@smithy/property-provider": "^3.1.0", - "@smithy/types": "^3.0.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/lib-storage": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.621.0.tgz", + "integrity": "sha512-J4fwwmg2pH+vUsSbGO1kEIbAIv5TqDynrhOy48nIv8U5TNUWP29T+ZLs9+arQDla7bDJmvtB5f3iWHjI775ABQ==", + "dependencies": { + "@smithy/abort-controller": "^3.1.1", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/smithy-client": "^3.1.11", + "buffer": "5.6.0", + "events": "3.3.0", + "stream-browserify": "3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.587.0" + "@aws-sdk/client-s3": "^3.621.0" + } + }, + "node_modules/@aws-sdk/lib-storage/node_modules/buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.587.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.587.0.tgz", - "integrity": "sha512-HkFXLPl8pr6BH/Q0JpOESqEKL0ZK3sk7aSZ1S6GE4RXET7H5R94THULXqQFZzD48gZcyFooO/yNKZTqrZFaWKg==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.620.0.tgz", + "integrity": "sha512-eGLL0W6L3HDb3OACyetZYOWpHJ+gLo0TehQKeQyy2G8vTYXqNTeqYhuI6up9HVjBzU9eQiULVQETmgQs7TFaRg==", "dependencies": { - "@aws-sdk/types": "3.577.0", + "@aws-sdk/types": "3.609.0", "@aws-sdk/util-arn-parser": "3.568.0", - "@smithy/node-config-provider": "^3.1.0", - "@smithy/protocol-http": "^4.0.0", - "@smithy/types": "^3.0.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", "@smithy/util-config-provider": "^3.0.0", "tslib": "^2.6.2" }, @@ -637,14 +919,38 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.577.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.577.0.tgz", - "integrity": "sha512-6dPp8Tv4F0of4un5IAyG6q++GrRrNQQ4P2NAMB1W0VO4JoEu1C8GievbbDLi88TFIFmtKpnHB0ODCzwnoe8JsA==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.620.0.tgz", + "integrity": "sha512-QXeRFMLfyQ31nAHLbiTLtk0oHzG9QLMaof5jIfqcUwnOkO8YnQdeqzakrg1Alpy/VQ7aqzIi8qypkBe2KXZz0A==", "dependencies": { - "@aws-sdk/types": "3.577.0", - "@smithy/protocol-http": "^4.0.0", - "@smithy/types": "^3.0.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -652,16 +958,16 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.587.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.587.0.tgz", - "integrity": "sha512-URMwp/budDvKhIvZ4a6zIBfFTun/iDlPWXqsGKYjEtHt8jz27OSjCZtDtIeqW4WTBdKL8KZgQcl+DdaE5M1qiQ==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.620.0.tgz", + "integrity": "sha512-ftz+NW7qka2sVuwnnO1IzBku5ccP+s5qZGeRTPgrKB7OzRW85gthvIo1vQR2w+OwHFk7WJbbhhWwbCbktnP4UA==", "dependencies": { - "@aws-crypto/crc32": "3.0.0", - "@aws-crypto/crc32c": "3.0.0", - "@aws-sdk/types": "3.577.0", + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-sdk/types": "3.609.0", "@smithy/is-array-buffer": "^3.0.0", - "@smithy/protocol-http": "^4.0.0", - "@smithy/types": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -669,14 +975,38 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.577.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.577.0.tgz", - "integrity": "sha512-9ca5MJz455CODIVXs0/sWmJm7t3QO4EUa1zf8pE8grLpzf0J94bz/skDWm37Pli13T3WaAQBHCTiH2gUVfCsWg==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", + "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", "dependencies": { - "@aws-sdk/types": "3.577.0", - "@smithy/protocol-http": "^4.0.0", - "@smithy/types": "^3.0.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -684,12 +1014,24 @@ } }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.577.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.577.0.tgz", - "integrity": "sha512-DKPTD2D2s+t2QUo/IXYtVa/6Un8GZ+phSTBkyBNx2kfZz4Kwavhl/JJzSqTV3GfCXkVdFu7CrjoX7BZ6qWeTUA==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.609.0.tgz", + "integrity": "sha512-xzsdoTkszGVqGVPjUmgoP7TORiByLueMHieI1fhQL888WPdqctwAx3ES6d/bA9Q/i8jnc6hs+Fjhy8UvBTkE9A==", "dependencies": { - "@aws-sdk/types": "3.577.0", - "@smithy/types": "^3.0.0", + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -697,12 +1039,24 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.577.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.577.0.tgz", - "integrity": "sha512-aPFGpGjTZcJYk+24bg7jT4XdIp42mFXSuPt49lw5KygefLyJM/sB0bKKqPYYivW0rcuZ9brQ58eZUNthrzYAvg==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", + "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", "dependencies": { - "@aws-sdk/types": "3.577.0", - "@smithy/types": "^3.0.0", + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -710,13 +1064,25 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.577.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.577.0.tgz", - "integrity": "sha512-pn3ZVEd2iobKJlR3H+bDilHjgRnNrQ6HMmK9ZzZw89Ckn3Dcbv48xOv4RJvu0aU8SDLl/SNCxppKjeLDTPGBNA==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", + "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", "dependencies": { - "@aws-sdk/types": "3.577.0", - "@smithy/protocol-http": "^4.0.0", - "@smithy/types": "^3.0.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -743,16 +1109,46 @@ } }, "node_modules/@aws-sdk/middleware-signing": { - "version": "3.587.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.587.0.tgz", - "integrity": "sha512-tiZaTDj4RvhXGRAlncFn7CSEfL3iNPO67WSaxAq+Ls5j1VgczPhu5262cWONNoMgth3nXR1hhLC4ITSl/a6AzA==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.620.0.tgz", + "integrity": "sha512-gxI7rubiaanUXaLfJ4NybERa9MGPNg2Ycl/OqANsozrBnR3Pw8vqy3EuVImQOyn2pJ2IFvl8ZPoSMHf4pX56FQ==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-signing/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", "dependencies": { - "@aws-sdk/types": "3.577.0", - "@smithy/property-provider": "^3.1.0", - "@smithy/protocol-http": "^4.0.0", - "@smithy/signature-v4": "^3.0.0", - "@smithy/types": "^3.0.0", - "@smithy/util-middleware": "^3.0.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/signature-v4": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", + "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { @@ -760,12 +1156,24 @@ } }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.577.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.577.0.tgz", - "integrity": "sha512-i2BPJR+rp8xmRVIGc0h1kDRFcM2J9GnClqqpc+NLSjmYadlcg4mPklisz9HzwFVcRPJ5XcGf3U4BYs5G8+iTyg==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.609.0.tgz", + "integrity": "sha512-GZSD1s7+JswWOTamVap79QiDaIV7byJFssBW68GYjyRS5EBjNfwA/8s+6uE6g39R3ojyTbYOmvcANoZEhSULXg==", "dependencies": { - "@aws-sdk/types": "3.577.0", - "@smithy/types": "^3.0.0", + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -773,14 +1181,26 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.587.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.587.0.tgz", - "integrity": "sha512-SyDomN+IOrygLucziG7/nOHkjUXES5oH5T7p8AboO8oakMQJdnudNXiYWTicQWO52R51U6CR27rcMPTGeMedYA==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.620.0.tgz", + "integrity": "sha512-bvS6etn+KsuL32ubY5D3xNof1qkenpbJXf/ugGXbg0n98DvDFQ/F+SMLxHgbnER5dsKYchNnhmtI6/FC3HFu/A==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", "dependencies": { - "@aws-sdk/types": "3.577.0", - "@aws-sdk/util-endpoints": "3.587.0", - "@smithy/protocol-http": "^4.0.0", - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -788,15 +1208,27 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.587.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.587.0.tgz", - "integrity": "sha512-93I7IPZtulZQoRK+O20IJ4a1syWwYPzoO2gc3v+/GNZflZPV3QJXuVbIm0pxBsu0n/mzKGUKqSOLPIaN098HcQ==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", + "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", "dependencies": { - "@aws-sdk/types": "3.577.0", - "@smithy/node-config-provider": "^3.1.0", - "@smithy/types": "^3.0.0", + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -838,21 +1270,33 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.587.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.587.0.tgz", - "integrity": "sha512-ULqhbnLy1hmJNRcukANBWJmum3BbjXnurLPSFXoGdV0llXYlG55SzIla2VYqdveQEEjmsBuTZdFvXAtNpmS5Zg==", - "dependencies": { - "@aws-sdk/types": "3.577.0", - "@smithy/property-provider": "^3.1.0", - "@smithy/shared-ini-file-loader": "^3.1.0", - "@smithy/types": "^3.0.0", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", + "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.587.0" + "@aws-sdk/client-sso-oidc": "^3.614.0" + } + }, + "node_modules/@aws-sdk/token-providers/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/types": { @@ -879,13 +1323,25 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.587.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.587.0.tgz", - "integrity": "sha512-8I1HG6Em8wQWqKcRW6m358mqebRVNpL8XrrEoT4In7xqkKkmYtHRNVYP6lcmiQh5pZ/c/FXu8dSchuFIWyEtqQ==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.614.0.tgz", + "integrity": "sha512-wK2cdrXHH4oz4IomV/yrGkftU9A+ITB6nFL+rxxyO78is2ifHJpFdV4aqk4LSkXYPi6CXWNru/Dqc7yiKXgJPw==", "dependencies": { - "@aws-sdk/types": "3.577.0", - "@smithy/types": "^3.0.0", - "@smithy/util-endpoints": "^2.0.1", + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -918,24 +1374,36 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.577.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.577.0.tgz", - "integrity": "sha512-zEAzHgR6HWpZOH7xFgeJLc6/CzMcx4nxeQolZxVZoB5pPaJd3CjyRhZN0xXeZB0XIRCWmb4yJBgyiugXLNMkLA==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", + "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", "dependencies": { - "@aws-sdk/types": "3.577.0", - "@smithy/types": "^3.0.0", + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, + "node_modules/@aws-sdk/util-user-agent-browser/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.587.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.587.0.tgz", - "integrity": "sha512-Pnl+DUe/bvnbEEDHP3iVJrOtE3HbFJBPgsD6vJ+ml/+IYk1Eq49jEG+EHZdNTPz3SDG0kbp2+7u41MKYJHR/iQ==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", + "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", "dependencies": { - "@aws-sdk/types": "3.577.0", - "@smithy/node-config-provider": "^3.1.0", - "@smithy/types": "^3.0.0", + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -950,20 +1418,24 @@ } } }, - "node_modules/@aws-sdk/util-utf8-browser": { - "version": "3.259.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", - "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", + "node_modules/@aws-sdk/util-user-agent-node/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", "dependencies": { - "tslib": "^2.3.1" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.575.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.575.0.tgz", - "integrity": "sha512-cWgAwmbFYNCFzPwxL705+lWps0F3ZvOckufd2KKoEZUmtpVw9/txUXNrPySUXSmRTSRhoatIMABNfStWR043bQ==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.609.0.tgz", + "integrity": "sha512-l9XxNcA4HX98rwCC2/KoiWcmEiRfZe4G+mYwDbCFT87JIMj6GBhLDkAzr/W8KAaA2IDr8Vc6J8fZPgVulxxfMA==", "dependencies": { - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -1563,15 +2035,104 @@ "node": ">=12.22" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "dev": true + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", @@ -1779,6 +2340,15 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -1824,11 +2394,11 @@ "dev": true }, "node_modules/@smithy/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-p6GlFGBt9K4MYLu72YuJ523NVR4A8oHlC5M2JO6OmQqN8kAc/uh1JqLE+FizTokrSJGg0CSvC+BrsmGzKtsZKA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", "dependencies": { - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -1853,14 +2423,14 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.1.tgz", - "integrity": "sha512-hbkYJc20SBDz2qqLzttjI/EqXemtmWk0ooRznLsiXp3066KQRTvuKHa7U4jCZCJq6Dozqvy0R1/vNESC9inPJg==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.5.tgz", + "integrity": "sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==", "dependencies": { - "@smithy/node-config-provider": "^3.1.0", - "@smithy/types": "^3.0.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", "tslib": "^2.6.2" }, "engines": { @@ -1868,17 +2438,17 @@ } }, "node_modules/@smithy/core": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.2.0.tgz", - "integrity": "sha512-ygLZSSKgt9bR8HAxR9mK+U5obvAJBr6zlQuhN5soYWx/amjDoQN4dTkydTypgKe6rIbUjTILyLU+W5XFwXr4kg==", - "dependencies": { - "@smithy/middleware-endpoint": "^3.0.1", - "@smithy/middleware-retry": "^3.0.3", - "@smithy/middleware-serde": "^3.0.0", - "@smithy/protocol-http": "^4.0.0", - "@smithy/smithy-client": "^3.1.1", - "@smithy/types": "^3.0.0", - "@smithy/util-middleware": "^3.0.0", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.3.1.tgz", + "integrity": "sha512-BC7VMXx/1BCmRPCVzzn4HGWAtsrb7/0758EtwOGFJQrlSwJBEjCcDLNZLFoL/68JexYa2s+KmgL/UfmXdG6v1w==", + "dependencies": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.13", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", "tslib": "^2.6.2" }, "engines": { @@ -1886,14 +2456,14 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.1.0.tgz", - "integrity": "sha512-q4A4d38v8pYYmseu/jTS3Z5I3zXlEOe5Obi+EJreVKgSVyWUHOd7/yaVCinC60QG4MRyCs98tcxBH1IMC0bu7Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.0.tgz", + "integrity": "sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==", "dependencies": { - "@smithy/node-config-provider": "^3.1.0", - "@smithy/property-provider": "^3.1.0", - "@smithy/types": "^3.0.0", - "@smithy/url-parser": "^3.0.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", "tslib": "^2.6.2" }, "engines": { @@ -1901,23 +2471,23 @@ } }, "node_modules/@smithy/eventstream-codec": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.0.0.tgz", - "integrity": "sha512-PUtyEA0Oik50SaEFCZ0WPVtF9tz/teze2fDptW6WRXl+RrEenH8UbEjudOz8iakiMl3lE3lCVqYf2Y+znL8QFQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.2.tgz", + "integrity": "sha512-0mBcu49JWt4MXhrhRAlxASNy0IjDRFU+aWNDRal9OtUJvJNiwDuyKMUONSOjLjSCeGwZaE0wOErdqULer8r7yw==", "dependencies": { - "@aws-crypto/crc32": "3.0.0", - "@smithy/types": "^3.0.0", + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^3.3.0", "@smithy/util-hex-encoding": "^3.0.0", "tslib": "^2.6.2" } }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.0.tgz", - "integrity": "sha512-NB7AFiPN4NxP/YCAnrvYR18z2/ZsiHiF7VtG30gshO9GbFrIb1rC8ep4NGpJSWrz6P64uhPXeo4M0UsCLnZKqw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.5.tgz", + "integrity": "sha512-dEyiUYL/ekDfk+2Ra4GxV+xNnFoCmk1nuIXg+fMChFTrM2uI/1r9AdiTYzPqgb72yIv/NtAj6C3dG//1wwgakQ==", "dependencies": { - "@smithy/eventstream-serde-universal": "^3.0.0", - "@smithy/types": "^3.0.0", + "@smithy/eventstream-serde-universal": "^3.0.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -1925,11 +2495,11 @@ } }, "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.0.tgz", - "integrity": "sha512-RUQG3vQ3LX7peqqHAbmayhgrF5aTilPnazinaSGF1P0+tgM3vvIRWPHmlLIz2qFqB9LqFIxditxc8O2Z6psrRw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.3.tgz", + "integrity": "sha512-NVTYjOuYpGfrN/VbRQgn31x73KDLfCXCsFdad8DiIc3IcdxL+dYA9zEQPyOP7Fy2QL8CPy2WE4WCUD+ZsLNfaQ==", "dependencies": { - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -1937,12 +2507,12 @@ } }, "node_modules/@smithy/eventstream-serde-node": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.0.tgz", - "integrity": "sha512-baRPdMBDMBExZXIUAoPGm/hntixjt/VFpU6+VmCyiYJYzRHRxoaI1MN+5XE+hIS8AJ2GCHLMFEIOLzq9xx1EgQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.4.tgz", + "integrity": "sha512-mjlG0OzGAYuUpdUpflfb9zyLrBGgmQmrobNT8b42ZTsGv/J03+t24uhhtVEKG/b2jFtPIHF74Bq+VUtbzEKOKg==", "dependencies": { - "@smithy/eventstream-serde-universal": "^3.0.0", - "@smithy/types": "^3.0.0", + "@smithy/eventstream-serde-universal": "^3.0.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -1950,12 +2520,12 @@ } }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.0.tgz", - "integrity": "sha512-HNFfShmotWGeAoW4ujP8meV9BZavcpmerDbPIjkJbxKbN8RsUcpRQ/2OyIxWNxXNH2GWCAxuSB7ynmIGJlQ3Dw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.4.tgz", + "integrity": "sha512-Od9dv8zh3PgOD7Vj4T3HSuox16n0VG8jJIM2gvKASL6aCtcS8CfHZDWe1Ik3ZXW6xBouU+45Q5wgoliWDZiJ0A==", "dependencies": { - "@smithy/eventstream-codec": "^3.0.0", - "@smithy/types": "^3.0.0", + "@smithy/eventstream-codec": "^3.1.2", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -1963,34 +2533,34 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.0.1.tgz", - "integrity": "sha512-uaH74i5BDj+rBwoQaXioKpI0SHBJFtOVwzrCpxZxphOW0ki5jhj7dXvDMYM2IJem8TpdFvS2iC08sjOblfFGFg==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", "dependencies": { - "@smithy/protocol-http": "^4.0.0", - "@smithy/querystring-builder": "^3.0.0", - "@smithy/types": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" } }, "node_modules/@smithy/hash-blob-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.0.0.tgz", - "integrity": "sha512-/Wbpdg+bwJvW7lxR/zpWAc1/x/YkcqguuF2bAzkJrvXriZu1vm8r+PUdE4syiVwQg7PPR2dXpi3CLBb9qRDaVQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.2.tgz", + "integrity": "sha512-hAbfqN2UbISltakCC2TP0kx4LqXBttEv2MqSPE98gVuDFMf05lU+TpC41QtqGP3Ff5A3GwZMPfKnEy0VmEUpmg==", "dependencies": { "@smithy/chunked-blob-reader": "^3.0.0", "@smithy/chunked-blob-reader-native": "^3.0.0", - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, "node_modules/@smithy/hash-node": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.0.tgz", - "integrity": "sha512-84qXstNemP3XS5jcof0el6+bDfjzuvhJPQTEfro3lgtbCtKgzPm3MgiS6ehXVPjeQ5+JS0HqmTz8f/RYfzHVxw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", + "integrity": "sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==", "dependencies": { - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" @@ -2000,11 +2570,11 @@ } }, "node_modules/@smithy/hash-stream-node": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.0.0.tgz", - "integrity": "sha512-J0i7de+EgXDEGITD4fxzmMX8CyCNETTIRXlxjMiNUvvu76Xn3GJ31wQR85ynlPk2wI1lqoknAFJaD1fiNDlbIA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.2.tgz", + "integrity": "sha512-PBgDMeEdDzi6JxKwbfBtwQG9eT9cVwsf0dZzLXoJF4sHKHs5HEo/3lJWpn6jibfJwT34I1EBXpBnZE8AxAft6g==", "dependencies": { - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -2013,11 +2583,11 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.0.tgz", - "integrity": "sha512-F6wBBaEFgJzj0s4KUlliIGPmqXemwP6EavgvDqYwCH40O5Xr2iMHvS8todmGVZtuJCorBkXsYLyTu4PuizVq5g==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", + "integrity": "sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==", "dependencies": { - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, @@ -2033,22 +2603,22 @@ } }, "node_modules/@smithy/md5-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.0.tgz", - "integrity": "sha512-Tm0vrrVzjlD+6RCQTx7D3Ls58S3FUH1ZCtU1MIh/qQmaOo1H9lMN2as6CikcEwgattnA9SURSdoJJ27xMcEfMA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.3.tgz", + "integrity": "sha512-O/SAkGVwpWmelpj/8yDtsaVe6sINHLB1q8YE/+ZQbDxIw3SRLbTZuRaI10K12sVoENdnHqzPp5i3/H+BcZ3m3Q==", "dependencies": { - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "node_modules/@smithy/middleware-content-length": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.0.tgz", - "integrity": "sha512-3C4s4d/iGobgCtk2tnWW6+zSTOBg1PRAm2vtWZLdriwTroFbbWNSr3lcyzHdrQHnEXYCC5K52EbpfodaIUY8sg==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.5.tgz", + "integrity": "sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==", "dependencies": { - "@smithy/protocol-http": "^4.0.0", - "@smithy/types": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -2056,16 +2626,16 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.0.1.tgz", - "integrity": "sha512-lQ/UOdGD4KM5kLZiAl0q8Qy3dPbynvAXKAdXnYlrA1OpaUwr+neSsVokDZpY6ZVb5Yx8jnus29uv6XWpM9P4SQ==", - "dependencies": { - "@smithy/middleware-serde": "^3.0.0", - "@smithy/node-config-provider": "^3.1.0", - "@smithy/shared-ini-file-loader": "^3.1.0", - "@smithy/types": "^3.0.0", - "@smithy/url-parser": "^3.0.0", - "@smithy/util-middleware": "^3.0.0", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", + "dependencies": { + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", "tslib": "^2.6.2" }, "engines": { @@ -2073,17 +2643,17 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.3.tgz", - "integrity": "sha512-Wve1qzJb83VEU/6q+/I0cQdAkDnuzELC6IvIBwDzUEiGpKqXgX1v10FUuZGbRS6Ov/P+HHthcAoHOJZQvZNAkA==", - "dependencies": { - "@smithy/node-config-provider": "^3.1.0", - "@smithy/protocol-http": "^4.0.0", - "@smithy/service-error-classification": "^3.0.0", - "@smithy/smithy-client": "^3.1.1", - "@smithy/types": "^3.0.0", - "@smithy/util-middleware": "^3.0.0", - "@smithy/util-retry": "^3.0.0", + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.13.tgz", + "integrity": "sha512-zvCLfaRYCaUmjbF2yxShGZdolSHft7NNCTA28HVN9hKcEbOH+g5irr1X9s+in8EpambclGnevZY4A3lYpvDCFw==", + "dependencies": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/service-error-classification": "^3.0.3", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", "tslib": "^2.6.2", "uuid": "^9.0.1" }, @@ -2104,11 +2674,11 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.0.tgz", - "integrity": "sha512-I1vKG1foI+oPgG9r7IMY1S+xBnmAn1ISqployvqkwHoSb8VPsngHDTOgYGYBonuOKndaWRUGJZrKYYLB+Ane6w==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", "dependencies": { - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -2116,11 +2686,11 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.0.tgz", - "integrity": "sha512-+H0jmyfAyHRFXm6wunskuNAqtj7yfmwFB6Fp37enytp2q047/Od9xetEaUbluyImOlGnGpaVGaVfjwawSr+i6Q==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", "dependencies": { - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -2128,13 +2698,13 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.0.tgz", - "integrity": "sha512-ngfB8QItUfTFTfHMvKuc2g1W60V1urIgZHqD1JNFZC2tTWXahqf2XvKXqcBS7yZqR7GqkQQZy11y/lNOUWzq7Q==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz", + "integrity": "sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==", "dependencies": { - "@smithy/property-provider": "^3.1.0", - "@smithy/shared-ini-file-loader": "^3.1.0", - "@smithy/types": "^3.0.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -2142,14 +2712,14 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.0.0.tgz", - "integrity": "sha512-3trD4r7NOMygwLbUJo4eodyQuypAWr7uvPnebNJ9a70dQhVn+US8j/lCnvoJS6BXfZeF7PkkkI0DemVJw+n+eQ==", - "dependencies": { - "@smithy/abort-controller": "^3.0.0", - "@smithy/protocol-http": "^4.0.0", - "@smithy/querystring-builder": "^3.0.0", - "@smithy/types": "^3.0.0", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", + "dependencies": { + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -2157,11 +2727,11 @@ } }, "node_modules/@smithy/property-provider": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.0.tgz", - "integrity": "sha512-Tj3+oVhqdZgemjCiWjFlADfhvLF4C/uKDuKo7/tlEsRQ9+3emCreR2xndj970QSRSsiCEU8hZW3/8JQu+n5w4Q==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", + "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", "dependencies": { - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -2169,11 +2739,11 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.0.0.tgz", - "integrity": "sha512-qOQZOEI2XLWRWBO9AgIYuHuqjZ2csyr8/IlgFDHDNuIgLAMRx2Bl8ck5U5D6Vh9DPdoaVpuzwWMa0xcdL4O/AQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "dependencies": { - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -2181,11 +2751,11 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.0.tgz", - "integrity": "sha512-bW8Fi0NzyfkE0TmQphDXr1AmBDbK01cA4C1Z7ggwMAU5RDz5AAv/KmoRwzQAS0kxXNf/D2ALTEgwK0U2c4LtRg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", "dependencies": { - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "@smithy/util-uri-escape": "^3.0.0", "tslib": "^2.6.2" }, @@ -2194,11 +2764,11 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.0.tgz", - "integrity": "sha512-UzHwthk0UEccV4dHzPySnBy34AWw3V9lIqUTxmozQ+wPDAO9csCWMfOLe7V9A2agNYy7xE+Pb0S6K/J23JSzfQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", "dependencies": { - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -2206,22 +2776,22 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.0.tgz", - "integrity": "sha512-3BsBtOUt2Gsnc3X23ew+r2M71WwtpHfEDGhHYHSDg6q1t8FrWh15jT25DLajFV1H+PpxAJ6gqe9yYeRUsmSdFA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz", + "integrity": "sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==", "dependencies": { - "@smithy/types": "^3.0.0" + "@smithy/types": "^3.3.0" }, "engines": { "node": ">=16.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.0.tgz", - "integrity": "sha512-dAM7wSX0NR3qTNyGVN/nwwpEDzfV9T/3AN2eABExWmda5VqZKSsjlINqomO5hjQWGv+IIkoXfs3u2vGSNz8+Rg==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz", + "integrity": "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==", "dependencies": { - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -2246,15 +2816,15 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.1.1.tgz", - "integrity": "sha512-tj4Ku7MpzZR8cmVuPcSbrLFVxmptWktmJMwST/uIEq4sarabEdF8CbmQdYB7uJ/X51Qq2EYwnRsoS7hdR4B7rA==", - "dependencies": { - "@smithy/middleware-endpoint": "^3.0.1", - "@smithy/middleware-stack": "^3.0.0", - "@smithy/protocol-http": "^4.0.0", - "@smithy/types": "^3.0.0", - "@smithy/util-stream": "^3.0.1", + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.1.11.tgz", + "integrity": "sha512-l0BpyYkciNyMaS+PnFFz4aO5sBcXvGLoJd7mX9xrMBIm2nIQBVvYgp2ZpPDMzwjKCavsXu06iuCm0F6ZJZc6yQ==", + "dependencies": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", "tslib": "^2.6.2" }, "engines": { @@ -2262,9 +2832,9 @@ } }, "node_modules/@smithy/types": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.0.0.tgz", - "integrity": "sha512-VvWuQk2RKFuOr98gFhjca7fkBS+xLLURT8bUjk5XQoV0ZLm7WPwWPPY3/AwzTLuUBDeoKDCthfe1AsTUWaSEhw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "dependencies": { "tslib": "^2.6.2" }, @@ -2273,12 +2843,12 @@ } }, "node_modules/@smithy/url-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.0.tgz", - "integrity": "sha512-2XLazFgUu+YOGHtWihB3FSLAfCUajVfNBXGGYjOaVKjLAuAxx3pSBY3hBgLzIgB17haf59gOG3imKqTy8mcrjw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", "dependencies": { - "@smithy/querystring-parser": "^3.0.0", - "@smithy/types": "^3.0.0", + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, @@ -2338,13 +2908,13 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.3.tgz", - "integrity": "sha512-3DFON2bvXJAukJe+qFgPV/rorG7ZD3m4gjCXHD1V5z/tgKQp5MCTCLntrd686tX6tj8Uli3lefWXJudNg5WmCA==", + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.13.tgz", + "integrity": "sha512-ZIRSUsnnMRStOP6OKtW+gCSiVFkwnfQF2xtf32QKAbHR6ACjhbAybDvry+3L5qQYdh3H6+7yD/AiUE45n8mTTw==", "dependencies": { - "@smithy/property-provider": "^3.1.0", - "@smithy/smithy-client": "^3.1.1", - "@smithy/types": "^3.0.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", "bowser": "^2.11.0", "tslib": "^2.6.2" }, @@ -2353,16 +2923,16 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.3.tgz", - "integrity": "sha512-D0b8GJXecT00baoSQ3Iieu3k3mZ7GY8w1zmg8pdogYrGvWJeLcIclqk2gbkG4K0DaBGWrO6v6r20iwIFfDYrmA==", - "dependencies": { - "@smithy/config-resolver": "^3.0.1", - "@smithy/credential-provider-imds": "^3.1.0", - "@smithy/node-config-provider": "^3.1.0", - "@smithy/property-provider": "^3.1.0", - "@smithy/smithy-client": "^3.1.1", - "@smithy/types": "^3.0.0", + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.13.tgz", + "integrity": "sha512-voUa8TFJGfD+U12tlNNLCDlXibt9vRdNzRX45Onk/WxZe7TS+hTOZouEZRa7oARGicdgeXvt1A0W45qLGYdy+g==", + "dependencies": { + "@smithy/config-resolver": "^3.0.5", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.1.11", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -2370,12 +2940,12 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.1.tgz", - "integrity": "sha512-ZRT0VCOnKlVohfoABMc8lWeQo/JEFuPWctfNRXgTHbyOVssMOLYFUNWukxxiHRGVAhV+n3c0kPW+zUqckjVPEA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.5.tgz", + "integrity": "sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==", "dependencies": { - "@smithy/node-config-provider": "^3.1.0", - "@smithy/types": "^3.0.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -2394,11 +2964,11 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.0.tgz", - "integrity": "sha512-q5ITdOnV2pXHSVDnKWrwgSNTDBAMHLptFE07ua/5Ty5WJ11bvr0vk2a7agu7qRhrCFRQlno5u3CneU5EELK+DQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "dependencies": { - "@smithy/types": "^3.0.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -2406,12 +2976,12 @@ } }, "node_modules/@smithy/util-retry": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.0.tgz", - "integrity": "sha512-nK99bvJiziGv/UOKJlDvFF45F00WgPLKVIGUfAK+mDhzVN2hb/S33uW2Tlhg5PVBoqY7tDVqL0zmu4OxAHgo9g==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz", + "integrity": "sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==", "dependencies": { - "@smithy/service-error-classification": "^3.0.0", - "@smithy/types": "^3.0.0", + "@smithy/service-error-classification": "^3.0.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -2419,13 +2989,13 @@ } }, "node_modules/@smithy/util-stream": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.0.1.tgz", - "integrity": "sha512-7F7VNNhAsfMRA8I986YdOY5fE0/T1/ZjFF6OLsqkvQVNP3vZ/szYDfGCyphb7ioA09r32K/0qbSFfNFU68aSzA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", "dependencies": { - "@smithy/fetch-http-handler": "^3.0.1", - "@smithy/node-http-handler": "^3.0.0", - "@smithy/types": "^3.0.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", "@smithy/util-base64": "^3.0.0", "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-hex-encoding": "^3.0.0", @@ -2460,12 +3030,12 @@ } }, "node_modules/@smithy/util-waiter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.0.0.tgz", - "integrity": "sha512-+fEXJxGDLCoqRKVSmo0auGxaqbiCo+8oph+4auefYjaNxjOLKSY2MxVQfRzo65PaZv4fr+5lWg+au7vSuJJ/zw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.2.tgz", + "integrity": "sha512-4pP0EV3iTsexDx+8PPGAKCQpd/6hsQBaQhqWzU4hqKPHN5epPsxKbvUTIiYIHTxaKt6/kEaqPBpu/ufvfbrRzw==", "dependencies": { - "@smithy/abort-controller": "^3.0.0", - "@smithy/types": "^3.0.0", + "@smithy/abort-controller": "^3.1.1", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -2571,6 +3141,15 @@ "@types/node": "*" } }, + "node_modules/@types/archiver": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-6.0.2.tgz", + "integrity": "sha512-KmROQqbQzKGuaAbmK+ZcytkJ51+YqDa7NmbXjmtC5YBLSyQYo21YaUnQ3HbaPFKL1ooo6RQ6OPYPIDyxfpDDXw==", + "dev": true, + "dependencies": { + "@types/readdir-glob": "*" + } + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -2722,6 +3301,15 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, + "node_modules/@types/readdir-glob": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz", + "integrity": "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -3012,6 +3600,17 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3167,6 +3766,90 @@ "node": ">=8" } }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils/node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver/node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + }, "node_modules/archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", @@ -3298,11 +3981,41 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/bare-events": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", + "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", + "optional": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -3428,16 +4141,47 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" - }, - "bin": { - "browserslist": "cli.js" - }, + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">=8.0.0" } }, "node_modules/buffer-equal-constant-time": { @@ -3733,6 +4477,21 @@ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3836,6 +4595,29 @@ "node": ">=8.0.0" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -3846,7 +4628,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -4165,6 +4946,11 @@ "node": ">=4.6.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -4623,11 +5409,27 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/express": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", @@ -4730,6 +5532,11 @@ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -4764,9 +5571,9 @@ "dev": true }, "node_modules/fast-xml-parser": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.6.tgz", - "integrity": "sha512-M2SovcRxD4+vC493Uc2GZVcZaj66CCJhWurC4viynVSTvrpErCShNcDz1lAho6n9REQKvL/ll4A4/fw6Y9z8nw==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "funding": [ { "type": "github", @@ -5266,8 +6073,7 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/graphemer": { "version": "1.4.0", @@ -5460,6 +6266,25 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -5902,8 +6727,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/isstream": { "version": "0.1.2", @@ -6028,6 +6852,20 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jose": { "version": "4.15.5", "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz", @@ -6256,6 +7094,44 @@ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -6569,7 +7445,6 @@ "version": "9.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -6588,6 +7463,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -6955,7 +7838,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -7566,6 +8448,11 @@ "node": ">=8" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -7631,7 +8518,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -7641,6 +8527,26 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -7701,6 +8607,14 @@ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.4.tgz", "integrity": "sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==" }, + "node_modules/pg-cursor": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/pg-cursor/-/pg-cursor-2.11.0.tgz", + "integrity": "sha512-TLCOCtu+rqMarzjUi+/Ffc2DV5ZqO/27y5GqnK9Z3w51rWXMwC8FcO96Uf9/ORo5o+qRXEVJxM9Ts3K2K31MLg==", + "peerDependencies": { + "pg": "^8" + } + }, "node_modules/pg-int8": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", @@ -7731,6 +8645,17 @@ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.1.tgz", "integrity": "sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==" }, + "node_modules/pg-query-stream": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/pg-query-stream/-/pg-query-stream-4.6.0.tgz", + "integrity": "sha512-sg2Hewe6ge6osEY07zGu7Z8djrsQBvyiTy5ZjQffoSatEgnNNVsV3EWDm9Px/8R9oaAL1YnfnP8AXPMmfzujZg==", + "dependencies": { + "pg-cursor": "^2.11.0" + }, + "peerDependencies": { + "pg": "^8" + } + }, "node_modules/pg-types": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.2.tgz", @@ -8019,6 +8944,14 @@ } } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -8145,6 +9078,11 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -8235,6 +9173,40 @@ "node": ">=4" } }, + "node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -8644,7 +9616,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -8656,7 +9627,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -8886,6 +9856,28 @@ "node": ">= 0.8" } }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/stream-browserify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -8894,6 +9886,27 @@ "node": ">=10.0.0" } }, + "node_modules/streamx": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.0.tgz", + "integrity": "sha512-ZGd1LhDeGFucr1CUCTBOS58ZhEendd0ttpGT3usTvosS4ntIwKN9LJFp+OeCSprsCPL14BXVRZlHGRY1V9PVzQ==", + "dependencies": { + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -8907,6 +9920,20 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.padend": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", @@ -8985,6 +10012,18 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -9053,6 +10092,16 @@ "express": ">=4.0.0 || >=5.0.0-beta" } }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/tarn": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", @@ -9117,6 +10166,14 @@ "node": "*" } }, + "node_modules/text-decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.1.tgz", + "integrity": "sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -9648,7 +10705,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -9763,14 +10819,6 @@ "node": ">= 6" } }, - "node_modules/winston-transport/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/winston/node_modules/@colors/colors": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", @@ -9792,14 +10840,6 @@ "node": ">= 6" } }, - "node_modules/winston/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/workerpool": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", @@ -9819,6 +10859,23 @@ "node": ">=8" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -10018,6 +11075,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/zod": { "version": "3.23.8", "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", diff --git a/api/package.json b/api/package.json index fe2fd2dcb2..463bee9848 100644 --- a/api/package.json +++ b/api/package.json @@ -27,6 +27,7 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.583.0", + "@aws-sdk/lib-storage": "^3.621.0", "@aws-sdk/s3-request-presigner": "^3.583.0", "@turf/bbox": "^6.5.0", "@turf/circle": "^6.5.0", @@ -34,6 +35,7 @@ "@turf/meta": "^6.5.0", "adm-zip": "0.5.12", "ajv": "^8.12.0", + "archiver": "^7.0.1", "axios": "^1.6.7", "clamscan": "^2.2.1", "dayjs": "^1.11.10", @@ -55,6 +57,7 @@ "mime": "^3.0.0", "multer": "^1.4.5-lts.1", "pg": "^8.7.1", + "pg-query-stream": "^4.5.5", "qs": "^6.10.1", "sql-template-strings": "^2.2.2", "swagger-ui-express": "^4.3.0", @@ -69,6 +72,7 @@ "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.1", "@types/adm-zip": "^0.4.34", + "@types/archiver": "^6.0.2", "@types/chai": "^4.3.14", "@types/clamscan": "^2.0.8", "@types/express": "^4.17.13", diff --git a/api/src/__mocks__/db.ts b/api/src/__mocks__/db.ts index 8b528995cc..5b5f9d4006 100644 --- a/api/src/__mocks__/db.ts +++ b/api/src/__mocks__/db.ts @@ -1,5 +1,5 @@ import { Request, Response } from 'express'; -import { QueryResult } from 'pg'; +import { PoolClient, QueryResult } from 'pg'; import sinon from 'sinon'; import * as db from '../database/db'; import { IDBConnection } from '../database/db'; @@ -35,6 +35,9 @@ export const getMockDBConnection = (config?: Partial): IDBConnect systemUserIdentifier: () => { return null as unknown as string; }, + getClient: async () => { + return null as unknown as PoolClient; + }, open: async () => { // do nothing }, @@ -47,9 +50,6 @@ export const getMockDBConnection = (config?: Partial): IDBConnect rollback: async () => { // do nothing }, - query: async () => { - return undefined as unknown as QueryResult; - }, sql: async () => { return undefined as unknown as QueryResult; }, diff --git a/api/src/database/db.test.ts b/api/src/database/db.test.ts index 0dcb4dd1f7..8121b65403 100644 --- a/api/src/database/db.test.ts +++ b/api/src/database/db.test.ts @@ -266,52 +266,6 @@ describe('db', () => { }); }); - describe('query', () => { - describe('when a connection is open', () => { - it('sends a query with values', async () => { - sinonSandbox.stub(db, 'getDBPool').returns(mockPool as unknown as pg.Pool); - - await connection.open(); - - await connection.query('sql query', ['value1', 'value2']); - - expect(queryStub).to.have.been.calledWith('sql query', ['value1', 'value2']); - }); - - it('sends a query with empty values', async () => { - sinonSandbox.stub(db, 'getDBPool').returns(mockPool as unknown as pg.Pool); - - await connection.open(); - - await connection.query('sql query'); - - expect(queryStub).to.have.been.calledWith('sql query', []); - }); - }); - - describe('when a connection is not open', () => { - it('throws an error', async () => { - sinonSandbox.stub(db, 'getDBPool').returns(mockPool as unknown as pg.Pool); - - let expectedError: ApiExecuteSQLError; - try { - await connection.query('sql query'); - - expect.fail('Expected an error to be thrown'); - } catch (error) { - expectedError = error as ApiExecuteSQLError; - } - - expect(expectedError.message).to.equal('Failed to execute SQL'); - - expect(expectedError.errors?.length).to.be.greaterThan(0); - expectedError.errors?.forEach((item) => { - expect(item).to.be.eql({ name: 'Error', message: 'DBConnection is not open' }); - }); - }); - }); - }); - describe('sql', () => { describe('when a connection is open', () => { it('sends a sql statement', async () => { diff --git a/api/src/database/db.ts b/api/src/database/db.ts index cdc3c1a8b5..a213567c86 100644 --- a/api/src/database/db.ts +++ b/api/src/database/db.ts @@ -102,6 +102,15 @@ export const getDBPool = function (): pg.Pool | undefined { }; export interface IDBConnection { + /** + * Get a new pg client. + * + * Note: This is not the same client that is initialized when calling `.open()`, and must be released manually by + * calling `client.release()`. + * + * @memberof IDBConnection + */ + getClient: () => Promise; /** * Opens a new connection, begins a transaction, and sets the user context. * @@ -132,17 +141,6 @@ export interface IDBConnection { * @memberof IDBConnection */ rollback: () => Promise; - /** - * Performs a query against this connection, returning the results. - * - * @param {string} text SQL text - * @param {any[]} [values] SQL values array (optional) - * @return {*} {(Promise>)} - * @throws If the connection is not open. - * @deprecated Prefer using `.sql` (pass entire statement object) or `.knex` (pass knex query builder object) - * @memberof IDBConnection - */ - query: (text: string, values?: any[]) => Promise>; /** * Performs a query against this connection, returning the results. * @@ -236,6 +234,21 @@ export const getDBConnection = function (keycloakToken?: KeycloakUserInformation let _systemUserId: number | null = null; const _token = keycloakToken; + /** + * Get a new pg client. + * + * @return {*} + */ + const _getClient = async () => { + const pool = getDBPool(); + + if (!pool) { + throw Error('DBPool is not initialized'); + } + + return pool.connect(); + }; + /** * Opens a new connection, begins a transaction, and sets the user context. * @@ -553,8 +566,8 @@ export const getDBConnection = function (keycloakToken?: KeycloakUserInformation }; return { + getClient: asyncErrorWrapper(_getClient), open: asyncErrorWrapper(_open), - query: asyncErrorWrapper(_query), sql: asyncErrorWrapper(_sql), knex: asyncErrorWrapper(_knex), release: syncErrorWrapper(_release), diff --git a/api/src/models/animal-view.ts b/api/src/models/animal-view.ts index 96af516b35..b43acb721b 100644 --- a/api/src/models/animal-view.ts +++ b/api/src/models/animal-view.ts @@ -1,6 +1,37 @@ export interface IAnimalAdvancedFilters { + /** + * Filter results by keyword. + * + * @type {string} + * @memberof IAnimalAdvancedFilters + */ keyword?: string; + /** + * Filter results by ITIS TSNs. + * + * @type {number[]} + * @memberof IAnimalAdvancedFilters + */ itis_tsns?: number[]; + /** + * Filter results by ITIS TSN. + * + * @type {number} + * @memberof IAnimalAdvancedFilters + */ itis_tsn?: number; + /** + * Filter results by system user id (not necessarily the user making the request). + * + * @type {number} + * @memberof IAnimalAdvancedFilters + */ system_user_id?: number; + /** + * Filter results by survey ids + * + * @type {number[]} + * @memberof IAnimalAdvancedFilters + */ + survey_ids?: number[]; } diff --git a/api/src/models/telemetry-view.ts b/api/src/models/telemetry-view.ts index f04c9dba5d..67a693dff7 100644 --- a/api/src/models/telemetry-view.ts +++ b/api/src/models/telemetry-view.ts @@ -1,6 +1,37 @@ export interface IAllTelemetryAdvancedFilters { + /** + * Filter results by keyword. + * + * @type {string} + * @memberof IAnimalAdvancedFilters + */ keyword?: string; + /** + * Filter results by ITIS TSNs. + * + * @type {number[]} + * @memberof IAnimalAdvancedFilters + */ itis_tsns?: number[]; + /** + * Filter results by ITIS TSN. + * + * @type {number} + * @memberof IAnimalAdvancedFilters + */ itis_tsn?: number; + /** + * Filter results by system user id (not necessarily the user making the request). + * + * @type {number} + * @memberof IAnimalAdvancedFilters + */ system_user_id?: number; + /** + * Filter results by survey ids + * + * @type {number[]} + * @memberof IAnimalAdvancedFilters + */ + survey_ids?: number[]; } diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/export/index.test.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/export/index.test.ts new file mode 100644 index 0000000000..56e42fd093 --- /dev/null +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/export/index.test.ts @@ -0,0 +1,98 @@ +import chai, { expect } from 'chai'; +import { describe } from 'mocha'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import { exportData } from '.'; +import { SYSTEM_ROLE } from '../../../../../../constants/roles'; +import * as db from '../../../../../../database/db'; +import { HTTPError } from '../../../../../../errors/http-error'; +import { SystemUser } from '../../../../../../repositories/user-repository'; +import { ExportService } from '../../../../../../services/export-services/export-service'; +import { getMockDBConnection, getRequestHandlerMocks } from '../../../../../../__mocks__/db'; + +chai.use(sinonChai); + +describe('exportData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('catches and re-throws error', async () => { + // Setup + const mockDBConnection = getMockDBConnection({ release: sinon.stub() }); + sinon.stub(db, 'getDBConnection').returns(mockDBConnection); + + sinon.stub(ExportService.prototype, 'export').rejects(new Error('a test error')); + + const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); + + mockReq.system_user = { + system_user_id: 1, + role_names: [SYSTEM_ROLE.PROJECT_CREATOR] + } as SystemUser; + + mockReq.params = { + projectId: '1', + surveyId: '2' + }; + + mockReq.body = { + methodTechniqueIds: [1, 2, 3] + }; + + const requestHandler = exportData(); + + try { + // Execute + await requestHandler(mockReq, mockRes, mockNext); + + expect.fail('Expected an error to be thrown'); + } catch (actualError) { + // Assert + expect(mockDBConnection.release).to.have.been.calledOnce; + + expect((actualError as HTTPError).message).to.equal('a test error'); + } + }); + + it('returns the s3 signed url for the export data file', async () => { + // Setup + const mockDBConnection = getMockDBConnection({ release: sinon.stub() }); + sinon.stub(db, 'getDBConnection').returns(mockDBConnection); + + sinon.stub(ExportService.prototype, 'export').resolves(['signed-url-for:path/to/file/key']); + + const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); + + mockReq.system_user = { + system_user_id: 1, + role_names: [SYSTEM_ROLE.PROJECT_CREATOR] + } as SystemUser; + + mockReq.params = { + projectId: '1', + surveyId: '2' + }; + + mockReq.body = { + config: { + metadata: true, + sampling_data: false, + observation_data: true, + telemetry_data: true, + animal_data: false, + artifacts: false + } + }; + + // Execute + const requestHandler = exportData(); + + await requestHandler(mockReq, mockRes, mockNext); + + // Assert + expect(mockRes.jsonValue).to.eql({ presignedS3Urls: ['signed-url-for:path/to/file/key'] }); + + expect(mockDBConnection.release).to.have.been.calledOnce; + }); +}); diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/export/index.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/export/index.ts new file mode 100644 index 0000000000..b417551e18 --- /dev/null +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/export/index.ts @@ -0,0 +1,184 @@ +import { RequestHandler } from 'express'; +import { Operation } from 'express-openapi'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../constants/roles'; +import { getDBConnection } from '../../../../../../database/db'; +import { authorizeRequestHandler, userHasValidRole } from '../../../../../../request-handlers/security/authorization'; +import { ExportService } from '../../../../../../services/export-services/export-service'; +import { + ExportSurveyConfig, + ExportSurveyStrategy +} from '../../../../../../services/export-services/survey/export-survey-strategy'; +import { generateS3ExportKey } from '../../../../../../utils/file-utils'; +import { getLogger } from '../../../../../../utils/logger'; +import { getSystemUserFromRequest } from '../../../../../../utils/request'; + +const defaultLog = getLogger('/api/project/{projectId}/survey/{surveyId}/export/index.ts'); + +export const POST: Operation = [ + authorizeRequestHandler((req) => { + return { + or: [ + { + validSystemRoles: [SYSTEM_ROLE.SYSTEM_ADMIN, SYSTEM_ROLE.DATA_ADMINISTRATOR], + discriminator: 'SystemRole' + }, + { + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], + surveyId: Number(req.params.surveyId), + discriminator: 'ProjectPermission' + } + ] + }; + }), + exportData() +]; + +POST.apiDoc = { + description: 'Export SIMS data.', + tags: ['export'], + security: [ + { + Bearer: [] + } + ], + parameters: [ + { + in: 'path', + name: 'projectId', + schema: { + type: 'integer', + minimum: 1 + }, + required: true + }, + { + in: 'path', + name: 'surveyId', + schema: { + type: 'integer', + minimum: 1 + }, + required: true + } + ], + requestBody: { + description: 'Export SIMS data request object.', + content: { + 'application/json': { + schema: { + type: 'object', + additionalProperties: false, + required: ['config'], + properties: { + config: { + type: 'object', + additionalProperties: false, + required: ['metadata', 'sampling_data', 'observation_data', 'telemetry_data', 'animal_data', 'artifacts'], + description: 'Configure which data to include in the export.', + properties: { + metadata: { + type: 'boolean' + }, + sampling_data: { + type: 'boolean' + }, + observation_data: { + type: 'boolean' + }, + telemetry_data: { + type: 'boolean' + }, + animal_data: { + type: 'boolean' + }, + artifacts: { + type: 'boolean' + } + } + } + } + } + } + } + }, + responses: { + 200: { + description: 'Response containing the signed url of the export file.', + content: { + 'text/plain': { + schema: { + type: 'object', + additionalProperties: false, + required: ['presignedS3Urls'], + properties: { + presignedS3Urls: { + type: 'array', + items: { + type: 'string' + } + } + } + } + } + } + }, + 400: { + $ref: '#/components/responses/400' + }, + 401: { + $ref: '#/components/responses/401' + }, + 403: { + $ref: '#/components/responses/403' + }, + 500: { + $ref: '#/components/responses/500' + }, + default: { + $ref: '#/components/responses/default' + } + } +}; + +/** + * Export survey information. + * + * @return {*} {RequestHandler} + */ +export function exportData(): RequestHandler { + return async (req, res) => { + const connection = getDBConnection(req['keycloak_token']); + + try { + const surveyId = Number(req.params.surveyId); + const config = req.body.config as ExportSurveyConfig; + + const systemUser = getSystemUserFromRequest(req); + const isUserAdmin = userHasValidRole( + [SYSTEM_ROLE.SYSTEM_ADMIN, SYSTEM_ROLE.DATA_ADMINISTRATOR], + systemUser.role_names + ); + + await connection.open(); + + const exportSurveyStrategy = new ExportSurveyStrategy({ surveyId, config, connection, isUserAdmin }); + + const exportService = new ExportService(connection); + + const response = await exportService.export({ + exportStrategies: [exportSurveyStrategy], + s3Key: generateS3ExportKey() + }); + + await connection.commit(); + + return res.status(200).json({ presignedS3Urls: response }); + } catch (error) { + defaultLog.error({ label: 'publishSurvey', message: 'error', error }); + await connection.rollback(); + throw error; + } finally { + connection.release(); + } + }; +} diff --git a/api/src/repositories/survey-critter-repository.ts b/api/src/repositories/survey-critter-repository.ts index e2f114dd1e..14bc670ec4 100644 --- a/api/src/repositories/survey-critter-repository.ts +++ b/api/src/repositories/survey-critter-repository.ts @@ -92,6 +92,10 @@ export class SurveyCritterRepository extends BaseRepository { }); } + if (filterFields?.survey_ids) { + query.whereIn('critter.survey_id', filterFields.survey_ids); + } + return query; } diff --git a/api/src/services/export-services/export-service.test.ts b/api/src/services/export-services/export-service.test.ts new file mode 100644 index 0000000000..7afd799c80 --- /dev/null +++ b/api/src/services/export-services/export-service.test.ts @@ -0,0 +1,120 @@ +import { CompleteMultipartUploadCommandOutput } from '@aws-sdk/client-s3'; +import chai, { expect } from 'chai'; +import { describe } from 'mocha'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import SQL from 'sql-template-strings'; +import { PassThrough } from 'stream'; +import { ApiError } from '../../errors/api-error'; +import * as file_utils from '../../utils/file-utils'; +import { getMockDBConnection } from '../../__mocks__/db'; +import { ExportService } from './export-service'; +import { ExportConfig, ExportStrategy } from './export-strategy'; +import * as export_utils from './export-utils'; + +chai.use(sinonChai); + +describe('ExportService', () => { + afterEach(() => { + sinon.restore(); + }); + + describe('export', () => { + it('throws an error if no export strategies are defined', async () => { + // Setup + const mockDBConnection = getMockDBConnection(); + + const exportConfig: ExportConfig = { + exportStrategies: [], + s3Key: 'path/to/file/key' + }; + + // Execute + const exportService = new ExportService(mockDBConnection); + + try { + await exportService.export(exportConfig); + + expect.fail('Expected an error to be thrown'); + } catch (error) { + // Assert + expect((error as ApiError).message).to.equal('No export strategies have been defined.'); + } + }); + + it('exports data', async () => { + // Setup + + // mock database client/connection + const mockClient = { + query: sinon.stub().resolves(), + release: sinon.stub().resolves() + }; + const mockDBConnection = getMockDBConnection({ + getClient: sinon.stub().resolves(mockClient) + }); + + // mock upload stream to s3 + const mockUploadResponse = {} as CompleteMultipartUploadCommandOutput; + sinon.stub(file_utils, 'uploadStreamToS3').resolves(mockUploadResponse); + + // mock archive stream + const archiveStream = export_utils.getArchiveStream(); + sinon.stub(archiveStream, 'pipe').returns(archiveStream); + sinon.stub(archiveStream, 'append').returns(archiveStream); + sinon.stub(archiveStream, 'finalize').resolves(); + sinon.stub(export_utils, 'getArchiveStream').returns(archiveStream); + + // mock s3 signed urls function + sinon.stub(ExportService.prototype, '_getAllSignedURLs').resolves(['signed-url-for:path/to/file/key']); + + // Create mock export strategies + const sqlExportStrategy: ExportStrategy = { + getExportStrategyConfig: sinon.stub().resolves({ + queries: [ + { + sql: SQL``, + fileName: 'file1.csv' + } + ] + }) + }; + + const mockStream = new PassThrough(); + const streamExportStrategy: ExportStrategy = { + getExportStrategyConfig: sinon.stub().resolves({ + streams: [ + { + stream: sinon.stub().returns(mockStream), + fileName: 'file2.csv' + } + ] + }) + }; + + const exportConfig: ExportConfig = { + exportStrategies: [sqlExportStrategy, streamExportStrategy], + s3Key: 'path/to/file/key' + }; + + // Execute + const exportService = new ExportService(mockDBConnection); + const signedUrls = await exportService.export(exportConfig); + + // Assert + expect(signedUrls.length).to.equal(1); + expect(signedUrls[0]).to.equal('signed-url-for:path/to/file/key'); + + // Get export strategy config + expect(sqlExportStrategy.getExportStrategyConfig).to.have.been.calledOnceWith(mockDBConnection); + expect(streamExportStrategy.getExportStrategyConfig).to.have.been.calledOnceWith(mockDBConnection); + // Append query strategy + expect(archiveStream.append).to.have.been.calledWith(sinon.match.any, { name: 'file1.csv' }); + // Append stream strategy + expect(archiveStream.append).to.have.been.calledWith(sinon.match.any, { name: 'file2.csv' }); + + // Cleanup + mockStream.destroy(); + }); + }); +}); diff --git a/api/src/services/export-services/export-service.ts b/api/src/services/export-services/export-service.ts new file mode 100644 index 0000000000..0f17eefcea --- /dev/null +++ b/api/src/services/export-services/export-service.ts @@ -0,0 +1,145 @@ +import { PassThrough } from 'stream'; +import { IDBConnection } from '../../database/db'; +import { ApiGeneralError } from '../../errors/api-error'; +import { getS3SignedURLs, uploadStreamToS3 } from '../../utils/file-utils'; +import { getLogger } from '../../utils/logger'; +import { DBService } from '../db-service'; +import { ExportConfig } from './export-strategy'; +import { + getArchiveStream, + getJsonStringifyTransformStream, + getQueryStream, + registerStreamErrorHandler +} from './export-utils'; + +const defaultLog = getLogger('/api/src/services/export-services/export-service.ts'); + +const EXPORT_ARCHIVE_MIME_TYPE = 'application/zip'; + +/** + * Provides functionality for exporting data. + * + * @export + * @class ExportService + * @extends {DBService} + */ +export class ExportService extends DBService { + constructor(connection: IDBConnection) { + super(connection); + } + + /** + * Export the results of a set of SQL queries to S3. + * + * @param {ExportDataConfig} config + * @return {*} {Promise} + * @memberof ExportService + */ + async export(exportConfig: ExportConfig): Promise { + defaultLog.debug({ label: 'export', message: 'exportConfig', exportConfig }); + + if (exportConfig.exportStrategies.length === 0) { + throw new ApiGeneralError('No export strategies have been defined.'); + } + + const dbClient = await this.connection.getClient(); + + try { + const archiveStream = getArchiveStream(); + + registerStreamErrorHandler(archiveStream); + + const s3UploadStream = new PassThrough(); + + registerStreamErrorHandler(s3UploadStream); + + // Pipe the archive stream to the s3 upload stream + archiveStream.pipe(s3UploadStream); + + // Start the S3 upload + const uploadPromise = uploadStreamToS3(s3UploadStream, EXPORT_ARCHIVE_MIME_TYPE, exportConfig.s3Key); + + await Promise.all( + exportConfig.exportStrategies.map(async (exportStrategy) => { + const exportStrategyConfig = await exportStrategy.getExportStrategyConfig(this.connection); + + // Append and execute all export strategy queries + if (exportStrategyConfig.queries) { + for (const queryConfig of exportStrategyConfig.queries) { + const queryStream = getQueryStream(queryConfig.sql); + + registerStreamErrorHandler(queryStream); + + const jsonStringifyTransformStream = getJsonStringifyTransformStream(); + + registerStreamErrorHandler(jsonStringifyTransformStream); + + queryStream.pipe(jsonStringifyTransformStream); + + // Append the file stream to the archive stream + archiveStream.append(jsonStringifyTransformStream, { + name: queryConfig.fileName + }); + + // Execute the query and pipe the results to the file stream + dbClient.query(queryStream); + } + } + + // Append all export strategy streams + if (exportStrategyConfig.streams) { + for (const streamConfig of exportStrategyConfig.streams) { + // Create the stream + const stream = streamConfig.stream({ dbClient }); + + registerStreamErrorHandler(stream); + + const jsonStringifyTransformStream = getJsonStringifyTransformStream(); + + registerStreamErrorHandler(jsonStringifyTransformStream); + + stream.pipe(jsonStringifyTransformStream); + + // Append the stream output to the archive stream + archiveStream.append(jsonStringifyTransformStream, { name: streamConfig.fileName }); + } + } + }) + ); + + // Finalize the archive stream (finished appending streams) + await archiveStream.finalize(); + + // Wait for the S3 upload to complete + await uploadPromise; + + // Generate signed URLs for the export file + return this._getAllSignedURLs([exportConfig.s3Key]); + } catch (error) { + defaultLog.error({ label: 'export', message: 'Error exporting data.', error }); + throw error; + } finally { + // Release the client back to the pool + dbClient.release(); + } + } + + /** + * Generate a signed URL for each s3Key. + * + * @param {string[]} s3Keys + * @return {*} {Promise} + * @memberof ExportService + */ + async _getAllSignedURLs(s3Keys: string[]): Promise { + defaultLog.debug({ label: '_getAllSignedURLs', message: 'Generating signed URLs for export file(s).' }); + + const signedURLs = await getS3SignedURLs(s3Keys); + + if (signedURLs.some((item) => item === null)) { + throw new ApiGeneralError('Failed to generate signed URLs for all export files.'); + } + + return signedURLs as string[]; + } +} diff --git a/api/src/services/export-services/export-strategy.ts b/api/src/services/export-services/export-strategy.ts new file mode 100644 index 0000000000..c09eb665d6 --- /dev/null +++ b/api/src/services/export-services/export-strategy.ts @@ -0,0 +1,75 @@ +import { Knex } from 'knex'; +import { PoolClient } from 'pg'; +import { SQLStatement } from 'sql-template-strings'; +import { Readable } from 'stream'; +import { IDBConnection } from '../../database/db'; + +export type ExportDataQuery = { + /** + * The SQL statement or Knex query builder to execute to fetch the data. + */ + sql: SQLStatement | Knex.QueryBuilder; + /** + * The file name to use for the exported data when it is saved to S3. + */ + fileName: string; +}; + +export type ExportDataStreamOptions = { + /** + * A SIMS database client. + * + * @type {PoolClient} + */ + dbClient: PoolClient; +}; + +export type ExportDataStream = { + /** + * The stream that yields the exported data. + */ + stream: (options: ExportDataStreamOptions) => Readable; + /** + * The file name to use for the exported data when it is saved to S3. + */ + fileName: string; +}; + +export type ExportStrategyConfig = { + /** + * The queries that fetch the data for this export strategy. + */ + queries?: ExportDataQuery[]; + /** + * The streams that yield the exported data. + */ + streams?: ExportDataStream[]; +}; + +export type ExportConfig = { + /** + * The export strategies to execute. + */ + exportStrategies: ExportStrategy[]; + /** + * The S3 key for the archive (zip) file to upload the exported data to. + */ + s3Key: string; +}; + +/** + * Provides functionality for exporting data. + * + * @export + * @interface ExportStrategy + */ +export interface ExportStrategy { + /** + * Get the export strategy configuration. + * + * @param {IDBConnection} connection + * @return {*} {Promise} + * @memberof ExportStrategy + */ + getExportStrategyConfig(connection: IDBConnection): Promise; +} diff --git a/api/src/services/export-services/export-utils.test.ts b/api/src/services/export-services/export-utils.test.ts new file mode 100644 index 0000000000..6b63b9ffb4 --- /dev/null +++ b/api/src/services/export-services/export-utils.test.ts @@ -0,0 +1,72 @@ +import chai, { expect } from 'chai'; +import { describe } from 'mocha'; +import QueryStream from 'pg-query-stream'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import SQL from 'sql-template-strings'; +import { Readable, Transform } from 'stream'; +import { getKnex } from '../../database/db'; +import { + getArchiveStream, + getJsonStringifyTransformStream, + getQueryStream, + registerStreamErrorHandler +} from './export-utils'; + +chai.use(sinonChai); + +describe('getArchiveStream', () => { + it('returns a new instance of archive stream', async () => { + const archiveStream = getArchiveStream(); + + expect(archiveStream).not.to.be.undefined; + }); +}); + +describe('getQueryStream', () => { + it('returns a new instance of query stream when given a sql statement', async () => { + const sqlStatement = SQL` + select 1; + `; + + const queryStream = getQueryStream(sqlStatement); + + expect(queryStream).to.be.instanceOf(QueryStream); + }); + + it('returns a new instance of query stream when given a knex query builder', async () => { + const queryBuilder = getKnex()('table').select('*'); + + const queryStream = getQueryStream(queryBuilder); + + expect(queryStream).to.be.instanceOf(QueryStream); + }); +}); + +describe('getJsonStringifyTransformStream', () => { + it('returns a transform stream', async () => { + const jsonTransform = getJsonStringifyTransformStream(); + + expect(jsonTransform).to.be.instanceOf(Transform); + }); +}); + +describe('registerStreamErrorHandler', () => { + it('adds error event handler to the stream', async () => { + const readStub = sinon.stub(); + const destroyStub = sinon.stub(); + + const stream1 = new Readable({ + read: readStub, + destroy: destroyStub + }); + + registerStreamErrorHandler(stream1); + + stream1.read(); + stream1.emit('error', new Error('test error')); // Emit an error to trigger the error handler + + expect(readStub).to.have.been.calledOnce; + expect(destroyStub).to.have.been.calledOnce; + }); +}); diff --git a/api/src/services/export-services/export-utils.ts b/api/src/services/export-services/export-utils.ts new file mode 100644 index 0000000000..09a9fb8d12 --- /dev/null +++ b/api/src/services/export-services/export-utils.ts @@ -0,0 +1,101 @@ +import archiver from 'archiver'; +import { Knex } from 'knex'; +import QueryStream from 'pg-query-stream'; +import { SQLStatement } from 'sql-template-strings'; +import { Readable, Transform } from 'stream'; +import { getLogger } from '../../utils/logger'; + +const defaultLog = getLogger('export-utils.ts'); + +export function getArchiveStream(): archiver.Archiver { + const archiveStream = archiver('zip', { + zlib: { + level: 9 // Compression level + } + }); + + return archiveStream; +} + +/** + * Get a query stream from a SQLStatement or Knex.QueryBuilder. + * + * @export + * @param {(SQLStatement | Knex.QueryBuilder)} query + * @return {*} + */ +export function getQueryStream(query: SQLStatement | Knex.QueryBuilder) { + const { text, values } = getQueryParams(query); + + const queryStream = new QueryStream(text, values); + + return queryStream; +} + +/** + * Get the query text and values from a SQLStatement or Knex.QueryBuilder. + * + * @param {(SQLStatement | Knex.QueryBuilder)} query + * @return {*} {{ text: string; values: unknown[] }} + * @memberof ExportService + */ +export function getQueryParams(query: SQLStatement | Knex.QueryBuilder): { text: string; values: unknown[] } { + let queryText = ''; + let queryValues = []; + + if (query instanceof SQLStatement) { + queryText = query.text; + queryValues = query.values; + } else { + queryText = query.toSQL().toNative().sql; + queryValues = query.toSQL().toNative().bindings as unknown[]; + } + + return { text: queryText, values: queryValues }; +} + +/** + * Get a JSON Stringify transform stream, that expects objects and outputs stringified JSON. + * + * Note: The incoming data stream must yield objects, or this will throw an error. + * + * @export + * @return {*} + */ +export function getJsonStringifyTransformStream(): Transform { + const transformStream = new Transform({ + objectMode: true, // Expects objects + transform(chunk, _encoding, callback) { + // Stringify the chunk and push it to the next stream + callback(null, JSON.stringify(chunk)); + } + }); + + return transformStream; +} + +/** + * Adds error handling to a stream to prevent memory leaks. + * + * Registers an 'error' event handler on the stream that emits am 'end' event and destroys the stream, if not already + * destroyed. + * + * @export + * @param {Readable} stream + * @return {*} {Readable} + */ +export function registerStreamErrorHandler(stream: Readable): Readable { + stream.on('error', (error) => { + defaultLog.debug({ label: 'handleStreamEvents', message: 'error', error, stream: stream.constructor.name }); + + // Emit end to close the stream + stream.emit('end'); + + if (!stream.destroyed) { + // Destroy the stream to prevent memory leaks + stream.destroy(); + } + }); + + return stream; +} diff --git a/api/src/services/export-services/observation/export-observation-strategy.test.ts b/api/src/services/export-services/observation/export-observation-strategy.test.ts new file mode 100644 index 0000000000..5d8f362192 --- /dev/null +++ b/api/src/services/export-services/observation/export-observation-strategy.test.ts @@ -0,0 +1,27 @@ +import chai, { expect } from 'chai'; +import { describe } from 'mocha'; +import sinonChai from 'sinon-chai'; +import { getMockDBConnection } from '../../../__mocks__/db'; +import { ExportObservationStrategy } from './export-observation-strategy'; + +chai.use(sinonChai); + +describe('ExportObservationStrategy', () => { + describe('getExportStrategyConfig', () => { + it('should return the export strategy config', async () => { + const connection = getMockDBConnection(); + + const config = { + surveyId: 1, + isUserAdmin: true + }; + + const exportObservationStrategy = new ExportObservationStrategy(config, connection); + + const result = await exportObservationStrategy.getExportStrategyConfig(); + + expect(result.queries?.length).to.equal(1); + expect(result.queries?.[0].fileName).to.equal('observations.json'); + }); + }); +}); diff --git a/api/src/services/export-services/observation/export-observation-strategy.ts b/api/src/services/export-services/observation/export-observation-strategy.ts new file mode 100644 index 0000000000..9e5111f18d --- /dev/null +++ b/api/src/services/export-services/observation/export-observation-strategy.ts @@ -0,0 +1,73 @@ +import { getKnex, IDBConnection } from '../../../database/db'; +import { getLogger } from '../../../utils/logger'; +import { DBService } from '../../db-service'; +import { ExportStrategy, ExportStrategyConfig } from '../export-strategy'; + +const defaultLog = getLogger('services/export-observation-strategy'); + +export type ExportObservationConfig = { + surveyId: number; + isUserAdmin: boolean; +}; + +/** + * Provides functionality for exporting observation data. + * + * @export + * @class ExportObservationStrategy + * @extends {DBService} + * @implements {ExportStrategy} + */ +export class ExportObservationStrategy extends DBService implements ExportStrategy { + config: ExportObservationConfig; + + constructor(config: ExportObservationConfig, connection: IDBConnection) { + super(connection); + + this.config = config; + } + + /** + * Get the export strategy configuration for the observations. + * + * @return {*} {Promise} + * @memberof ExportObservationStrategy + */ + async getExportStrategyConfig(): Promise { + try { + return { + queries: [ + { + sql: this._getSql(), + fileName: 'observations.json' + } + ] + }; + } catch (error) { + defaultLog.error({ + label: 'getExportStrategyConfig', + message: 'Error generating export strategy config.', + error + }); + + throw error; + } + } + + /** + * Build and return the observation data sql query. + * + * @memberof ExportObservationStrategy + */ + _getSql = () => { + const knex = getKnex(); + + const queryBuilder = knex + .queryBuilder() + .select('*') + .from('survey_observation') + .where('survey_id', this.config.surveyId); + + return queryBuilder; + }; +} diff --git a/api/src/services/export-services/survey/export-survey-metadata-strategy.test.ts b/api/src/services/export-services/survey/export-survey-metadata-strategy.test.ts new file mode 100644 index 0000000000..6fc564a491 --- /dev/null +++ b/api/src/services/export-services/survey/export-survey-metadata-strategy.test.ts @@ -0,0 +1,32 @@ +import chai, { expect } from 'chai'; +import { describe } from 'mocha'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import { getMockDBConnection } from '../../../__mocks__/db'; +import { ExportSurveyMetadataStrategy } from './export-survey-metadata-strategy'; + +chai.use(sinonChai); + +describe('ExportSurveyMetadataStrategy', () => { + afterEach(() => { + sinon.restore(); + }); + + describe('getExportStrategyConfig', () => { + it('should return the export strategy config', async () => { + const connection = getMockDBConnection(); + + const config = { + surveyId: 1, + isUserAdmin: true + }; + + const exportSurveyMetadataStrategy = new ExportSurveyMetadataStrategy(config, connection); + + const result = await exportSurveyMetadataStrategy.getExportStrategyConfig(); + + expect(result.queries?.length).to.equal(1); + expect(result.queries?.[0].fileName).to.equal('survey_metadata.json'); + }); + }); +}); diff --git a/api/src/services/export-services/survey/export-survey-metadata-strategy.ts b/api/src/services/export-services/survey/export-survey-metadata-strategy.ts new file mode 100644 index 0000000000..04bdf332c4 --- /dev/null +++ b/api/src/services/export-services/survey/export-survey-metadata-strategy.ts @@ -0,0 +1,69 @@ +import { getKnex, IDBConnection } from '../../../database/db'; +import { getLogger } from '../../../utils/logger'; +import { DBService } from '../../db-service'; +import { ExportStrategy, ExportStrategyConfig } from '../export-strategy'; + +const defaultLog = getLogger('services/export-survey-metadata-strategy'); + +export type ExportSurveyMetadataConfig = { + surveyId: number; + isUserAdmin: boolean; +}; + +/** + * Provides functionality for exporting survey metadata data. + * + * @export + * @class ExportSurveyMetadataStrategy + * @extends {DBService} + * @implements {ExportStrategy} + */ +export class ExportSurveyMetadataStrategy extends DBService implements ExportStrategy { + config: ExportSurveyMetadataConfig; + + constructor(config: ExportSurveyMetadataConfig, connection: IDBConnection) { + super(connection); + + this.config = config; + } + + /** + * Get the export strategy configuration for the survey metadata. + * + * @return {*} {Promise} + * @memberof ExportSurveyMetadataStrategy + */ + async getExportStrategyConfig(): Promise { + try { + return { + queries: [ + { + sql: this._getSql(), + fileName: 'survey_metadata.json' + } + ] + }; + } catch (error) { + defaultLog.error({ + label: 'getExportStrategyConfig', + message: 'Error generating export strategy config.', + error + }); + + throw error; + } + } + + /** + * Build and return the survey metadata data sql query. + * + * @memberof ExportSurveyMetadataStrategy + */ + _getSql = () => { + const knex = getKnex(); + + const queryBuilder = knex.queryBuilder().select('*').from('survey').where('survey_id', this.config.surveyId); + + return queryBuilder; + }; +} diff --git a/api/src/services/export-services/survey/export-survey-strategy.test.ts b/api/src/services/export-services/survey/export-survey-strategy.test.ts new file mode 100644 index 0000000000..e8ab53dee8 --- /dev/null +++ b/api/src/services/export-services/survey/export-survey-strategy.test.ts @@ -0,0 +1,147 @@ +import chai, { expect } from 'chai'; +import { describe } from 'mocha'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import SQL from 'sql-template-strings'; +import { Readable } from 'stream'; +import { getMockDBConnection } from '../../../__mocks__/db'; +import { ExportObservationStrategy } from '../observation/export-observation-strategy'; +import { ExportTelemetryStrategy } from '../telemetry/export-telemetry-strategy'; +import { ExportSurveyMetadataStrategy } from './export-survey-metadata-strategy'; +import { ExportSurveyConfig, ExportSurveyStrategy, ExportSurveyStrategyConfig } from './export-survey-strategy'; + +chai.use(sinonChai); + +describe('ExportSurveyStrategy', () => { + afterEach(() => { + sinon.restore(); + }); + + it('should initialize with correct properties', () => { + const dbConnection = getMockDBConnection(); + + const exportSurveyConfig: ExportSurveyConfig = { + metadata: true, + sampling_data: false, + observation_data: true, + telemetry_data: true, + animal_data: false, + artifacts: false + }; + + const exportSurveyStrategyConfig: ExportSurveyStrategyConfig = { + surveyId: 1, + config: exportSurveyConfig, + connection: dbConnection, + isUserAdmin: true + }; + + const exportSurveyStrategy = new ExportSurveyStrategy(exportSurveyStrategyConfig); + + expect(exportSurveyStrategy.surveyId).to.equal(exportSurveyStrategyConfig.surveyId); + expect(exportSurveyStrategy.config).to.deep.equal(exportSurveyStrategyConfig.config); + expect(exportSurveyStrategy.isUserAdmin).to.equal(exportSurveyStrategyConfig.isUserAdmin); + }); + + it('should call getExportStrategyConfig for each enabled strategy', async () => { + const dbConnection = getMockDBConnection(); + + const exportSurveyConfig: ExportSurveyConfig = { + metadata: true, + sampling_data: false, + observation_data: true, + telemetry_data: true, + animal_data: false, + artifacts: false + }; + + const exportSurveyStrategyConfig: ExportSurveyStrategyConfig = { + surveyId: 1, + config: exportSurveyConfig, + connection: dbConnection, + isUserAdmin: true + }; + + const exportSurveyStrategy = new ExportSurveyStrategy(exportSurveyStrategyConfig); + + const metadataGetExportStrategyConfig = sinon + .stub(ExportSurveyMetadataStrategy.prototype, 'getExportStrategyConfig') + .resolves({ + queries: [ + { + sql: SQL`metadata_query`, + fileName: 'metadata.json' + } + ] + }); + + const observationGetExportStrategyConfig = sinon + .stub(ExportObservationStrategy.prototype, 'getExportStrategyConfig') + .resolves({ + queries: [ + { + sql: SQL`observation_query`, + fileName: 'observation.json' + } + ] + }); + + const telemetryGetExportStrategyConfig = sinon + .stub(ExportTelemetryStrategy.prototype, 'getExportStrategyConfig') + .resolves({ + streams: [ + { + stream: () => { + return new Readable({ + read() {} + }); + }, + fileName: 'telemetry.json' + } + ] + }); + + const result = await exportSurveyStrategy.getExportStrategyConfig(); + + expect(metadataGetExportStrategyConfig).to.have.been.calledOnce; + expect(observationGetExportStrategyConfig).to.have.been.calledOnce; + expect(telemetryGetExportStrategyConfig).to.have.been.calledOnce; + + expect(result.queries?.length).to.equal(2); + expect(result.queries?.[0].fileName).to.equal('metadata.json'); + expect(result.queries?.[1].fileName).to.equal('observation.json'); + + expect(result.streams?.length).to.equal(1); + expect(result.streams?.[0].fileName).to.equal('telemetry.json'); + }); + + it('should catch and re-throw errors', async () => { + const dbConnection = getMockDBConnection(); + + const exportSurveyConfig: ExportSurveyConfig = { + metadata: true, + sampling_data: false, + observation_data: true, + telemetry_data: true, + animal_data: false, + artifacts: false + }; + + const exportSurveyStrategyConfig: ExportSurveyStrategyConfig = { + surveyId: 1, + config: exportSurveyConfig, + connection: dbConnection, + isUserAdmin: true + }; + + const exportSurveyStrategy = new ExportSurveyStrategy(exportSurveyStrategyConfig); + + sinon.stub(ExportSurveyMetadataStrategy.prototype, 'getExportStrategyConfig').rejects(new Error('test error')); + + try { + await exportSurveyStrategy.getExportStrategyConfig(); + } catch (error) { + expect((error as Error).message).to.equal('test error'); + } + }); +}); diff --git a/api/src/services/export-services/survey/export-survey-strategy.ts b/api/src/services/export-services/survey/export-survey-strategy.ts new file mode 100644 index 0000000000..809ccebd14 --- /dev/null +++ b/api/src/services/export-services/survey/export-survey-strategy.ts @@ -0,0 +1,132 @@ +import { IDBConnection } from '../../../database/db'; +import { getLogger } from '../../../utils/logger'; +import { DBService } from '../../db-service'; +import { ExportStrategy, ExportStrategyConfig } from '../export-strategy'; +import { ExportObservationStrategy } from '../observation/export-observation-strategy'; +import { ExportTelemetryStrategy } from '../telemetry/export-telemetry-strategy'; +import { ExportSurveyMetadataStrategy } from './export-survey-metadata-strategy'; + +const defaultLog = getLogger('services/export-survey-strategy'); + +export type ExportSurveyStrategyConfig = { + /** + * The survey id. + * + * @type {number} + */ + surveyId: number; + /** + * The export configuration. + * + * @type {ExportSurveyConfig} + */ + config: ExportSurveyConfig; + /** + * The database connection. + * + * @type {IDBConnection} + */ + connection: IDBConnection; + /** + * Indicates if the user is an admin. + */ + isUserAdmin: boolean; +}; + +/** + * Configure which data to include in the export. + */ +export type ExportSurveyConfig = { + metadata: boolean; + sampling_data: boolean; + observation_data: boolean; + telemetry_data: boolean; + animal_data: boolean; + artifacts: boolean; +}; + +/** + * Provides functionality for exporting survey data. + * + * @export + * @class ExportSurveyStrategy + * @extends {DBService} + * @implements {ExportStrategy} + */ +export class ExportSurveyStrategy extends DBService implements ExportStrategy { + surveyId: number; + config: ExportSurveyConfig; + isUserAdmin: boolean; + + constructor(options: ExportSurveyStrategyConfig) { + super(options.connection); + + this.surveyId = options.surveyId; + this.config = options.config; + this.isUserAdmin = options.isUserAdmin; + } + + /** + * Get the export strategy configuration for the survey. + * + * @return {*} {Promise} + * @memberof ExportSurveyStrategy + */ + async getExportStrategyConfig(): Promise { + try { + const strategyPromises = []; + + if (this.config.metadata) { + const strategy = new ExportSurveyMetadataStrategy( + { surveyId: this.surveyId, isUserAdmin: this.isUserAdmin }, + this.connection + ); + strategyPromises.push(strategy.getExportStrategyConfig()); + } + + if (this.config.observation_data) { + const strategy = new ExportObservationStrategy( + { surveyId: this.surveyId, isUserAdmin: this.isUserAdmin }, + this.connection + ); + strategyPromises.push(strategy.getExportStrategyConfig()); + } + + if (this.config.telemetry_data) { + const strategy = new ExportTelemetryStrategy( + { surveyId: this.surveyId, isUserAdmin: this.isUserAdmin }, + this.connection + ); + strategyPromises.push(strategy.getExportStrategyConfig()); + } + + const strategies = await Promise.all(strategyPromises); + + const allQueries = []; + const allStreams = []; + + for (const strategy of strategies) { + if (strategy.queries) { + allQueries.push(...strategy.queries); + } + + if (strategy.streams) { + allStreams.push(...strategy.streams); + } + } + + return { + queries: allQueries, + streams: allStreams + }; + } catch (error) { + defaultLog.error({ + label: 'getExportStrategyConfig', + message: 'Error generating export strategy config.', + error + }); + + throw error; + } + } +} diff --git a/api/src/services/export-services/telemetry/export-telemetry-strategy.test.ts b/api/src/services/export-services/telemetry/export-telemetry-strategy.test.ts new file mode 100644 index 0000000000..69eaf1b7fc --- /dev/null +++ b/api/src/services/export-services/telemetry/export-telemetry-strategy.test.ts @@ -0,0 +1,27 @@ +import chai, { expect } from 'chai'; +import { describe } from 'mocha'; +import sinonChai from 'sinon-chai'; +import { getMockDBConnection } from '../../../__mocks__/db'; +import { ExportTelemetryStrategy } from './export-telemetry-strategy'; + +chai.use(sinonChai); + +describe('ExportTelemetryStrategy', () => { + describe('getExportStrategyConfig', () => { + it('should return the export strategy config', async () => { + const connection = getMockDBConnection(); + + const config = { + surveyId: 1, + isUserAdmin: true + }; + + const exportTelemetryStrategy = new ExportTelemetryStrategy(config, connection); + + const result = await exportTelemetryStrategy.getExportStrategyConfig(); + + expect(result.streams?.length).to.equal(1); + expect(result.streams?.[0].fileName).to.equal('telemetry.json'); + }); + }); +}); diff --git a/api/src/services/export-services/telemetry/export-telemetry-strategy.ts b/api/src/services/export-services/telemetry/export-telemetry-strategy.ts new file mode 100644 index 0000000000..64dd0ee938 --- /dev/null +++ b/api/src/services/export-services/telemetry/export-telemetry-strategy.ts @@ -0,0 +1,95 @@ +import { Readable } from 'stream'; +import { IDBConnection } from '../../../database/db'; +import { getLogger } from '../../../utils/logger'; +import { DBService } from '../../db-service'; +import { TelemetryService } from '../../telemetry-service'; +import { ExportDataStreamOptions, ExportStrategy, ExportStrategyConfig } from '../export-strategy'; + +const defaultLog = getLogger('services/export-telemetry-strategy'); + +export type ExportTelemetryConfig = { + surveyId: number; + isUserAdmin: boolean; +}; + +/** + * Provides functionality for exporting telemetry data. + * + * @export + * @class ExportTelemetryStrategy + * @extends {DBService} + * @implements {ExportStrategy} + */ +export class ExportTelemetryStrategy extends DBService implements ExportStrategy { + config: ExportTelemetryConfig; + + constructor(config: ExportTelemetryConfig, connection: IDBConnection) { + super(connection); + + this.config = config; + } + + /** + * Get the export strategy configuration for the telemetry data. + * + * @return {*} {Promise} + * @memberof ExportTelemetryStrategy + */ + async getExportStrategyConfig(): Promise { + try { + return { + streams: [ + { + stream: this._getStream, + fileName: 'telemetry.json' + } + ] + }; + } catch (error) { + defaultLog.error({ + label: 'getExportStrategyConfig', + message: 'Error generating export strategy config.', + error + }); + + throw error; + } + } + + /** + * Build and return the telemetry data stream. + * + * @param {ExportDataStreamOptions} _options + * @memberof ExportTelemetryStrategy + */ + _getStream = (_options: ExportDataStreamOptions): Readable => { + const telemetryService = new TelemetryService(this.connection); + + const isUserAdmin = this.config.isUserAdmin; + const systemUserId = this.connection.systemUserId(); + const filterFields = { + survey_ids: [this.config.surveyId] + }; + + const stream = new Readable({ + objectMode: true, + read() { + telemetryService + .findTelemetry(isUserAdmin, systemUserId, filterFields) + .then((telemetry) => { + for (const item of telemetry) { + this.push(item); + } + + // Signal the end of the stream + this.push(null); + }) + .catch((error) => { + this.emit('error', error); + }); + } + }); + + return stream; + }; +} diff --git a/api/src/utils/file-utils.test.ts b/api/src/utils/file-utils.test.ts index 4783d97742..6137d956db 100644 --- a/api/src/utils/file-utils.test.ts +++ b/api/src/utils/file-utils.test.ts @@ -5,12 +5,12 @@ import { deleteFileFromS3, generateS3FileKey, getS3HostUrl, + getS3KeyPrefix, getS3SignedURL, _getClamAvScanner, _getObjectStoreBucketName, _getObjectStoreUrl, - _getS3Client, - _getS3KeyPrefix + _getS3Client } from './file-utils'; describe('deleteFileFromS3', () => { @@ -219,7 +219,7 @@ describe('_getObjectStoreUrl', () => { }); }); -describe('_getS3KeyPrefix', () => { +describe('getS3KeyPrefix', () => { const OLD_S3_KEY_PREFIX = process.env.S3_KEY_PREFIX; afterEach(() => { @@ -229,14 +229,14 @@ describe('_getS3KeyPrefix', () => { it('should return an s3 key prefix', () => { process.env.S3_KEY_PREFIX = 'test-sims'; - const result = _getS3KeyPrefix(); + const result = getS3KeyPrefix(); expect(result).to.equal('test-sims'); }); it('should return its default value', () => { delete process.env.S3_KEY_PREFIX; - const result = _getS3KeyPrefix(); + const result = getS3KeyPrefix(); expect(result).to.equal('sims'); }); }); diff --git a/api/src/utils/file-utils.ts b/api/src/utils/file-utils.ts index 63f2c1f49a..da556e1e49 100644 --- a/api/src/utils/file-utils.ts +++ b/api/src/utils/file-utils.ts @@ -1,4 +1,5 @@ import { + CompleteMultipartUploadCommandOutput, DeleteObjectCommand, DeleteObjectCommandOutput, GetObjectCommand, @@ -11,6 +12,7 @@ import { PutObjectCommandOutput, S3Client } from '@aws-sdk/client-s3'; +import { Upload } from '@aws-sdk/lib-storage'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; import NodeClam from 'clamscan'; import { Readable } from 'stream'; @@ -91,7 +93,7 @@ export const getS3HostUrl = (key?: string): string => { * * @returns {*} {string} The S3 key prefix */ -export const _getS3KeyPrefix = (): string => { +export const getS3KeyPrefix = (): string => { return process.env.S3_KEY_PREFIX || 'sims'; }; @@ -126,7 +128,7 @@ export async function deleteFileFromS3(key: string): Promise} [metadata={}] A metadata object to store additional information with the file - * @returns {Promise} the response from S3 or null if required parameters are null + * @returns {Promise} the response from S3 */ export async function uploadFileToS3( file: Express.Multer.File, @@ -154,7 +156,7 @@ export async function uploadFileToS3( * @param {string} mimetype the mimetype of the buffer * @param {string} key the path where S3 will store the file * @param {Record} [metadata={}] A metadata object to store additional information with the file - * @returns {Promise} the response from S3 or null if required parameters are null + * @returns {Promise} the response from S3 */ export async function uploadBufferToS3( buffer: Buffer, @@ -175,6 +177,36 @@ export async function uploadBufferToS3( ); } +/** + * Upload a stream to S3. + * + * @export + * @param {stream} Readable the stream to upload + * @param {string} mimetype the mimetype of the stream data + * @param {string} key the path where S3 will store the file + * @param {Record} [metadata={}] A metadata object to store additional information with the file + * @return {*} {Promise} the response from S3 + */ +export async function uploadStreamToS3( + stream: Readable, + mimetype: string, + key: string, + metadata: Record = {} +): Promise { + const streamUpload = new Upload({ + client: _getS3Client(), + params: { + Bucket: _getObjectStoreBucketName(), + Key: key, + Body: stream, + ContentType: mimetype, + Metadata: metadata + } + }); + + return streamUpload.done(); +} + /** * Fetch a file from S3. * @@ -231,7 +263,7 @@ export async function getObjectMeta(key: string): Promise} the response from S3 or null if required parameters are null + * @return {*} {(Promise)} the response from S3 or null if required parameters are null */ export async function getS3SignedURL(key: string): Promise { const s3Client = _getS3Client(); @@ -252,6 +284,17 @@ export async function getS3SignedURL(key: string): Promise { ); } +/** + * Get an array of s3 signed urls. + * + * @export + * @param {string[]} keys + * @return {*} {(Promise<(string | null)[]>)} + */ +export async function getS3SignedURLs(keys: string[]): Promise<(string | null)[]> { + return Promise.all(keys.map((key) => getS3SignedURL(key))); +} + export interface IS3FileKey { /** * The project ID the file is associated with. @@ -282,8 +325,15 @@ export interface IS3FileKey { fileName: string; } +/** + * Generate an S3 key for a project or survey attachment file. + * + * @export + * @param {IS3FileKey} options + * @return {*} {string} + */ export function generateS3FileKey(options: IS3FileKey): string { - const keyParts: (string | number)[] = [_getS3KeyPrefix()]; + const keyParts: (string | number)[] = [getS3KeyPrefix()]; if (options.projectId) { keyParts.push('projects'); @@ -311,6 +361,31 @@ export function generateS3FileKey(options: IS3FileKey): string { return keyParts.filter(Boolean).join('/'); } +/** + * Generate an S3 key for a data export zip file. + * + * @export + * @return {*} {string} + */ +export function generateS3ExportKey(): string { + const keyParts: (string | number)[] = [getS3KeyPrefix()]; + + keyParts.push('exports'); + keyParts.push(`sims_data_export_${new Date().toISOString()}.zip`); + + return keyParts.join('/'); +} + +/** + * Execute a clamav virus scan against the given file. + * + * Note: This depends on the external clamav service being available and configured correctly. + * + * @export + * @param {Express.Multer.File} file + * @return {*} {Promise} `true` if the file is safe, `false` if the file is a virus or contains malicious + * content. + */ export async function scanFileForVirus(file: Express.Multer.File): Promise { if (process.env.ENABLE_FILE_VIRUS_SCAN !== 'true' || !process.env.CLAMAV_HOST || !process.env.CLAMAV_PORT) { // Virus scanning is not enabled or necessary environment variables are not set diff --git a/api/src/utils/sql-utils.test.ts b/api/src/utils/sql-utils.test.ts deleted file mode 100644 index 5342493a73..0000000000 --- a/api/src/utils/sql-utils.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { expect } from 'chai'; -import { describe } from 'mocha'; -import SQL, { SQLStatement } from 'sql-template-strings'; -import { appendSQLColumns, appendSQLColumnsEqualValues, appendSQLValues } from './sql-utils'; - -describe('appendSQLColumnsEqualValues', () => { - it('appends nothing when empty array provided', async () => { - const sqlStatement: SQLStatement = SQL`UPDATE table SET`; - - appendSQLColumnsEqualValues(sqlStatement, []); - - expect(sqlStatement.text).to.equal('UPDATE table SET'); - expect(sqlStatement.values).to.eql([]); - }); - - it('appends a column name and value to the sql statement', async () => { - const sqlStatement: SQLStatement = SQL`UPDATE table SET`; - - appendSQLColumnsEqualValues(sqlStatement, [{ columnName: 'col1', columnValue: 'col1value' }]); - - expect(sqlStatement.text).to.equal('UPDATE table SET col1 = $1'); - expect(sqlStatement.values).to.eql(['col1value']); - }); - - it('appends multiple column names and values to the sql statement', async () => { - const sqlStatement: SQLStatement = SQL`UPDATE table SET`; - - appendSQLColumnsEqualValues(sqlStatement, [ - { columnName: 'col1', columnValue: 'col1value' }, - { columnName: 'col2', columnValue: 'col2value' }, - { columnName: 'col3', columnValue: 'col3value' } - ]); - - expect(sqlStatement.text).to.equal('UPDATE table SET col1 = $1, col2 = $2, col3 = $3'); - expect(sqlStatement.values).to.eql(['col1value', 'col2value', 'col3value']); - }); -}); - -describe('appendSQLColumns', () => { - it('appends nothing when empty array provided', async () => { - const sqlStatement: SQLStatement = SQL`INSERT INTO table (id,`; - - appendSQLColumns(sqlStatement, []); - - expect(sqlStatement.text).to.equal('INSERT INTO table (id,'); - expect(sqlStatement.values).to.eql([]); - }); - - it('appends a column name to the sql statement', async () => { - const sqlStatement: SQLStatement = SQL`INSERT INTO table (id,`; - - appendSQLColumns(sqlStatement, [{ columnName: 'col1' }]); - - expect(sqlStatement.text).to.equal('INSERT INTO table (id, col1'); - expect(sqlStatement.values).to.eql([]); - }); - - it('appends multiple column names to the sql statement', async () => { - const sqlStatement: SQLStatement = SQL`INSERT INTO table (id,`; - - appendSQLColumns(sqlStatement, [{ columnName: 'col1' }, { columnName: 'col2' }, { columnName: 'col3' }]); - - expect(sqlStatement.text).to.equal('INSERT INTO table (id, col1, col2, col3'); - expect(sqlStatement.values).to.eql([]); - }); -}); - -describe('appendSQLValues', () => { - it('appends nothing when empty array provided', async () => { - const sqlStatement: SQLStatement = SQL`INSERT INTO table (id,`; - - appendSQLValues(sqlStatement, []); - - expect(sqlStatement.text).to.equal('INSERT INTO table (id,'); - expect(sqlStatement.values).to.eql([]); - }); - - it('appends a column value to the sql statement', async () => { - const sqlStatement: SQLStatement = SQL`INSERT INTO table (id, col1) VALUES (${123},`; - - appendSQLValues(sqlStatement, [{ columnValue: 'col1value' }]); - - expect(sqlStatement.text).to.equal('INSERT INTO table (id, col1) VALUES ($1, $2'); - expect(sqlStatement.values).to.eql([123, 'col1value']); - }); - - it('appends multiple column values to the sql statement', async () => { - const sqlStatement: SQLStatement = SQL`INSERT INTO table (id, col1, col2, col3) VALUES (${123},`; - - appendSQLValues(sqlStatement, [ - { columnValue: 'col1value' }, - { columnValue: 'col2value' }, - { columnValue: 'col3value' } - ]); - - expect(sqlStatement.text).to.equal('INSERT INTO table (id, col1, col2, col3) VALUES ($1, $2, $3, $4'); - expect(sqlStatement.values).to.eql([123, 'col1value', 'col2value', 'col3value']); - }); -}); diff --git a/api/src/utils/sql-utils.ts b/api/src/utils/sql-utils.ts deleted file mode 100644 index a31fb818dd..0000000000 --- a/api/src/utils/sql-utils.ts +++ /dev/null @@ -1,73 +0,0 @@ -import SQL, { SQLStatement } from 'sql-template-strings'; - -export type AppendSQLColumnsEqualValues = AppendSQLColumn & AppendSQLValue; - -export type AppendSQLColumn = { - columnName: string; -}; - -export type AppendSQLValue = { - columnValue: any; -}; - -export const appendSQLColumnsEqualValues = ( - sqlStatement: SQLStatement, - items: AppendSQLColumnsEqualValues[] -): SQLStatement => { - if (!items || !items.length) { - // no items, return sql statement unchanged - return sqlStatement; - } - - // append the first column - sqlStatement.append(` ${items[0].columnName} = `); - sqlStatement.append(SQL`${items[0].columnValue}`); - - // append all subsequent columns - if (items.length > 1) { - for (let i = 1; i < items.length; i++) { - sqlStatement.append(`, ${items[i].columnName} = `); - sqlStatement.append(SQL`${items[i].columnValue}`); - } - } - - return sqlStatement; -}; - -export const appendSQLColumns = (sqlStatement: SQLStatement, items: AppendSQLColumn[]): SQLStatement => { - if (!items || !items.length) { - // no items, return sql statement unchanged - return sqlStatement; - } - - // append the first column - sqlStatement.append(` ${items[0].columnName}`); - - // append all subsequent columns - if (items.length > 1) { - for (let i = 1; i < items.length; i++) { - sqlStatement.append(`, ${items[i].columnName}`); - } - } - - return sqlStatement; -}; - -export const appendSQLValues = (sqlStatement: SQLStatement, items: AppendSQLValue[]): SQLStatement => { - if (!items || !items.length) { - // no items, return sql statement unchanged - return sqlStatement; - } - - // append the first value - sqlStatement.append(SQL` ${items[0].columnValue}`); - - // append all subsequent values - if (items.length > 1) { - for (let i = 1; i < items.length; i++) { - sqlStatement.append(SQL`, ${items[i].columnValue}`); - } - } - - return sqlStatement; -}; diff --git a/app/package-lock.json b/app/package-lock.json index f114441c7b..f469247e16 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -49,7 +49,6 @@ "react-dom": "^18.2.0", "react-dropzone": "^11.3.2", "react-leaflet": "^4.2.1", - "react-leaflet-cluster": "^2.1.0", "react-number-format": "^4.5.2", "react-oidc-context": "^2.3.1", "react-router": "^5.3.3", @@ -17233,14 +17232,6 @@ "resolved": "https://registry.npmjs.org/leaflet.locatecontrol/-/leaflet.locatecontrol-0.79.0.tgz", "integrity": "sha512-h64QIHFkypYdr90lkSfjKvPvvk8/b8UnP3m9WuoWdp5p2AaCWC0T1NVwyuj4rd5U4fBW3tQt4ppmZ2LceHMIDg==" }, - "node_modules/leaflet.markercluster": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz", - "integrity": "sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==", - "peerDependencies": { - "leaflet": "^1.3.1" - } - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -21060,20 +21051,6 @@ "react-dom": "^18.0.0" } }, - "node_modules/react-leaflet-cluster": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/react-leaflet-cluster/-/react-leaflet-cluster-2.1.0.tgz", - "integrity": "sha512-16X7XQpRThQFC4PH4OpXHimGg19ouWmjxjtpxOeBKpvERSvIRqTx7fvhTwkEPNMFTQ8zTfddz6fRTUmUEQul7g==", - "dependencies": { - "leaflet.markercluster": "^1.5.3" - }, - "peerDependencies": { - "leaflet": "^1.8.0", - "react": "^18.0.0", - "react-dom": "^18.0.0", - "react-leaflet": "^4.0.0" - } - }, "node_modules/react-number-format": { "version": "4.9.4", "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-4.9.4.tgz", diff --git a/app/package.json b/app/package.json index 05208cad7d..4f524a4399 100644 --- a/app/package.json +++ b/app/package.json @@ -65,7 +65,6 @@ "react-dom": "^18.2.0", "react-dropzone": "^11.3.2", "react-leaflet": "^4.2.1", - "react-leaflet-cluster": "^2.1.0", "react-number-format": "^4.5.2", "react-oidc-context": "^2.3.1", "react-router": "^5.3.3", diff --git a/app/src/constants/i18n.ts b/app/src/constants/i18n.ts index aa32e36408..e216532879 100644 --- a/app/src/constants/i18n.ts +++ b/app/src/constants/i18n.ts @@ -474,3 +474,9 @@ export const EditAnimalDeploymentI18N = { createErrorText: 'An error has occurred while attempting to create your deployment. Please try again. If the error persists, please contact your system administrator.' }; + +export const SurveyExportI18N = { + exportErrorTitle: 'Error Exporting Survey Data', + exportErrorText: + 'An error has occurred while attempting to export survey data. Please try again. If the error persists, please contact your system administrator.' +}; diff --git a/app/src/features/surveys/view/SurveyHeader.tsx b/app/src/features/surveys/view/SurveyHeader.tsx index d08ed75c26..be6af77b5e 100644 --- a/app/src/features/surveys/view/SurveyHeader.tsx +++ b/app/src/features/surveys/view/SurveyHeader.tsx @@ -1,4 +1,11 @@ -import { mdiCalendarRange, mdiChevronDown, mdiCogOutline, mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js'; +import { + mdiCalendarRange, + mdiChevronDown, + mdiCogOutline, + mdiPencilOutline, + mdiTrashCanOutline, + mdiTrayArrowDown +} from '@mdi/js'; import Icon from '@mdi/react'; import Box from '@mui/material/Box'; import Breadcrumbs from '@mui/material/Breadcrumbs'; @@ -21,6 +28,7 @@ import { PROJECT_PERMISSION, SYSTEM_ROLE } from 'constants/roles'; import { DialogContext } from 'contexts/dialogContext'; import { ProjectContext } from 'contexts/projectContext'; import { SurveyContext } from 'contexts/surveyContext'; +import { SurveyExportDialog } from 'features/surveys/view/survey-export/SurveyExportDialog'; import { APIError } from 'hooks/api/useAxios'; import { useBiohubApi } from 'hooks/useBioHubApi'; import React, { useContext, useState } from 'react'; @@ -47,6 +55,8 @@ const SurveyHeader = () => { const dialogContext = useContext(DialogContext); + const [openSurveyExportDialog, setOpenSurveyExportDialog] = useState(false); + const defaultYesNoDialogProps = { dialogTitle: DeleteSurveyI18N.deleteTitle, dialogText: DeleteSurveyI18N.deleteText, @@ -208,6 +218,14 @@ const SurveyHeader = () => { Settings + { + setOpenSurveyExportDialog(false); + setMenuAnchorEl(null); + }} + /> + { Delete Survey + + setOpenSurveyExportDialog(true)}> + + + + Export Survey + + } diff --git a/app/src/features/surveys/view/survey-export/SurveyExportDialog.tsx b/app/src/features/surveys/view/survey-export/SurveyExportDialog.tsx new file mode 100644 index 0000000000..0834864019 --- /dev/null +++ b/app/src/features/surveys/view/survey-export/SurveyExportDialog.tsx @@ -0,0 +1,107 @@ +import LoadingButton from '@mui/lab/LoadingButton'; +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogTitle from '@mui/material/DialogTitle'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import useTheme from '@mui/system/useTheme'; +import AlertBar from 'components/alert/AlertBar'; +import { SurveyExportI18N } from 'constants/i18n'; +import { SurveyExportConfig, SurveyExportForm } from 'features/surveys/view/survey-export/SurveyExportForm'; +import { APIError } from 'hooks/api/useAxios'; +import { useBiohubApi } from 'hooks/useBioHubApi'; +import { useDialogContext, useSurveyContext } from 'hooks/useContext'; +import { useState } from 'react'; + +export interface ISurveyExportDialogProps { + open: boolean; + onCancel: () => void; +} + +export const SurveyExportDialog = (props: ISurveyExportDialogProps) => { + const { open, onCancel } = props; + + const theme = useTheme(); + const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); + + const biohubApi = useBiohubApi(); + + const dialogContext = useDialogContext(); + const surveyContext = useSurveyContext(); + + const [exportConfig, setExportConfig] = useState({} as any); + const [isExporting, setIsExporting] = useState(false); + + const isDisabled = Object.values(exportConfig).every((value) => value === false); + + /** + * Handle the export of the survey data, including downloading the file to the users device. + */ + const handleExport = async () => { + try { + setIsExporting(true); + + const response = await biohubApi.survey.exportData(surveyContext.projectId, surveyContext.surveyId, exportConfig); + + setIsExporting(false); + + if (!response) { + return; + } + + response.presignedS3Urls.forEach((url) => { + window.open(url); + }); + } catch (error) { + const apiError = error as APIError; + dialogContext.setErrorDialog({ + open: true, + dialogTitle: SurveyExportI18N.exportErrorTitle, + dialogText: SurveyExportI18N.exportErrorText, + dialogErrorDetails: apiError.errors, + onOk: () => dialogContext.setErrorDialog({ open: false }), + onClose: () => dialogContext.setErrorDialog({ open: false }) + }); + } + }; + + return ( + + {'Export Survey Data'} + + + + + + + Start Export + + + + + ); +}; diff --git a/app/src/features/surveys/view/survey-export/SurveyExportForm.tsx b/app/src/features/surveys/view/survey-export/SurveyExportForm.tsx new file mode 100644 index 0000000000..d19b49ba1b --- /dev/null +++ b/app/src/features/surveys/view/survey-export/SurveyExportForm.tsx @@ -0,0 +1,228 @@ +import Box from '@mui/material/Box'; +import Checkbox from '@mui/material/Checkbox'; +import FormControl from '@mui/material/FormControl'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import FormGroup from '@mui/material/FormGroup'; +import FormHelperText from '@mui/material/FormHelperText'; +import Typography from '@mui/material/Typography'; +import { useEffect, useReducer } from 'react'; + +const useStyles = () => { + return { + exportInput: { + alignItems: 'flex-start', + maxWidth: '92ch', + '& label': { + alignItems: 'flex-start' + }, + '& .MuiButtonBase-root': { + marginTop: '-6px', + marginRight: '0.5rem' + } + } + }; +}; + +export interface ISurveyExportFormProps { + onExportConfig: (config: SurveyExportConfig) => void; +} + +export type SurveyExportConfig = { + metadata: boolean; + sampling_data: boolean; + observation_data: boolean; + telemetry_data: boolean; + animal_data: boolean; + artifacts: boolean; +}; + +export const SurveyExportForm = (props: ISurveyExportFormProps) => { + const { onExportConfig } = props; + + const classes = useStyles(); + + const [surveyExportConfig, dispatchSurveyExportConfig] = useReducer( + ( + state: SurveyExportConfig, + action: { type: 'all' | 'none' } | { type: 'toggle'; fieldName: keyof SurveyExportConfig } + ): SurveyExportConfig => { + switch (action.type) { + case 'all': + return { + metadata: true, + sampling_data: true, + observation_data: true, + telemetry_data: true, + animal_data: true, + artifacts: true + }; + case 'none': + return { + metadata: false, + sampling_data: false, + observation_data: false, + telemetry_data: false, + animal_data: false, + artifacts: false + }; + case 'toggle': + return { + ...state, + [action.fieldName]: !state[action.fieldName] + }; + default: + return state; + } + }, + { + metadata: false, + sampling_data: false, + observation_data: false, + telemetry_data: false, + animal_data: false, + artifacts: false + } + ); + + useEffect(() => { + onExportConfig(surveyExportConfig); + }, [onExportConfig, surveyExportConfig]); + + const allChecked = Object.values(surveyExportConfig).every((value) => value === true); + const someChecked = Object.values(surveyExportConfig).some((value) => value === true); + + return ( + + Select Data to Export + + + + Select All + + } + control={ + { + if (allChecked) { + dispatchSurveyExportConfig({ type: 'none' }); + } else { + dispatchSurveyExportConfig({ type: 'all' }); + } + }} + /> + } + /> + + + + + + dispatchSurveyExportConfig({ type: 'toggle', fieldName: 'metadata' })} + /> + } + /> + + + Basic information about the survey, including its name, objectives, and focal species. + + + + + dispatchSurveyExportConfig({ type: 'toggle', fieldName: 'sampling_data' })} + /> + } + /> + + + Survey sampling information (sites, methods, periods, blocks, etc). + + + + + dispatchSurveyExportConfig({ type: 'toggle', fieldName: 'observation_data' })} + /> + } + /> + + + Observation data recorded during this survey. + + + + + dispatchSurveyExportConfig({ type: 'toggle', fieldName: 'telemetry_data' })} + /> + } + /> + + + Telemetry data for all devices registered to this survey. + + + + + dispatchSurveyExportConfig({ type: 'toggle', fieldName: 'animal_data' })} + /> + } + /> + + + Animal data recorded during this survey. + + + + + dispatchSurveyExportConfig({ type: 'toggle', fieldName: 'artifacts' })} + /> + } + /> + + + Survey artifact information (file names, report authors, etc). + + + + + + ); +}; diff --git a/app/src/hooks/api/useSurveyApi.test.ts b/app/src/hooks/api/useSurveyApi.test.ts index 7cd6dd2ff3..940319ecb3 100644 --- a/app/src/hooks/api/useSurveyApi.test.ts +++ b/app/src/hooks/api/useSurveyApi.test.ts @@ -186,4 +186,23 @@ describe('useSurveyApi', () => { expect(result).toEqual(response); }); }); + + describe('exportData', () => { + it('should get critters', async () => { + const mockResponse = { presignedS3Urls: ['signed-url-for:path/to/file/key'] }; + + mock.onPost(`/api/project/${projectId}/survey/${surveyId}/export`).reply(200, mockResponse); + + const result = await useSurveyApi(axios).exportData(projectId, surveyId, { + metadata: true, + sampling_data: false, + observation_data: true, + telemetry_data: true, + animal_data: false, + artifacts: false + }); + + expect(result).toEqual(mockResponse); + }); + }); }); diff --git a/app/src/hooks/api/useSurveyApi.ts b/app/src/hooks/api/useSurveyApi.ts index c2c9929419..18c73924e5 100644 --- a/app/src/hooks/api/useSurveyApi.ts +++ b/app/src/hooks/api/useSurveyApi.ts @@ -4,6 +4,7 @@ import { IReportMetaForm } from 'components/attachments/ReportMetaForm'; import { ISurveyCritter } from 'contexts/animalPageContext'; import { ISurveyAdvancedFilters } from 'features/summary/list-data/survey/SurveysListFilterForm'; import { ICreateCritter } from 'features/surveys/view/survey-animals/animal'; +import { SurveyExportConfig } from 'features/surveys/view/survey-export/SurveyExportForm'; import { ICritterDetailedResponse, ICritterSimpleResponse } from 'interfaces/useCritterApi.interface'; import { IGetReportDetails, IUploadAttachmentResponse } from 'interfaces/useProjectApi.interface'; import { @@ -683,6 +684,24 @@ const useSurveyApi = (axios: AxiosInstance) => { return data; }; + /** + * Initiates a data export for a survey. + * + * @param {number} projectId + * @param {number} surveyId + * @param {SurveyExportConfig} exportConfig + * @return {*} {Promise<{ presignedS3Urls: string[] }>} + */ + const exportData = async ( + projectId: number, + surveyId: number, + exportConfig: SurveyExportConfig + ): Promise<{ presignedS3Urls: string[] }> => { + const { data } = await axios.post(`/api/project/${projectId}/survey/${surveyId}/export`, { config: exportConfig }); + + return data; + }; + return { createSurvey, getSurveyForView, @@ -713,7 +732,8 @@ const useSurveyApi = (axios: AxiosInstance) => { importMarkingsFromCsv, importMeasurementsFromCsv, endDeployment, - deleteDeployment + deleteDeployment, + exportData }; }; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index ba33e3cf6c..0000000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "biohubbc", - "lockfileVersion": 3, - "requires": true, - "packages": {} -}