diff --git a/.github/workflows/ci_check_translations.yml b/.github/workflows/ci_check_translations.yml index 6c0742501c..e30f4b96eb 100644 --- a/.github/workflows/ci_check_translations.yml +++ b/.github/workflows/ci_check_translations.yml @@ -3,9 +3,7 @@ name: check translations on: push: branches: - - production - development - - staging pull_request: concurrency: diff --git a/.github/workflows/ci_cypress_component.yml b/.github/workflows/ci_cypress_component.yml index ca112a41cb..b8982d5e6a 100644 --- a/.github/workflows/ci_cypress_component.yml +++ b/.github/workflows/ci_cypress_component.yml @@ -3,10 +3,7 @@ name: Component testing on: push: branches: - - production - development - - staging - pull_request: concurrency: diff --git a/.github/workflows/ci_e2e_cypress_base.yml b/.github/workflows/ci_e2e_cypress_base.yml index 4d7831b10c..31454fe1d6 100644 --- a/.github/workflows/ci_e2e_cypress_base.yml +++ b/.github/workflows/ci_e2e_cypress_base.yml @@ -3,9 +3,7 @@ name: e2e cypress BASE on: push: branches: - - production - development - - staging pull_request: concurrency: diff --git a/.github/workflows/ci_e2e_cypress_pages.yml b/.github/workflows/ci_e2e_cypress_pages.yml index 3903933385..c44f9f9c28 100644 --- a/.github/workflows/ci_e2e_cypress_pages.yml +++ b/.github/workflows/ci_e2e_cypress_pages.yml @@ -3,9 +3,7 @@ name: e2e cypress PAGES on: push: branches: - - production - development - - staging pull_request: concurrency: diff --git a/.github/workflows/ci_e2e_cypress_ssettings.yml b/.github/workflows/ci_e2e_cypress_ssettings.yml index b92c1727c8..89c0879209 100644 --- a/.github/workflows/ci_e2e_cypress_ssettings.yml +++ b/.github/workflows/ci_e2e_cypress_ssettings.yml @@ -3,9 +3,7 @@ name: e2e cypress SETTINGS on: push: branches: - - production - development - - staging pull_request: concurrency: diff --git a/.github/workflows/ci_e2e_puppeteer.yml b/.github/workflows/ci_e2e_puppeteer.yml index dcfc5cdd2f..b571366770 100644 --- a/.github/workflows/ci_e2e_puppeteer.yml +++ b/.github/workflows/ci_e2e_puppeteer.yml @@ -3,9 +3,7 @@ name: e2e puppeteer on: push: branches: - - production - development - - staging pull_request: concurrency: diff --git a/.github/workflows/ci_eslint.yml b/.github/workflows/ci_eslint.yml index 03fd3b3879..bcc80bb2ac 100644 --- a/.github/workflows/ci_eslint.yml +++ b/.github/workflows/ci_eslint.yml @@ -3,9 +3,7 @@ name: eslint on: push: branches: - - production - development - - staging pull_request: concurrency: diff --git a/.github/workflows/ci_ts_emit_check.yml b/.github/workflows/ci_ts_emit_check.yml index 1368662456..79ba13c775 100644 --- a/.github/workflows/ci_ts_emit_check.yml +++ b/.github/workflows/ci_ts_emit_check.yml @@ -3,9 +3,7 @@ name: check-emitted-types on: push: branches: - - production - development - - staging pull_request: concurrency: diff --git a/.github/workflows/ci_ts_types.yml b/.github/workflows/ci_ts_types.yml index 15e27b0819..d32e0b1f40 100644 --- a/.github/workflows/ci_ts_types.yml +++ b/.github/workflows/ci_ts_types.yml @@ -3,9 +3,7 @@ name: check-types on: push: branches: - - production - development - - staging pull_request: concurrency: diff --git a/.github/workflows/ci_unit_tests.yml b/.github/workflows/ci_unit_tests.yml index cc02c2b0e7..1a6523d774 100644 --- a/.github/workflows/ci_unit_tests.yml +++ b/.github/workflows/ci_unit_tests.yml @@ -3,9 +3,7 @@ name: Unit tests on: push: branches: - - production - development - - staging pull_request: concurrency: diff --git a/app/api/csv/arrangeThesauri.ts b/app/api/csv/arrangeThesauri.ts index f9882e7755..ee0bb890e6 100644 --- a/app/api/csv/arrangeThesauri.ts +++ b/app/api/csv/arrangeThesauri.ts @@ -21,15 +21,12 @@ import { LabelInfoBase } from './typeParsers/select'; import { headerWithLanguage } from './csvDefinitions'; class ArrangeThesauriError extends Error { - source: Error; - row: CSVRow; index: number; constructor(source: Error, row: CSVRow, index: number) { - super(source.message); - this.source = source; + super(source.message, { cause: source }); this.row = row; this.index = index; } @@ -163,9 +160,7 @@ const tryAddingLabel = ( const map = thesauriValueData[id]; if (isStandaloneGroup(map, labelInfo)) { throw new Error( - `The label "${ - labelInfo.label - }" at property "${name}" is a group label in line:\n${JSON.stringify(row)}` + `The label "${labelInfo.label}" at property "${name}" is a group label in line:\n${JSON.stringify(row)}` ); } const { childInfo, parentInfo } = pickParentChild(labelInfo); diff --git a/app/api/entities.v2/contracts/EntitiesDataSource.ts b/app/api/entities.v2/contracts/EntitiesDataSource.ts index 4a9195a18b..5f637a26e7 100644 --- a/app/api/entities.v2/contracts/EntitiesDataSource.ts +++ b/app/api/entities.v2/contracts/EntitiesDataSource.ts @@ -1,5 +1,5 @@ import { ResultSet } from 'api/common.v2/contracts/ResultSet'; -import { Entity, MetadataValue } from '../model/Entity'; +import { Entity, EntityMetadata, MetadataValue } from '../model/Entity'; type MarkAsChangedCriteria = { template: string } | { sharedId: string }; type MarkAsChangedData = { property: string } | { properties: string[] }; @@ -8,7 +8,11 @@ export type MarkAsChangedItems = MarkAsChangedCriteria & MarkAsChangedData; export interface EntitiesDataSource { updateObsoleteMetadataValues( id: Entity['_id'], - values: Record + values: Record + ): Promise; + updateMetadataValues( + id: Entity['_id'], + values: Record ): Promise; entitiesExist(sharedIds: string[]): Promise; getByIds(sharedIds: string[], language?: string): ResultSet; diff --git a/app/api/entities.v2/database/MongoEntitiesDataSource.ts b/app/api/entities.v2/database/MongoEntitiesDataSource.ts index 48498120fc..3a2de5c661 100644 --- a/app/api/entities.v2/database/MongoEntitiesDataSource.ts +++ b/app/api/entities.v2/database/MongoEntitiesDataSource.ts @@ -1,15 +1,16 @@ import { ResultSet } from 'api/common.v2/contracts/ResultSet'; import { MongoDataSource } from 'api/common.v2/database/MongoDataSource'; +import { MongoIdHandler } from 'api/common.v2/database/MongoIdGenerator'; import { MongoResultSet } from 'api/common.v2/database/MongoResultSet'; import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; -import { MongoIdHandler } from 'api/common.v2/database/MongoIdGenerator'; +import entities from 'api/entities/entities'; import { MongoSettingsDataSource } from 'api/settings.v2/database/MongoSettingsDataSource'; import { MongoTemplatesDataSource } from 'api/templates.v2/database/MongoTemplatesDataSource'; import { Db } from 'mongodb'; import { EntitiesDataSource } from '../contracts/EntitiesDataSource'; +import { Entity, EntityMetadata, MetadataValue } from '../model/Entity'; import { EntityMappers } from './EntityMapper'; import { EntityDBO, EntityJoinTemplate } from './schemas/EntityTypes'; -import { Entity, MetadataValue } from '../model/Entity'; export class MongoEntitiesDataSource extends MongoDataSource @@ -123,9 +124,30 @@ export class MongoEntitiesDataSource return new MongoResultSet(result, entity => entity.sharedId); } + // eslint-disable-next-line class-methods-use-this + async updateMetadataValues( + id: Entity['_id'], + values: Record + ) { + // This is using V1 so that it gets denormalized to speed up development + // this is a hack and should be changed as soon as we finish AT + const entityToModify = await entities.getById(id); + if (!entityToModify) { + throw new Error(`entity does not exists: ${id}`); + } + + Object.entries(values).forEach(([propertyName, metadataValues]) => { + entityToModify.metadata = entityToModify.metadata || {}; + // @ts-ignore + entityToModify.metadata[propertyName] = metadataValues; + }); + + await entities.save(entityToModify, { user: {}, language: entityToModify.language }); + } + async updateObsoleteMetadataValues( id: Entity['_id'], - values: Record + values: Record ): Promise { const stream = this.createBulkStream(); diff --git a/app/api/entities.v2/model/Entity.ts b/app/api/entities.v2/model/Entity.ts index 5b4749a427..6491c59c08 100644 --- a/app/api/entities.v2/model/Entity.ts +++ b/app/api/entities.v2/model/Entity.ts @@ -1,5 +1,7 @@ +type MetadataValue = unknown; + type BaseMetadataValue = { - value: unknown; + value: MetadataValue; label: string; }; @@ -8,9 +10,9 @@ type InheritedResultValue = BaseMetadataValue & { inheritedType: string; }; -type MetadataValue = BaseMetadataValue | InheritedResultValue; +type EntityMetadata = BaseMetadataValue | InheritedResultValue; -type Metadata = Record; +type Metadata = Record; export class Entity { readonly _id: string; @@ -45,4 +47,5 @@ export class Entity { this.obsoleteMetadata = obsoleteMetadata ?? []; } } -export type { Metadata, MetadataValue }; + +export type { Metadata, EntityMetadata, MetadataValue }; diff --git a/app/api/entities.v2/services/EntityRelationshipsUpdateService.ts b/app/api/entities.v2/services/EntityRelationshipsUpdateService.ts index ef040328c2..fccedb5c7f 100644 --- a/app/api/entities.v2/services/EntityRelationshipsUpdateService.ts +++ b/app/api/entities.v2/services/EntityRelationshipsUpdateService.ts @@ -3,7 +3,7 @@ import { Template } from 'api/templates.v2/model/Template'; import { RelationshipProperty } from 'api/templates.v2/model/RelationshipProperty'; import { RelationshipsDataSource } from 'api/relationships.v2/contracts/RelationshipsDataSource'; import { TemplatesDataSource } from 'api/templates.v2/contracts/TemplatesDataSource'; -import { Entity, MetadataValue } from '../model/Entity'; +import { Entity, EntityMetadata } from '../model/Entity'; import { EntitiesDataSource } from '../contracts/EntitiesDataSource'; export class EntityRelationshipsUpdateService { @@ -40,7 +40,7 @@ export class EntityRelationshipsUpdateService { private async transformToDenormalizedData( property: RelationshipProperty, queryResult: Entity[] - ): Promise { + ): Promise { return Promise.all( queryResult.map(async entity => ({ value: entity.sharedId, @@ -65,7 +65,7 @@ export class EntityRelationshipsUpdateService { await this.entitiesDataSource.getByIds(sharedIds).forEach(async entity => { template = await this.findTemplate(template, entity.template); - const metadataToUpdate: Record = {}; + const metadataToUpdate: Record = {}; await Promise.all( template.properties.map(async property => { diff --git a/app/api/externalIntegrations.v2/automaticTranslation/SaveEntityTranslations.ts b/app/api/externalIntegrations.v2/automaticTranslation/SaveEntityTranslations.ts new file mode 100644 index 0000000000..06671d0f44 --- /dev/null +++ b/app/api/externalIntegrations.v2/automaticTranslation/SaveEntityTranslations.ts @@ -0,0 +1,69 @@ +import { EntitiesDataSource } from 'api/entities.v2/contracts/EntitiesDataSource'; +import { TemplatesDataSource } from 'api/templates.v2/contracts/TemplatesDataSource'; +import { ATTranslationResultValidator } from './contracts/ATTranslationResultValidator'; +import { InvalidInputDataFormat } from './errors/generateATErrors'; +import { TranslationResult } from './types/TranslationResult'; + +export class SaveEntityTranslations { + static AITranslatedText = '(AI translated)'; + + private entitiesDS: EntitiesDataSource; + + private templatesDS: TemplatesDataSource; + + private validator: ATTranslationResultValidator; + + constructor( + templatesDS: TemplatesDataSource, + entitiesDS: EntitiesDataSource, + validator: ATTranslationResultValidator + ) { + this.entitiesDS = entitiesDS; + this.templatesDS = templatesDS; + this.validator = validator; + } + + async execute(translationResult: TranslationResult | unknown) { + if (!this.validator.validate(translationResult)) { + throw new InvalidInputDataFormat(this.validator.getErrors()[0]); + } + + const [, entitySharedId, propertyId] = translationResult.key; + + const property = await this.getProperty(entitySharedId, propertyId); + + const entities = this.entitiesDS.getByIds([entitySharedId]); + + await entities.forEach(async oneEntity => { + const translation = translationResult.translations.find( + t => t.language === oneEntity.language + ); + if (translation) { + await this.entitiesDS.updateMetadataValues(oneEntity._id, { + [property.name]: [ + { value: `${SaveEntityTranslations.AITranslatedText} ${translation.text}` }, + ], + }); + } + }); + } + + private async getProperty(entitySharedId: string, propertyId: string) { + const entity = await this.entitiesDS.getByIds([entitySharedId]).first(); + if (!entity) { + throw new Error('Entity does not exists'); + } + + const template = await this.templatesDS.getById(entity.template); + if (!template) { + throw new Error('Template does not exists'); + } + + const property = template.properties.find(p => p.id === propertyId); + + if (!property) { + throw new Error('Property does not exists'); + } + return property; + } +} diff --git a/app/api/externalIntegrations.v2/automaticTranslation/contracts/ATTranslationResultValidator.ts b/app/api/externalIntegrations.v2/automaticTranslation/contracts/ATTranslationResultValidator.ts new file mode 100644 index 0000000000..f368e257c3 --- /dev/null +++ b/app/api/externalIntegrations.v2/automaticTranslation/contracts/ATTranslationResultValidator.ts @@ -0,0 +1,6 @@ +import { TranslationResult } from '../types/TranslationResult'; + +export interface ATTranslationResultValidator { + getErrors(): string[]; + validate(data: unknown): data is TranslationResult; +} diff --git a/app/api/externalIntegrations.v2/automaticTranslation/infrastructure/AJVTranslationResultValidator.ts b/app/api/externalIntegrations.v2/automaticTranslation/infrastructure/AJVTranslationResultValidator.ts new file mode 100644 index 0000000000..f27daaff91 --- /dev/null +++ b/app/api/externalIntegrations.v2/automaticTranslation/infrastructure/AJVTranslationResultValidator.ts @@ -0,0 +1,40 @@ +import { Ajv } from 'ajv'; +import { JTDSchemaType } from 'ajv/dist/core'; +import { TranslationResult } from '../types/TranslationResult'; +import { ATTranslationResultValidator } from '../contracts/ATTranslationResultValidator'; + +const schema: JTDSchemaType = { + additionalProperties: false, + properties: { + key: { elements: { type: 'string' } }, + text: { type: 'string' }, + language_from: { type: 'string' }, + languages_to: { elements: { type: 'string' } }, + translations: { + elements: { + properties: { + text: { type: 'string' }, + language: { type: 'string' }, + success: { type: 'boolean' }, + error_message: { type: 'string' }, + }, + }, + }, + }, +}; + +export class AJVTranslationResultValidator implements ATTranslationResultValidator { + private errors: string[] = []; + + getErrors() { + return this.errors; + } + + validate(data: unknown) { + const ajv = new Ajv({ strict: false }); + const validate = ajv.compile(schema); + const result = validate(data); + this.errors = validate.errors ? validate.errors?.map(e => JSON.stringify(e.params)) : []; + return result; + } +} diff --git a/app/api/externalIntegrations.v2/automaticTranslation/specs/SaveEntityTranslations.spec.ts b/app/api/externalIntegrations.v2/automaticTranslation/specs/SaveEntityTranslations.spec.ts new file mode 100644 index 0000000000..fd7f7c8a29 --- /dev/null +++ b/app/api/externalIntegrations.v2/automaticTranslation/specs/SaveEntityTranslations.spec.ts @@ -0,0 +1,160 @@ +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; +import { DefaultEntitiesDataSource } from 'api/entities.v2/database/data_source_defaults'; +import { DefaultTemplatesDataSource } from 'api/templates.v2/database/data_source_defaults'; +import { getFixturesFactory } from 'api/utils/fixturesFactory'; +import { testingEnvironment } from 'api/utils/testingEnvironment'; +import testingDB, { DBFixture } from 'api/utils/testing_db'; +import { LanguageISO6391 } from 'shared/types/commonTypes'; +import { SaveEntityTranslations } from '../SaveEntityTranslations'; +import { InvalidInputDataFormat } from '../errors/generateATErrors'; +import { AJVTranslationResultValidator } from '../infrastructure/AJVTranslationResultValidator'; +import { TranslationResult } from '../types/TranslationResult'; + +const factory = getFixturesFactory(); + +beforeEach(async () => { + const fixtures = { + templates: [ + factory.template('template1', [ + { + _id: factory.id('propertyName'), + name: 'propertyName', + type: 'text', + label: 'Prop 1', + }, + ]), + ], + entities: [ + ...factory.entityInMultipleLanguages(['en', 'pt', 'es'], 'entity', 'template1', { + propertyName: [{ value: 'original text' }], + }), + ], + settings: [ + { + languages: [ + { label: 'en', key: 'en' as LanguageISO6391, default: true }, + { label: 'pt', key: 'pt' as LanguageISO6391 }, + { label: 'es', key: 'es' as LanguageISO6391 }, + ], + }, + ], + }; + await testingEnvironment.setUp(fixtures); +}); + +afterAll(async () => { + await testingEnvironment.tearDown(); +}); + +describe('GenerateAutomaticTranslationConfig', () => { + let saveEntityTranslations: SaveEntityTranslations; + + beforeEach(() => { + const transactionManager = DefaultTransactionManager(); + saveEntityTranslations = new SaveEntityTranslations( + DefaultTemplatesDataSource(transactionManager), + DefaultEntitiesDataSource(transactionManager), + new AJVTranslationResultValidator() + ); + }); + + it('should validate input has proper shape at runtime', async () => { + const invalidConfig = { invalid_prop: true }; + await expect(saveEntityTranslations.execute(invalidConfig)).rejects.toEqual( + new InvalidInputDataFormat('{"additionalProperty":"invalid_prop"}') + ); + }); + + it(`should save entity with translated text with prepended "${SaveEntityTranslations.AITranslatedText}"`, async () => { + const translationResult: TranslationResult = { + key: ['tenant', 'entity', factory.idString('propertyName')], + text: 'original text', + language_from: 'en', + languages_to: ['es', 'pt'], + translations: [ + { text: 'texto original', language: 'es', success: true, error_message: '' }, + { text: 'texto original (pt)', language: 'pt', success: true, error_message: '' }, + ], + }; + + await saveEntityTranslations.execute(translationResult); + + const entities = + (await testingDB.mongodb?.collection('entities').find({ sharedId: 'entity' }).toArray()) || + []; + + expect(entities.find(e => e.language === 'es')).toMatchObject({ + metadata: { + propertyName: [{ value: `${SaveEntityTranslations.AITranslatedText} texto original` }], + }, + }); + + expect(entities.find(e => e.language === 'pt')).toMatchObject({ + metadata: { + propertyName: [{ value: `${SaveEntityTranslations.AITranslatedText} texto original (pt)` }], + }, + }); + + expect(entities.find(e => e.language === 'en')).toMatchObject({ + metadata: { propertyName: [{ value: 'original text' }] }, + }); + }); + + it('should denormalize text property on related entities', async () => { + const fixtures: DBFixture = { + settings: [ + { + languages: [{ label: 'es', key: 'es' as LanguageISO6391, default: true }], + }, + ], + templates: [ + factory.template('templateA', [factory.inherit('relationship', 'templateB', 'text')]), + factory.template('templateB', [factory.property('text')]), + ], + entities: [ + factory.entity('A1', 'templateA', { + relationship: [factory.metadataValue('B1')], + }), + factory.entity('B1', 'templateB', {}, { title: 'B1title' }), + ], + }; + + await testingEnvironment.setUp(fixtures); + + const translationResult: TranslationResult = { + key: ['tenant', 'B1', factory.idString('text')], + text: 'texto original', + language_from: 'es', + languages_to: ['en'], + translations: [{ text: 'original text', language: 'en', success: true, error_message: '' }], + }; + + await saveEntityTranslations.execute(translationResult); + + const entities = (await testingDB.mongodb?.collection('entities').find().toArray()) || []; + + expect(entities).toMatchObject([ + { + sharedId: 'A1', + metadata: { + relationship: [ + { + value: 'B1', + label: 'B1title', + inheritedValue: [ + { value: `${SaveEntityTranslations.AITranslatedText} original text` }, + ], + }, + ], + }, + }, + { + title: 'B1title', + sharedId: 'B1', + metadata: { + text: [{ value: `${SaveEntityTranslations.AITranslatedText} original text` }], + }, + }, + ]); + }); +}); diff --git a/app/api/externalIntegrations.v2/automaticTranslation/types/TranslationResult.ts b/app/api/externalIntegrations.v2/automaticTranslation/types/TranslationResult.ts new file mode 100644 index 0000000000..a53b8b3b95 --- /dev/null +++ b/app/api/externalIntegrations.v2/automaticTranslation/types/TranslationResult.ts @@ -0,0 +1,12 @@ +export interface TranslationResult { + key: string[]; + text: string; + language_from: string; + languages_to: string[]; + translations: { + text: string; + language: string; + success: boolean; + error_message: string; + }[]; +} diff --git a/app/api/files/S3Storage.ts b/app/api/files/S3Storage.ts index 6038e9755e..a456ef3110 100644 --- a/app/api/files/S3Storage.ts +++ b/app/api/files/S3Storage.ts @@ -6,23 +6,30 @@ import { S3Client, _Object, } from '@aws-sdk/client-s3'; -import { NodeHttpHandler } from '@smithy/node-http-handler'; import { config } from 'api/config'; +class S3TimeoutError extends Error { + constructor(cause: Error) { + super(cause.message, { cause }); + } +} + +const catchS3Errors = async (cb: () => Promise): Promise => { + try { + return await cb(); + } catch (err) { + if (err.name === 'TimeoutError') { + throw new S3TimeoutError(err); + } + throw err; + } +}; + export class S3Storage { private client: S3Client; - constructor() { - this.client = new S3Client({ - requestHandler: new NodeHttpHandler({ - socketTimeout: 30000, - }), - apiVersion: 'latest', - region: 'placeholder-region', - endpoint: config.s3.endpoint, - credentials: config.s3.credentials, - forcePathStyle: true, - }); + constructor(s3Client: S3Client) { + this.client = s3Client; } static bucketName() { @@ -30,20 +37,22 @@ export class S3Storage { } async upload(key: string, body: Buffer) { - return this.client.send( - new PutObjectCommand({ Bucket: S3Storage.bucketName(), Key: key, Body: body }) + return catchS3Errors(async () => + this.client.send( + new PutObjectCommand({ Bucket: S3Storage.bucketName(), Key: key, Body: body }) + ) ); } async get(key: string) { - const response = await this.client.send( - new GetObjectCommand({ - Bucket: S3Storage.bucketName(), - Key: key, - }) + return catchS3Errors(async () => + this.client.send( + new GetObjectCommand({ + Bucket: S3Storage.bucketName(), + Key: key, + }) + ) ); - - return response; } async list(prefix?: string) { @@ -61,21 +70,26 @@ export class S3Storage { return response.NextContinuationToken; }; - let continuationToken = await requestNext(); - while (continuationToken) { - // eslint-disable-next-line no-await-in-loop - continuationToken = await requestNext(continuationToken); - } - - return objects; + return catchS3Errors(async () => { + let continuationToken = await requestNext(); + while (continuationToken) { + // eslint-disable-next-line no-await-in-loop + continuationToken = await requestNext(continuationToken); + } + return objects; + }); } async delete(key: string) { - await this.client.send( - new DeleteObjectCommand({ - Bucket: S3Storage.bucketName(), - Key: key, - }) + return catchS3Errors(async () => + this.client.send( + new DeleteObjectCommand({ + Bucket: S3Storage.bucketName(), + Key: key, + }) + ) ); } } + +export { S3TimeoutError }; diff --git a/app/api/files/specs/s3Storage.spec.ts b/app/api/files/specs/s3Storage.spec.ts new file mode 100644 index 0000000000..35c601fe97 --- /dev/null +++ b/app/api/files/specs/s3Storage.spec.ts @@ -0,0 +1,45 @@ +import { S3Storage, S3TimeoutError } from '../S3Storage'; + +let s3Storage: S3Storage; + +class S3TimeoutClient { + // eslint-disable-next-line class-methods-use-this + send() { + const error = new Error(); + error.name = 'TimeoutError'; + throw error; + } +} + +describe('s3Storage', () => { + beforeAll(async () => { + // @ts-ignore + s3Storage = new S3Storage(new S3TimeoutClient()); + }); + + describe('get', () => { + it('should throw S3TimeoutError on timeout', async () => { + await expect(s3Storage.get('dummy_key')).rejects.toBeInstanceOf(S3TimeoutError); + }); + }); + + describe('upload', () => { + it('should throw S3TimeoutError on timeout', async () => { + await expect( + s3Storage.upload('dummy_key', Buffer.from('dummy buffer', 'utf-8')) + ).rejects.toBeInstanceOf(S3TimeoutError); + }); + }); + + describe('delete', () => { + it('should throw S3TimeoutError on timeout', async () => { + await expect(s3Storage.delete('dummy_key')).rejects.toBeInstanceOf(S3TimeoutError); + }); + }); + + describe('list', () => { + it('should throw S3TimeoutError on timeout', async () => { + await expect(s3Storage.list()).rejects.toBeInstanceOf(S3TimeoutError); + }); + }); +}); diff --git a/app/api/files/storage.ts b/app/api/files/storage.ts index 2dfac22938..ba43d25b75 100644 --- a/app/api/files/storage.ts +++ b/app/api/files/storage.ts @@ -1,6 +1,7 @@ -import { NoSuchKey } from '@aws-sdk/client-s3'; +import { NoSuchKey, S3Client } from '@aws-sdk/client-s3'; import { config } from 'api/config'; import { tenants } from 'api/tenants'; +import { NodeHttpHandler } from '@smithy/node-http-handler'; // eslint-disable-next-line node/no-restricted-import import { createReadStream, createWriteStream } from 'fs'; // eslint-disable-next-line node/no-restricted-import @@ -9,6 +10,7 @@ import path from 'path'; import { FileType } from 'shared/types/fileType'; import { Readable } from 'stream'; import { pipeline } from 'stream/promises'; +import { FileNotFound } from './FileNotFound'; import { activityLogPath, attachmentsPath, @@ -18,14 +20,24 @@ import { uploadsPath, } from './filesystem'; import { S3Storage } from './S3Storage'; -import { FileNotFound } from './FileNotFound'; type FileTypes = NonNullable | 'activitylog' | 'segmentation'; let s3Instance: S3Storage; const s3 = () => { if (config.s3.endpoint && !s3Instance) { - s3Instance = new S3Storage(); + s3Instance = new S3Storage( + new S3Client({ + requestHandler: new NodeHttpHandler({ + socketTimeout: 30000, + }), + apiVersion: 'latest', + region: 'placeholder-region', + endpoint: config.s3.endpoint, + credentials: config.s3.credentials, + forcePathStyle: true, + }) + ); } return s3Instance; }; diff --git a/app/api/preserve/specs/preserve.spec.ts b/app/api/preserve/specs/preserve.spec.ts index 28a1769f82..3dfbd0168a 100644 --- a/app/api/preserve/specs/preserve.spec.ts +++ b/app/api/preserve/specs/preserve.spec.ts @@ -73,7 +73,7 @@ describe('Preserve', () => { ]; await testingEnvironment.setUp({ ...fixtures, settings: newSettings }, 'preserve-index'); - await expect(Preserve.setup('en', { _id: 'someid' })).rejects.toEqual({ + await expect(Preserve.setup('en', { _id: 'someid' })).rejects.toMatchObject({ message: 'Preserve configuration not found', code: 402, }); diff --git a/app/api/services/convertToPDF/specs/convertToPdfWorker.spec.ts b/app/api/services/convertToPDF/specs/convertToPdfWorker.spec.ts index 0b8fa8cb62..19b1d2ef08 100644 --- a/app/api/services/convertToPDF/specs/convertToPdfWorker.spec.ts +++ b/app/api/services/convertToPDF/specs/convertToPdfWorker.spec.ts @@ -22,13 +22,13 @@ describe('convertToPdfWorker', () => { const recreateRedisQueue = async () => { try { - await redisSMQ.deleteQueueAsync({ qname: 'convert-to-pdf_results' }); + await redisSMQ.deleteQueueAsync({ qname: 'development_convert-to-pdf_results' }); } catch (err) { if (err instanceof Error && err.name !== 'queueNotFound') { throw err; } } - await redisSMQ.createQueueAsync({ qname: 'convert-to-pdf_results' }); + await redisSMQ.createQueueAsync({ qname: 'development_convert-to-pdf_results' }); }; beforeAll(async () => { @@ -88,7 +88,7 @@ describe('convertToPdfWorker', () => { }; await redisSMQ.sendMessageAsync({ - qname: 'convert-to-pdf_results', + qname: 'development_convert-to-pdf_results', message: JSON.stringify(message), }); }); @@ -162,7 +162,7 @@ describe('convertToPdfWorker', () => { jest.spyOn(handleError, 'handleError').mockImplementationOnce(() => {}); await redisSMQ.sendMessageAsync({ - qname: 'convert-to-pdf_results', + qname: 'development_convert-to-pdf_results', message: JSON.stringify(message), }); diff --git a/app/api/services/informationextraction/getFiles.ts b/app/api/services/informationextraction/getFiles.ts index d0ef637b72..da0f66fa4b 100644 --- a/app/api/services/informationextraction/getFiles.ts +++ b/app/api/services/informationextraction/getFiles.ts @@ -21,7 +21,7 @@ import languages from 'shared/languages'; import { ensure } from 'shared/tsUtils'; const BATCH_SIZE = 50; -const MAX_TRAINING_FILES_NUMBER = 500; +const MAX_TRAINING_FILES_NUMBER = 2000; type PropertyValue = string | Array<{ value: string; label: string }>; diff --git a/app/api/services/tasksmanager/TaskManager.ts b/app/api/services/tasksmanager/TaskManager.ts index f9c57b9254..e6dcf9166a 100644 --- a/app/api/services/tasksmanager/TaskManager.ts +++ b/app/api/services/tasksmanager/TaskManager.ts @@ -52,8 +52,8 @@ export class TaskManager< constructor(service: Service) { this.service = service; - this.taskQueue = `${service.serviceName}_tasks`; - this.resultsQueue = `${service.serviceName}_results`; + this.taskQueue = `${config.ENVIRONMENT}_${service.serviceName}_tasks`; + this.resultsQueue = `${config.ENVIRONMENT}_${service.serviceName}_results`; const redisUrl = `redis://${config.redis.host}:${config.redis.port}`; this.redisClient = Redis.createClient(redisUrl); this.redisSMQ = new RedisSMQ({ client: this.redisClient }); diff --git a/app/api/services/tasksmanager/specs/ExternalDummyService.ts b/app/api/services/tasksmanager/specs/ExternalDummyService.ts index 58b6371fac..8243048b6b 100644 --- a/app/api/services/tasksmanager/specs/ExternalDummyService.ts +++ b/app/api/services/tasksmanager/specs/ExternalDummyService.ts @@ -122,16 +122,16 @@ export class ExternalDummyService { } async resetQueue() { - await this.deleteQueue(`${this.serviceName}_tasks`); - await this.deleteQueue(`${this.serviceName}_results`); + await this.deleteQueue(`development_${this.serviceName}_tasks`); + await this.deleteQueue(`development_${this.serviceName}_results`); - await this.createQueue(`${this.serviceName}_tasks`); - await this.createQueue(`${this.serviceName}_results`); + await this.createQueue(`development_${this.serviceName}_tasks`); + await this.createQueue(`development_${this.serviceName}_results`); } async readFirstTaskMessage() { const message: RedisSMQ.QueueMessage | {} = await this.rsmq.receiveMessageAsync({ - qname: `${this.serviceName}_tasks`, + qname: `development_${this.serviceName}_tasks`, }); const queueMessage = message as QueueMessage; @@ -140,7 +140,7 @@ export class ExternalDummyService { } await this.rsmq.deleteMessageAsync({ - qname: `${this.serviceName}_tasks`, + qname: `development_${this.serviceName}_tasks`, id: queueMessage.id, }); @@ -186,7 +186,7 @@ export class ExternalDummyService { async sendFinishedMessage(task: ResultsMessage) { try { await this.rsmq.sendMessageAsync({ - qname: `${this.serviceName}_results`, + qname: `development_${this.serviceName}_results`, message: JSON.stringify(task), }); } catch (err) { diff --git a/app/api/templates/specs/templates.spec.js b/app/api/templates/specs/templates.spec.js index bce97c9c96..d18ba12f6c 100644 --- a/app/api/templates/specs/templates.spec.js +++ b/app/api/templates/specs/templates.spec.js @@ -206,7 +206,7 @@ describe('templates', () => { await templates.save(changedTemplate); throw new Error('properties have swaped names, should have failed with an error'); } catch (error) { - expect(error).toEqual({ code: 400, message: "Properties can't swap names: text" }); + expect(error).toMatchObject({ code: 400, message: "Properties can't swap names: text" }); } }); diff --git a/app/api/users/specs/users.spec.js b/app/api/users/specs/users.spec.js index 7daaed279a..45da5465a4 100644 --- a/app/api/users/specs/users.spec.js +++ b/app/api/users/specs/users.spec.js @@ -121,7 +121,7 @@ describe('Users', () => { _id: userId.toString(), username: 'user name', }; - await expect(users.save(userdata, currentUser)).rejects.toEqual({ + await expect(users.save(userdata, currentUser)).rejects.toMatchObject({ code: 400, message: 'Usernames can not contain spaces.', }); @@ -259,7 +259,7 @@ describe('Users', () => { role: 'editor', groups: [], }; - await expect(users.newUser(userdata, domain)).rejects.toEqual({ + await expect(users.newUser(userdata, domain)).rejects.toMatchObject({ code: 400, message: 'Usernames can not contain spaces.', }); @@ -587,16 +587,13 @@ describe('Users', () => { }); describe('when the user does not exist with that email', () => { - it('should not create the entry in the database, should not send a mail, and return an error.', async () => { + it('should not create the entry in the database, should not send a mail, and return nothing', async () => { jest.spyOn(Date, 'now').mockReturnValue(1000); const key = unlockCode.generateUnlockCode(); let response; - try { - response = await users.recoverPassword('false@email.com'); - } catch (error) { - expect(error.code).toBe(403); - response = await passwordRecoveriesModel.get({ key }); - } + response = await users.recoverPassword('false@email.com'); + expect(response).toBe(undefined); + response = await passwordRecoveriesModel.get({ key }); expect(response.length).toBe(0); }); }); diff --git a/app/api/users/users.js b/app/api/users/users.js index f9f44d5827..523760c682 100644 --- a/app/api/users/users.js +++ b/app/api/users/users.js @@ -321,7 +321,7 @@ export default { }); } - return Promise.reject(createError('User not found', 403)); + return undefined; }); }, diff --git a/app/api/utils/Error.js b/app/api/utils/Error.js index 045d718693..3fb74d8e01 100644 --- a/app/api/utils/Error.js +++ b/app/api/utils/Error.js @@ -1,11 +1,18 @@ -export default function (message, code = 500) { +/** + * @deprecated + */ +export default function (message, code = 500, logLevel = 'debug') { + if (code === 500) { + logLevel = 'error'; + } + if (message.ajv) { - return { message: message.message, ...message, code }; + return { message: message.message, ...message, code, logLevel }; } if (message instanceof Error) { - return { message: message.message, stack: message.stack, code, original: message }; + return { message: message.message, stack: message.stack, code, original: message, logLevel }; } - return { message, code }; + return { message, code, logLevel }; } diff --git a/app/api/utils/handleError.js b/app/api/utils/handleError.js index fa4750f183..044121352a 100644 --- a/app/api/utils/handleError.js +++ b/app/api/utils/handleError.js @@ -1,9 +1,12 @@ -import { legacyLogger } from 'api/log'; import Ajv from 'ajv'; -import { createError } from 'api/utils/index'; -import { appContext } from 'api/utils/AppContext'; import { UnauthorizedError } from 'api/authorization.v2/errors/UnauthorizedError'; import { ValidationError } from 'api/common.v2/validation/ValidationError'; +import { FileNotFound } from 'api/files/FileNotFound'; +import { S3TimeoutError } from 'api/files/S3Storage'; +import { legacyLogger } from 'api/log'; +import { appContext } from 'api/utils/AppContext'; +import { createError } from 'api/utils/index'; +import util from 'node:util'; const ajvPrettifier = error => { const errorMessage = [error.message]; @@ -58,39 +61,57 @@ const prettifyError = (error, { req = {}, uncaught = false } = {}) => { let result = error; if (error instanceof Error) { - result = { code: 500, message: error.stack }; + result = { code: 500, message: util.inspect(error), logLevel: 'error' }; + } + + if (error instanceof S3TimeoutError) { + result = { code: 408, message: util.inspect(error), logLevel: 'debug' }; } if (error instanceof Ajv.ValidationError) { - result = { code: 422, message: error.message, validations: error.errors }; + result = { code: 422, message: error.message, validations: error.errors, logLevel: 'debug' }; } if (error.name === 'ValidationError') { - result = { code: 422, message: error.message, validations: error.properties }; + result = { + code: 422, + message: error.message, + validations: error.properties, + logLevel: 'debug', + }; } if (error instanceof ValidationError) { - result = { code: 422, message: error.message, validations: error.errors }; + result = { code: 422, message: error.message, validations: error.errors, logLevel: 'debug' }; } if (error instanceof UnauthorizedError) { - result = { code: 401, message: error.message }; + result = { code: 401, message: error.message, logLevel: 'debug' }; + } + + if (error instanceof FileNotFound) { + result = { code: 404, message: error.message, logLevel: 'debug' }; } if (error.name === 'MongoError') { result.code = 500; + result.logLevel = 'error'; } if (error.message && error.message.match(/Cast to ObjectId failed for value/)) { result.code = 400; + result.logLevel = 'debug'; } if (error.message && error.message.match(/rison decoder error/)) { result.code = 400; + result.logLevel = 'debug'; } if (uncaught) { result.message = `uncaught exception or unhandled rejection, Node process finished !!\n ${result.message}`; + result.logLevel = 'error'; + result.code = 500; } const obfuscatedRequest = obfuscateCredentials(req); @@ -119,11 +140,12 @@ const getErrorMessage = (data, error) => { const sendLog = (data, error, errorOptions) => { const messageToLog = getErrorMessage(data, error); - if (data.code === 500) { - legacyLogger.error(messageToLog, errorOptions); - } else if (data.code === 400) { + if (data.logLevel === 'debug') { legacyLogger.debug(messageToLog, errorOptions); + return; } + + legacyLogger.error(messageToLog, errorOptions); }; function setRequestId(result) { diff --git a/app/api/utils/specs/__snapshots__/handleError.spec.js.snap b/app/api/utils/specs/__snapshots__/handleError.spec.js.snap index b52d62ba99..6fb41686ef 100644 --- a/app/api/utils/specs/__snapshots__/handleError.spec.js.snap +++ b/app/api/utils/specs/__snapshots__/handleError.spec.js.snap @@ -3,6 +3,7 @@ exports[`handleError errors by type when error is created with createError should return the error 1`] = ` Object { "code": 400, + "logLevel": "debug", "message": "test error", "prettyMessage": " test error", diff --git a/app/api/utils/specs/handleError.spec.js b/app/api/utils/specs/handleError.spec.js index bd7774956d..840486b144 100644 --- a/app/api/utils/specs/handleError.spec.js +++ b/app/api/utils/specs/handleError.spec.js @@ -1,8 +1,10 @@ -import { createError } from 'api/utils'; import { legacyLogger } from 'api/log'; +import { createError } from 'api/utils'; import { errors as elasticErrors } from '@elastic/elasticsearch'; +import { S3TimeoutError } from 'api/files/S3Storage'; import { appContext } from 'api/utils/AppContext'; +import util from 'node:util'; import { handleError, prettifyError } from '../handleError'; const contextRequestId = '1234'; @@ -18,6 +20,17 @@ describe('handleError', () => { }); describe('errors by type', () => { + describe('and is instance of S3TimeoutError', () => { + it('should be a debug logLevel and a 408 http code', () => { + const errorInstance = new S3TimeoutError(new Error('timeout')); + const error = handleError(errorInstance); + expect(error).toMatchObject({ + code: 408, + logLevel: 'debug', + }); + expect(legacyLogger.debug.mock.calls[0][0]).toContain('timeout'); + }); + }); describe('when error is instance of Error', () => { it('should return the error with 500 code without the original error and error stack', () => { const errorInstance = new Error('error'); @@ -36,7 +49,7 @@ describe('handleError', () => { handleError(error); expect(legacyLogger.error).toHaveBeenCalledWith( - `requestId: ${contextRequestId} \n${error.stack} + `requestId: ${contextRequestId} \n${util.inspect(error)} original error: { "name": "ConnectionError", "meta": { diff --git a/app/react/App/styles/globals.css b/app/react/App/styles/globals.css index 9d16ab92cb..503dc0a90c 100644 --- a/app/react/App/styles/globals.css +++ b/app/react/App/styles/globals.css @@ -1,5 +1,5 @@ /* -! tailwindcss v3.4.11 | MIT License | https://tailwindcss.com +! tailwindcss v3.4.10 | MIT License | https://tailwindcss.com */ /* diff --git a/app/react/Markdown/MarkdownViewer.js b/app/react/Markdown/MarkdownViewer.js index 8561a3591e..bbf5ac85cb 100644 --- a/app/react/Markdown/MarkdownViewer.js +++ b/app/react/Markdown/MarkdownViewer.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import sanitizeHtml from 'sanitize-html'; import { risonDecodeOrIgnore } from 'app/utils'; import { Translate } from 'app/I18N'; import { MarkdownLink, SearchBox, MarkdownMedia, ItemList } from './components'; @@ -7,6 +8,7 @@ import CustomHookComponents from './CustomHooks'; import markdownToReact from './markdownToReact'; import { ValidatedElement } from './ValidatedElement'; +import { visualizationHtmlTags } from './utils'; class MarkdownViewer extends Component { static errorHtml(index, message) { @@ -107,8 +109,15 @@ class MarkdownViewer extends Component { return false; } + const sanitizedMarkdown = !this.props.sanitized + ? this.props.markdown + : sanitizeHtml(this.props.markdown, { + allowedTags: visualizationHtmlTags, + allowedAttributes: false, + }); + const ReactFromMarkdown = markdownToReact( - this.props.markdown, + sanitizedMarkdown, this.customComponent.bind(this), this.props.html ); @@ -120,7 +129,8 @@ class MarkdownViewer extends Component { return ValidatedElement( 'div', { className: 'markdown-viewer' }, - ...React.Children.toArray(ReactFromMarkdown) + React.Children.toArray(ReactFromMarkdown), + this.props.sanitized ); } } @@ -130,6 +140,7 @@ MarkdownViewer.defaultProps = { markdown: '', html: false, compact: false, + sanitized: true, }; MarkdownViewer.propTypes = { @@ -137,6 +148,7 @@ MarkdownViewer.propTypes = { lists: PropTypes.arrayOf(PropTypes.object), html: PropTypes.bool, compact: PropTypes.bool, + sanitized: PropTypes.bool, }; export default MarkdownViewer; diff --git a/app/react/Markdown/ValidatedElement.tsx b/app/react/Markdown/ValidatedElement.tsx index 3ba6a277d0..6a98886f62 100644 --- a/app/react/Markdown/ValidatedElement.tsx +++ b/app/react/Markdown/ValidatedElement.tsx @@ -1,14 +1,16 @@ import React from 'react'; -import { validHtmlTags } from './utils'; +import { extendedHtmlTags, visualizationHtmlTags } from './utils'; -const isValidTagName = (tagName: string): boolean => validHtmlTags.has(tagName); +const isValidTagName = (tagName: string, sanitized: boolean): boolean => + !sanitized ? extendedHtmlTags.includes(tagName) : visualizationHtmlTags.includes(tagName); const ValidatedElement = ( type: string | React.JSXElementConstructor, props: (React.Attributes & { children?: React.ReactNode }) | null, - ...children: React.ReactNode[] + children: React.ReactNode[], + sanitized = true ): React.ReactElement | null => { - if (typeof type === 'string' && !isValidTagName(type)) { + if (typeof type === 'string' && !isValidTagName(type, sanitized)) { return React.createElement('div', { className: 'error' }, `Invalid tag: ${type}`); } @@ -17,7 +19,12 @@ const ValidatedElement = ( return child.map(c => { const childProps = c.props as React.Attributes & { children?: React.ReactNode }; return React.isValidElement(c) - ? ValidatedElement(c.type, childProps, ...React.Children.toArray(childProps.children)) + ? ValidatedElement( + c.type, + childProps, + React.Children.toArray(childProps.children), + sanitized + ) : c; }); } @@ -25,7 +32,8 @@ const ValidatedElement = ( return ValidatedElement( child.type, child.props as React.Attributes & { children?: React.ReactNode }, - ...React.Children.toArray(child.props.children) + React.Children.toArray(child.props.children), + sanitized ); } return child; diff --git a/app/react/Markdown/specs/MarkdownViewer.spec.js b/app/react/Markdown/specs/MarkdownViewer.spec.js index 7484f7b619..6099cccb01 100644 --- a/app/react/Markdown/specs/MarkdownViewer.spec.js +++ b/app/react/Markdown/specs/MarkdownViewer.spec.js @@ -1,3 +1,4 @@ +/* eslint-disable no-template-curly-in-string */ /* eslint-disable max-statements */ import React, { Component } from 'react'; @@ -13,6 +14,7 @@ describe('MarkdownViewer', () => { beforeEach(() => { props = { markdown: '## MarkdownContent', + sanitized: false, }; }); @@ -213,4 +215,41 @@ describe('MarkdownViewer', () => { expect(component).toMatchSnapshot(); }); }); + + describe('limited markdown', () => { + it.each` + type | markdown + ${'forbidden tag'} | ${'
This was cleaned
'} + ${'forbidden tag'} | ${'
This was cleaned
'} + ${'malicious code'} | ${''} + `('should replace banned tags $type', ({ markdown }) => { + props = { + markdown, + html: true, + sanitized: true, + }; + render(); + + expect(component).toMatchSnapshot(); + }); + + it.each` + type | markdown + ${'custom tags'} | ${'label and \n
test
'} + ${'custom hook'} | ${''} + ${'placeholder'} | ${'$content'} + ${'standard tags'} | ${'
Title
value
'} + ${'interpolation'} | ${'${template.color}'} + ${'media'} | ${'{vimeo}(1234, options)'} + `('should keep allowed tags $type', ({ markdown }) => { + props = { + markdown, + html: true, + sanitized: true, + }; + render(); + + expect(component).toMatchSnapshot(); + }); + }); }); diff --git a/app/react/Markdown/specs/__snapshots__/MarkdownViewer.spec.js.snap b/app/react/Markdown/specs/__snapshots__/MarkdownViewer.spec.js.snap index 49a174f09e..79f670d9ca 100644 --- a/app/react/Markdown/specs/__snapshots__/MarkdownViewer.spec.js.snap +++ b/app/react/Markdown/specs/__snapshots__/MarkdownViewer.spec.js.snap @@ -1,5 +1,126 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`MarkdownViewer limited markdown should keep allowed tags custom hook 1`] = ` +
+

+ +

+ + +
+`; + +exports[`MarkdownViewer limited markdown should keep allowed tags custom tags 1`] = ` +
+

+ + label + + and +

+ + +
+ test +
+
+`; + +exports[`MarkdownViewer limited markdown should keep allowed tags interpolation 1`] = ` +
+

+ \${template.color} +

+ + +
+`; + +exports[`MarkdownViewer limited markdown should keep allowed tags media 1`] = ` +
+
+ +
+ + +
+`; + +exports[`MarkdownViewer limited markdown should keep allowed tags placeholder 1`] = ` +
+

+ + $content + +

+ + +
+`; + +exports[`MarkdownViewer limited markdown should keep allowed tags standard tags 1`] = ` +
+ + + + + + + +
+ Title +
+ value +
+
+`; + +exports[`MarkdownViewer limited markdown should replace banned tags forbidden tag 1`] = ` +
+
+ This was cleaned +
+
+`; + +exports[`MarkdownViewer limited markdown should replace banned tags forbidden tag 2`] = ` +
+
+ This was cleaned +
+
+`; + +exports[`MarkdownViewer limited markdown should replace banned tags malicious code 1`] = ` +
+ +
+`; + exports[`MarkdownViewer render should be able to render properly custom components not separated by \\n\\n 1`] = `
{ /* eslint-enable no-console */ }; -const validHtmlTags = new Set([ +const customVisualizationTags = [ + 'vimeo', + 'youtube', + 'media', + 'markdownlink', + 'markdownmedia', + 'counter', + 'entitylink', + 'itemlist', + 'repeat', + 'context', + 'map', + 'value', + 'cejilchart', + 'cejilchart001', + 'cejilchart002', + 'cejilhero', + 'placeholder', +]; +const customExtendedTags = [ + 'searchbox', + 'contactform', + 'paypaldonatelink', + 'publicform', + 'searchbox', + 'entityinfo', + 'query', + 'dataset', + 'entitydata', + 'entitysection', + 'piechart', + 'pie', + 'piechart', + 'pie', + 'barchart', + 'bar', + 'tooltip', + 'stackeddualbarchart', + 'librarychart', + 'librarycharts', + 'coloredbar', + 'extendedtooltip', +]; + +const extendedValidHtmlTags = [ + 'blink', + 'body', + 'button', + 'canvas', + 'embed', + 'element', + 'fieldset', + 'font', + 'foreignObject', + 'form', + 'iframe', + 'input', + 'image', + 'legend', + 'marker', + 'marquee', + 'object', + 'option', + 'select', + 'spacer', + 'style', + 'textarea', + 'title', + 'use', + 'var', + 'view', + 'wbr', +].concat(customExtendedTags); + +const visualizationHtmlTags = [ 'a', 'abbr', 'acronym', @@ -27,12 +101,8 @@ const validHtmlTags = new Set([ 'bdi', 'bdo', 'big', - 'blink', 'blockquote', - 'body', 'br', - 'button', - 'canvas', 'caption', 'center', 'circle', @@ -55,10 +125,8 @@ const validHtmlTags = new Set([ 'div', 'dl', 'dt', - 'element', 'ellipse', 'em', - 'embed', 'feBlend', 'feColorMatrix', 'feComponentTransfer', @@ -83,14 +151,10 @@ const validHtmlTags = new Set([ 'feSpotLight', 'feTile', 'feTurbulence', - 'fieldset', 'figcaption', 'figure', 'filter', - 'font', 'footer', - 'foreignObject', - 'form', 'g', 'h1', 'h2', @@ -104,22 +168,16 @@ const validHtmlTags = new Set([ 'hr', 'html', 'i', - 'iframe', - 'image', 'img', - 'input', 'ins', 'kbd', 'label', - 'legend', 'li', 'line', 'linearGradient', 'main', 'map', 'mark', - 'marker', - 'marquee', 'mask', 'menu', 'menuitem', @@ -127,10 +185,8 @@ const validHtmlTags = new Set([ 'meter', 'nav', 'nobr', - 'object', 'ol', 'optgroup', - 'option', 'output', 'p', 'path', @@ -148,15 +204,12 @@ const validHtmlTags = new Set([ 's', 'samp', 'section', - 'select', 'shadow', 'small', 'source', - 'spacer', 'span', 'strike', 'strong', - 'style', 'sub', 'summary', 'sup', @@ -167,23 +220,22 @@ const validHtmlTags = new Set([ 'tbody', 'td', 'template', - 'textarea', 'tfoot', 'th', 'thead', 'time', - 'title', 'tr', 'track', 'tspan', 'tt', 'u', 'ul', - 'use', - 'var', 'video', - 'view', 'wbr', -]); +].concat(customVisualizationTags); + +const extendedHtmlTags = visualizationHtmlTags + .concat(extendedValidHtmlTags) + .concat(customExtendedTags); -export { objectPath, logError, validHtmlTags }; +export { objectPath, logError, extendedHtmlTags, visualizationHtmlTags }; diff --git a/app/react/Metadata/components/specs/__snapshots__/Metadata.spec.js.snap b/app/react/Metadata/components/specs/__snapshots__/Metadata.spec.js.snap index b4ac122f4e..fd4f233999 100644 --- a/app/react/Metadata/components/specs/__snapshots__/Metadata.spec.js.snap +++ b/app/react/Metadata/components/specs/__snapshots__/Metadata.spec.js.snap @@ -66,6 +66,7 @@ exports[`Metadata should render a Markdown when the metadata is type mardown 1`] html={true} lists={Array []} markdown="some markdown text" + sanitized={true} /> @@ -113,6 +114,7 @@ exports[`Metadata should render a media field 1`] = ` html={false} lists={Array []} markdown="{media}(http://youtube.com/videoid)" + sanitized={true} /> diff --git a/app/react/Pages/components/PageViewer.js b/app/react/Pages/components/PageViewer.js index 01ad70f8b4..739b8ffc02 100644 --- a/app/react/Pages/components/PageViewer.js +++ b/app/react/Pages/components/PageViewer.js @@ -85,7 +85,12 @@ class PageViewer extends Component { {this.state.customPageError && this.renderErrorWarning()} - +
diff --git a/contents/ui-translations/ar.csv b/contents/ui-translations/ar.csv index 5c6ff8644d..741b734090 100644 --- a/contents/ui-translations/ar.csv +++ b/contents/ui-translations/ar.csv @@ -399,7 +399,7 @@ Install browser extension (dynamic link),Install browser extension (dynamic link Install Language(s),Install Language(s) INSTALL the browser extension,INSTALL the browser extension "Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation.","Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation." -Instructions to reset password,If the email address supplied is correct +Instructions to reset password,"If the email address supplied is correct, we will send instructions to reset the password." Instructions to reset the password were sent to the user,Instructions to reset the password were sent to the user Invalid captcha,حروف التحقق غير صحيحة Invalid csv: all the values for a row must be either nested or non-nested,Invalid csv: all the values for a row must be either nested or non-nested diff --git a/contents/ui-translations/en.csv b/contents/ui-translations/en.csv index a88651bb77..733fc7f506 100644 --- a/contents/ui-translations/en.csv +++ b/contents/ui-translations/en.csv @@ -402,7 +402,7 @@ Install browser extension (dynamic link),Install browser extension (dynamic link Install Language(s),Install Language(s) INSTALL the browser extension,INSTALL the browser extension "Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation.","Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation." -Instructions to reset password,If the email address supplied is correct +Instructions to reset password,"If the email address supplied is correct, we will send instructions to reset the password." Instructions to reset the password were sent to the user,Instructions to reset the password were sent to the user Invalid captcha,Invalid captcha Invalid csv: all the values for a row must be either nested or non-nested,Invalid csv: all the values for a row must be either nested or non-nested diff --git a/contents/ui-translations/fr.csv b/contents/ui-translations/fr.csv index 179405940f..337f8edf3f 100644 --- a/contents/ui-translations/fr.csv +++ b/contents/ui-translations/fr.csv @@ -399,7 +399,7 @@ Install browser extension (dynamic link),Install browser extension (dynamic link Install Language(s),Install Language(s) INSTALL the browser extension,INSTALL the browser extension "Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation.","Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation." -Instructions to reset password,If the email address supplied is correct +Instructions to reset password,"If the email address supplied is correct, we will send instructions to reset the password." Instructions to reset the password were sent to the user,Instructions to reset the password were sent to the user Invalid captcha,Captcha invalide Invalid csv: all the values for a row must be either nested or non-nested,Invalid csv: all the values for a row must be either nested or non-nested diff --git a/contents/ui-translations/ko.csv b/contents/ui-translations/ko.csv index 4c88129ba4..3376dbd08b 100644 --- a/contents/ui-translations/ko.csv +++ b/contents/ui-translations/ko.csv @@ -400,7 +400,7 @@ Install browser extension (dynamic link),Install browser extension (dynamic link Install Language(s),Install Language(s) INSTALL the browser extension,INSTALL the browser extension "Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation.","Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation." -Instructions to reset password,If the email address supplied is correct +Instructions to reset password,"If the email address supplied is correct, we will send instructions to reset the password." Instructions to reset the password were sent to the user,Instructions to reset the password were sent to the user Invalid captcha,잘못된 보안문자 Invalid csv: all the values for a row must be either nested or non-nested,Invalid csv: all the values for a row must be either nested or non-nested diff --git a/contents/ui-translations/my.csv b/contents/ui-translations/my.csv index 80024ff6a9..09e619473c 100644 --- a/contents/ui-translations/my.csv +++ b/contents/ui-translations/my.csv @@ -400,7 +400,7 @@ Install browser extension (dynamic link),Install browser extension (dynamic link Install Language(s),Install Language(s) INSTALL the browser extension,INSTALL the browser extension "Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation.","Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation." -Instructions to reset password,If the email address supplied is correct +Instructions to reset password,"If the email address supplied is correct, we will send instructions to reset the password." Instructions to reset the password were sent to the user,Instructions to reset the password were sent to the user Invalid captcha,ကက်ပ်ချာ မှားနေသည် Invalid csv: all the values for a row must be either nested or non-nested,Invalid csv: all the values for a row must be either nested or non-nested diff --git a/contents/ui-translations/ru.csv b/contents/ui-translations/ru.csv index d762661471..4574498114 100644 --- a/contents/ui-translations/ru.csv +++ b/contents/ui-translations/ru.csv @@ -397,7 +397,7 @@ Install browser extension (dynamic link),Install browser extension (dynamic link Install Language(s),Install Language(s) INSTALL the browser extension,INSTALL the browser extension "Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation.","Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation." -Instructions to reset password,If the email address supplied is correct +Instructions to reset password,"If the email address supplied is correct, we will send instructions to reset the password." Instructions to reset the password were sent to the user,Instructions to reset the password were sent to the user Invalid captcha,Неверная Captcha Invalid csv: all the values for a row must be either nested or non-nested,Invalid csv: all the values for a row must be either nested or non-nested diff --git a/contents/ui-translations/th.csv b/contents/ui-translations/th.csv index 386ccdf887..5bcb9e3467 100644 --- a/contents/ui-translations/th.csv +++ b/contents/ui-translations/th.csv @@ -400,7 +400,7 @@ Install browser extension (dynamic link),Install browser extension (dynamic link Install Language(s),Install Language(s) INSTALL the browser extension,INSTALL the browser extension "Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation.","Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation." -Instructions to reset password,If the email address supplied is correct +Instructions to reset password,"If the email address supplied is correct, we will send instructions to reset the password." Instructions to reset the password were sent to the user,Instructions to reset the password were sent to the user Invalid captcha,captcha ไม่ถูกต้อง Invalid csv: all the values for a row must be either nested or non-nested,Invalid csv: all the values for a row must be either nested or non-nested diff --git a/contents/ui-translations/tr.csv b/contents/ui-translations/tr.csv index d6767488bf..f89cc66d05 100644 --- a/contents/ui-translations/tr.csv +++ b/contents/ui-translations/tr.csv @@ -400,7 +400,7 @@ Install browser extension (dynamic link),Install browser extension (dynamic link Install Language(s),Install Language(s) INSTALL the browser extension,INSTALL the browser extension "Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation.","Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation." -Instructions to reset password,If the email address supplied is correct +Instructions to reset password,"If the email address supplied is correct, we will send instructions to reset the password." Instructions to reset the password were sent to the user,Instructions to reset the password were sent to the user Invalid captcha,Geçersiz güvenlik kodu Invalid csv: all the values for a row must be either nested or non-nested,Invalid csv: all the values for a row must be either nested or non-nested diff --git a/package.json b/package.json index 23cc88553e..70b497ae6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uwazi", - "version": "1.185.0-rc3", + "version": "1.186.0-rc3", "description": "Uwazi is a free, open-source solution for organising, analysing and publishing your documents.", "keywords": [ "react" @@ -104,14 +104,14 @@ "@fortawesome/free-regular-svg-icons": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/react-fontawesome": "^0.2.2", - "@googlemaps/js-api-loader": "^1.16.8", + "@googlemaps/js-api-loader": "^1.16.6", "@headlessui/react": "1.7.17", "@heroicons/react": "^2.1.5", "@hookform/error-message": "^2.0.1", "@huridocs/react-text-selection-handler": "^0.3.0", "@loadable/component": "^5.16.4", "@popperjs/core": "^2.11.8", - "@remix-run/router": "^1.19.2", + "@remix-run/router": "1.19.2", "@sentry/node": "^7.114.0", "@sentry/react": "7.114.0", "@sentry/tracing": "^7.114.0", @@ -119,7 +119,7 @@ "@socket.io/redis-adapter": "7.2.0", "@socket.io/redis-emitter": "5.1.0", "@supercharge/promise-pool": "^3.2.0", - "@tanstack/react-table": "^8.20.5", + "@tanstack/react-table": "8.20.5", "@types/franc": "^5.0.1", "@types/lodash": "^4.14.170", "@types/mime-types": "^2.1.1", @@ -147,11 +147,11 @@ "date-fns": "^3.6.0", "diacritics": "^1.3.0", "dotenv": "^16.4.5", - "express": "^4.20.0", - "express-http-proxy": "^2.1.1", + "express": "^4.19.2", + "express-http-proxy": "2.1.1", "express-prom-bundle": "^7.0.0", "express-session": "1.18.0", - "filesize": "^10.1.6", + "filesize": "10.1.6", "flag-icon-css": "^4.1.7", "flowbite": "^2.3.0", "flowbite-datepicker": "^1.2.7", @@ -165,7 +165,7 @@ "immutable": "^3.7.6", "is-reachable": "^5.2.1", "isomorphic-fetch": "3.0.0", - "jotai": "^2.9.3", + "jotai": "2.9.3", "json-schema": "^0.4.0", "json-schema-to-typescript": "^13.1.2", "jvent": "1.0.2", @@ -173,7 +173,7 @@ "leaflet.gridlayer.googlemutant": "^0.14.1", "leaflet.markercluster": "^1.5.3", "lodash": "^4.17.21", - "luxon": "^3.5.0", + "luxon": "3.5.0", "mark.js": "^8.11.1", "markdown-it": "14.1.0", "markdown-it-container": "4.0.0", @@ -186,14 +186,14 @@ "mongoose": "8.1.2", "multer": "^1.4.5-lts.1", "node-uuid": "^1.4.7", - "nodemailer": "^6.9.14", + "nodemailer": "6.9.15", "nprogress": "^0.2.0", "otplib": "^11.0.1", "passport": "^0.7.0", "passport-local": "^1.0.0", - "pdfjs-dist": "^4.6.82", + "pdfjs-dist": "4.6.82", "postcss-loader": "^8.1.1", - "postcss-prefix-selector": "^1.16.1", + "postcss-prefix-selector": "^1.16.0", "prom-client": "^15.1.3", "prop-types": "^15.8.1", "qrcode.react": "^4.0.1", @@ -234,12 +234,13 @@ "rsmq": "^0.12.4", "rtlcss": "^4.3.0", "sanitize-filename": "^1.6.3", + "sanitize-html": "^2.13.0", "serialize-javascript": "^6.0.1", "sift": "^17.1.3", "socket.io": "4.7.5", "socket.io-client": "4.7.5", "socket.io-parser": "4.2.4", - "stopword": "^3.0.1", + "stopword": "3.1.1", "superagent": "10.1.0", "svg-captcha": "^1.4.0", "tiny-cookie": "^2.5.1", @@ -254,7 +255,7 @@ }, "devDependencies": { "@4tw/cypress-drag-drop": "^2.2.5", - "@babel/cli": "7.24.8", + "@babel/cli": "7.25.6", "@babel/core": "7.25.2", "@babel/eslint-parser": "7.25.1", "@babel/helper-call-delegate": "^7.12.13", @@ -286,7 +287,7 @@ "@storybook/react": "^8.1.11", "@storybook/react-webpack5": "^8.1.11", "@storybook/test": "^8.1.11", - "@testing-library/jest-dom": "^6.5.0", + "@testing-library/jest-dom": "6.5.0", "@testing-library/react": "^15.0.7", "@testing-library/user-event": "^14.5.2", "@types/body-parser": "^1.19.2", @@ -343,7 +344,7 @@ "copy-webpack-plugin": "12.0.2", "css-loader": "^7.1.2", "css-minimizer-webpack-plugin": "^7.0.0", - "cypress": "13.14.2", + "cypress": "13.14.1", "cypress-axe": "^1.5.0", "cypress-plugin-snapshots": "^1.4.4", "cypress-real-events": "^1.13.0", @@ -353,9 +354,9 @@ "eslint-config-airbnb": "19.0.4", "eslint-plugin-cypress": "^3.5.0", "eslint-plugin-import": "v2.29.1", - "eslint-plugin-jasmine": "4.2.0", - "eslint-plugin-jest": "v28.8.0", - "eslint-plugin-jsx-a11y": "6.7.1", + "eslint-plugin-jasmine": "4.2.1", + "eslint-plugin-jest": "v28.8.2", + "eslint-plugin-jsx-a11y": "6.10.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "5.1.3", "eslint-plugin-react": "v7.36.1", @@ -376,19 +377,19 @@ "node-polyfill-webpack-plugin": "^4.0.0", "nodemon": "^3.1.4", "plop": "^4.0.1", - "postcss": "^8.4.45", + "postcss": "8.4.45", "prettier": "3.3.3", "puppeteer": "^13.5.2", "react-dnd-test-backend": "15.1.1", "redux-mock-store": "^1.5.4", "rtlcss-webpack-plugin": "4.0.7", - "sass": "^1.78.0", + "sass": "1.78.0", "sass-loader": "16.0.1", "storybook": "^8.1.11", "stream-mock": "^2.0.5", "supertest": "7.0.0", "svg-inline-loader": "^0.8.2", - "tailwindcss": "^3.4.10", + "tailwindcss": "3.4.10", "terser-webpack-plugin": "^5.3.10", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", @@ -397,7 +398,7 @@ "webpack": "^5.94.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "5.1.4", - "webpack-dev-middleware": "7.3.0", + "webpack-dev-middleware": "7.4.2", "webpack-hot-middleware": "^2.26.1", "worker-loader": "^3.0.8" }, diff --git a/yarn.lock b/yarn.lock index f4ca9e785d..0c3b9a4f23 100644 --- a/yarn.lock +++ b/yarn.lock @@ -623,10 +623,10 @@ "@smithy/types" "^3.3.0" tslib "^2.6.2" -"@babel/cli@7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.24.8.tgz#79eaa55a69c77cafbea3e87537fd1df5a5a2edf8" - integrity sha512-isdp+G6DpRyKc+3Gqxy2rjzgF7Zj9K0mzLNnxz+E/fgeag8qT3vVulX4gY9dGO1q0y+0lUv6V3a+uhUzMzrwXg== +"@babel/cli@7.25.6": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.25.6.tgz#bc35561adc78ade43ac9c09a690768493ab9ed95" + integrity sha512-Z+Doemr4VtvSD2SNHTrkiFZ1LX+JI6tyRXAAOb4N9khIuPyoEPmTPJarPm8ljJV1D6bnMQjyHMWTT9NeKbQuXA== dependencies: "@jridgewell/trace-mapping" "^0.3.25" commander "^6.2.0" @@ -637,7 +637,7 @@ slash "^2.0.0" optionalDependencies: "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3" - chokidar "^3.4.0" + chokidar "^3.6.0" "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.24.7": version "7.24.7" @@ -1742,7 +1742,7 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.18", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.20.7", "@babel/runtime@^7.24.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.18", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.24.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== @@ -2287,10 +2287,12 @@ dependencies: prop-types "^15.8.1" -"@googlemaps/js-api-loader@^1.16.8": - version "1.16.8" - resolved "https://registry.yarnpkg.com/@googlemaps/js-api-loader/-/js-api-loader-1.16.8.tgz#1595a2af80ca07e551fc961d921a2437d1cb3643" - integrity sha512-CROqqwfKotdO6EBjZO/gQGVTbeDps5V7Mt9+8+5Q+jTg5CRMi3Ii/L9PmV3USROrt2uWxtGzJHORmByxyo9pSQ== +"@googlemaps/js-api-loader@^1.16.6": + version "1.16.6" + resolved "https://registry.yarnpkg.com/@googlemaps/js-api-loader/-/js-api-loader-1.16.6.tgz#c89970c94b55796d51746c092f0e52953994a171" + integrity sha512-V8p5W9DbPQx74jWUmyYJOerhiB4C+MHekaO0ZRmc6lrOYrvY7+syLhzOWpp55kqSPeNb+qbC2h8i69aLIX6krQ== + dependencies: + fast-deep-equal "^3.1.3" "@hapi/hoek@^9.0.0": version "9.3.0" @@ -3243,7 +3245,7 @@ resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz#d1b4befa423f692fa4abf1c79209702e7d8ae4b4" integrity sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA== -"@remix-run/router@1.19.2", "@remix-run/router@^1.19.2": +"@remix-run/router@1.19.2": version "1.19.2" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.19.2.tgz#0c896535473291cb41f152c180bedd5680a3b273" integrity sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA== @@ -4646,7 +4648,7 @@ dependencies: defer-to-connect "^2.0.0" -"@tanstack/react-table@^8.20.5": +"@tanstack/react-table@8.20.5": version "8.20.5" resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.20.5.tgz#19987d101e1ea25ef5406dce4352cab3932449d8" integrity sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA== @@ -4686,7 +4688,7 @@ lodash "^4.17.21" redent "^3.0.0" -"@testing-library/jest-dom@^6.5.0": +"@testing-library/jest-dom@6.5.0": version "6.5.0" resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz#50484da3f80fb222a853479f618a9ce5c47bfe54" integrity sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA== @@ -6166,19 +6168,26 @@ aria-hidden@^1.1.1: dependencies: tslib "^2.0.0" -aria-query@5.3.0, aria-query@^5.0.0, aria-query@^5.1.3: +aria-query@5.3.0, aria-query@^5.0.0: version "5.3.0" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== dependencies: dequal "^2.0.3" +aria-query@~5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" + integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ== + dependencies: + deep-equal "^2.0.5" + arr-union@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= -array-buffer-byte-length@^1.0.1: +array-buffer-byte-length@^1.0.0, array-buffer-byte-length@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== @@ -6266,7 +6275,7 @@ array.prototype.flat@^1.2.3, array.prototype.flat@^1.3.1, array.prototype.flat@^ es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" -array.prototype.flatmap@^1.3.1, array.prototype.flatmap@^1.3.2: +array.prototype.flatmap@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== @@ -6363,10 +6372,10 @@ assets-webpack-plugin@7.1.1: escape-string-regexp "^4.0.0" lodash "^4.17.21" -ast-types-flow@^0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" - integrity sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag== +ast-types-flow@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" + integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== ast-types@^0.16.1: version "0.16.1" @@ -6417,10 +6426,10 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3" integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg== -axe-core@^4.6.2: - version "4.8.3" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.8.3.tgz#205df863dd9917d5979e9435dab4d47692759051" - integrity sha512-d5ZQHPSPkF9Tw+yfyDcRoUOc4g/8UloJJe5J8m4L5+c7AtDdjDLRxew/knnI4CxvtdxEUVgWz4x3OIQUIFiMfw== +axe-core@^4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.0.tgz#d9e56ab0147278272739a000880196cdfe113b59" + integrity sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g== axios@^0.25.0: version "0.25.0" @@ -6429,12 +6438,10 @@ axios@^0.25.0: dependencies: follow-redirects "^1.14.7" -axobject-query@^3.1.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" - integrity sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg== - dependencies: - dequal "^2.0.3" +axobject-query@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee" + integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ== babel-core@^7.0.0-bridge.0: version "7.0.0-bridge.0" @@ -7109,7 +7116,7 @@ child-process-promise@^2.2.1: node-version "^1.0.0" promise-polyfill "^6.0.1" -"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.0, chokidar@^3.5.2, chokidar@^3.5.3: +"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.2, chokidar@^3.5.3, chokidar@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== @@ -7969,10 +7976,10 @@ cypress-real-events@^1.13.0: resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.13.0.tgz#6b7cd32dcac172db1493608f97a2576c7d0bd5af" integrity sha512-LoejtK+dyZ1jaT8wGT5oASTPfsNV8/ClRp99ruN60oPj8cBJYod80iJDyNwfPAu4GCxTXOhhAv9FO65Hpwt6Hg== -cypress@13.14.2: - version "13.14.2" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.14.2.tgz#4237eb7b26de2baeaa1f01e585f965d88fca7f39" - integrity sha512-lsiQrN17vHMB2fnvxIrKLAjOr9bPwsNbPZNrWf99s4u+DVmCY6U+w7O3GGG9FvP4EUVYaDu+guWeNLiUzBrqvA== +cypress@13.14.1: + version "13.14.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.14.1.tgz#05875bbbf6333e858a92aed33ba67d7ddf8370d7" + integrity sha512-Wo+byPmjps66hACEH5udhXINEiN3qS3jWNGRzJOjrRJF3D0+YrcP2LVB1T7oYaVQM/S+eanqEvBWYc8cf7Vcbg== dependencies: "@cypress/request" "^3.0.1" "@cypress/xvfb" "^1.2.4" @@ -8243,6 +8250,30 @@ deep-eql@^4.1.3: dependencies: type-detect "^4.0.0" +deep-equal@^2.0.5: + version "2.2.3" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.3.tgz#af89dafb23a396c7da3e862abc0be27cf51d56e1" + integrity sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.5" + es-get-iterator "^1.1.3" + get-intrinsic "^1.2.2" + is-arguments "^1.1.1" + is-array-buffer "^3.0.2" + is-date-object "^1.0.5" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + isarray "^2.0.5" + object-is "^1.1.5" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.1" + side-channel "^1.0.4" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.13" + deep-is@^0.1.3: version "0.1.3" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz" @@ -9026,6 +9057,21 @@ es-errors@^1.2.1, es-errors@^1.3.0: resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== +es-get-iterator@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" + integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + has-symbols "^1.0.3" + is-arguments "^1.1.1" + is-map "^2.0.2" + is-set "^2.0.2" + is-string "^1.0.7" + isarray "^2.0.5" + stop-iteration-iterator "^1.0.0" + es-iterator-helpers@^1.0.19: version "1.0.19" resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz#117003d0e5fec237b4b5c08aded722e0c6d50ca8" @@ -9307,39 +9353,39 @@ eslint-plugin-import@v2.29.1: semver "^6.3.1" tsconfig-paths "^3.15.0" -eslint-plugin-jasmine@4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jasmine/-/eslint-plugin-jasmine-4.2.0.tgz#ed0fe988b6e3b123905a7bf68d77239649fd018c" - integrity sha512-zSCsnP4gMqBSt8jApExP0ja43nAI1fpAD5kY+knrIJylBxC/LEth25PkqcKJqW32GjesjsiA1SSSR3Z5qIranA== +eslint-plugin-jasmine@4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jasmine/-/eslint-plugin-jasmine-4.2.1.tgz#80925dc6b24ee263eb834bedb09ff4abf3740b3d" + integrity sha512-Vwecc66rjMgz2e9UtGScsUdo6D+SbfgPA4Kf0zdAl4+5IQMRL0mXd8973MaZuYYF89XpRjQEGl5TNmg2Bv+KcQ== -eslint-plugin-jest@v28.8.0: - version "28.8.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-28.8.0.tgz#54f597b5a3295ad04ec946baa245ad02b9b2bca0" - integrity sha512-Tubj1hooFxCl52G4qQu0edzV/+EZzPUeN8p2NnW5uu4fbDs+Yo7+qDVDc4/oG3FbCqEBmu/OC3LSsyiU22oghw== +eslint-plugin-jest@v28.8.2: + version "28.8.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-28.8.2.tgz#7f8307179c5cf8d51101b3aa002be168daadecbc" + integrity sha512-mC3OyklHmS5i7wYU1rGId9EnxRI8TVlnFG56AE+8U9iRy6zwaNygZR+DsdZuCL0gRG0wVeyzq+uWcPt6yJrrMA== dependencies: "@typescript-eslint/utils" "^6.0.0 || ^7.0.0 || ^8.0.0" -eslint-plugin-jsx-a11y@6.7.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz#fca5e02d115f48c9a597a6894d5bcec2f7a76976" - integrity sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA== +eslint-plugin-jsx-a11y@6.10.0: + version "6.10.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.0.tgz#36fb9dead91cafd085ddbe3829602fb10ef28339" + integrity sha512-ySOHvXX8eSN6zz8Bywacm7CvGNhUtdjvqfQDVe6020TUK34Cywkw7m0KsCCk1Qtm9G1FayfTN1/7mMYnYO2Bhg== dependencies: - "@babel/runtime" "^7.20.7" - aria-query "^5.1.3" - array-includes "^3.1.6" - array.prototype.flatmap "^1.3.1" - ast-types-flow "^0.0.7" - axe-core "^4.6.2" - axobject-query "^3.1.1" + aria-query "~5.1.3" + array-includes "^3.1.8" + array.prototype.flatmap "^1.3.2" + ast-types-flow "^0.0.8" + axe-core "^4.10.0" + axobject-query "^4.1.0" damerau-levenshtein "^1.0.8" emoji-regex "^9.2.2" - has "^1.0.3" - jsx-ast-utils "^3.3.3" - language-tags "=1.0.5" + es-iterator-helpers "^1.0.19" + hasown "^2.0.2" + jsx-ast-utils "^3.3.5" + language-tags "^1.0.9" minimatch "^3.1.2" - object.entries "^1.1.6" - object.fromentries "^2.0.6" - semver "^6.3.0" + object.fromentries "^2.0.8" + safe-regex-test "^1.0.3" + string.prototype.includes "^2.0.0" eslint-plugin-node@^11.1.0: version "11.1.0" @@ -9659,7 +9705,7 @@ expect@^29.0.0, expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" -express-http-proxy@^2.1.1: +express-http-proxy@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/express-http-proxy/-/express-http-proxy-2.1.1.tgz#90bd7eaee5166be968157b035eb6b499d2af2bf4" integrity sha512-4aRQRqDQU7qNPV5av0/hLcyc0guB9UP71nCYrQEYml7YphTo8tmWf3nDZWdTJMMjFikyz9xKXaURor7Chygdwg== @@ -9729,7 +9775,7 @@ express@^4.17.3, express@^4.18.2: utils-merge "1.0.1" vary "~1.1.2" -express@^4.20.0: +express@^4.19.2: version "4.21.0" resolved "https://registry.yarnpkg.com/express/-/express-4.21.0.tgz#d57cb706d49623d4ac27833f1cbc466b668eb915" integrity sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng== @@ -9961,16 +10007,16 @@ filelist@^1.0.4: dependencies: minimatch "^5.0.1" +filesize@10.1.6: + version "10.1.6" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-10.1.6.tgz#31194da825ac58689c0bce3948f33ce83aabd361" + integrity sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w== + filesize@^10.0.12: version "10.1.4" resolved "https://registry.yarnpkg.com/filesize/-/filesize-10.1.4.tgz#184f256063a201f08b6e6b3cc47d21b60f5b8d89" integrity sha512-ryBwPIIeErmxgPnm6cbESAzXjuEFubs+yKYLBZvg3CaiNcmkJChoOGcBSrZ6IwkMwPABwPpVXE6IlNdGJJrvEg== -filesize@^10.1.6: - version "10.1.6" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-10.1.6.tgz#31194da825ac58689c0bce3948f33ce83aabd361" - integrity sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w== - fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -10425,7 +10471,7 @@ get-func-name@^2.0.1, get-func-name@^2.0.2: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== -get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: +get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== @@ -11033,6 +11079,16 @@ htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" +htmlparser2@^8.0.0: + version "8.0.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" + integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + entities "^4.4.0" + htmlparser2@^9.0, htmlparser2@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-9.1.0.tgz#cdb498d8a75a51f739b61d3f718136c369bc8c23" @@ -11266,7 +11322,7 @@ inquirer@^9.2.10: strip-ansi "^6.0.1" wrap-ansi "^6.2.0" -internal-slot@^1.0.7: +internal-slot@^1.0.4, internal-slot@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== @@ -11310,7 +11366,7 @@ is-absolute@^1.0.0: is-relative "^1.0.0" is-windows "^1.0.1" -is-arguments@^1.0.4: +is-arguments@^1.0.4, is-arguments@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== @@ -11318,7 +11374,7 @@ is-arguments@^1.0.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-array-buffer@^3.0.4: +is-array-buffer@^3.0.2, is-array-buffer@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== @@ -11476,7 +11532,7 @@ is-interactive@^2.0.0: resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-2.0.0.tgz#40c57614593826da1100ade6059778d597f16e90" integrity sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ== -is-map@^2.0.1: +is-map@^2.0.1, is-map@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== @@ -11594,7 +11650,7 @@ is-relative@^1.0.0: dependencies: is-unc-path "^1.0.0" -is-set@^2.0.1: +is-set@^2.0.1, is-set@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== @@ -12373,7 +12429,7 @@ joi@^17.6.0: "@sideway/formula" "^3.0.0" "@sideway/pinpoint" "^2.0.0" -jotai@^2.9.3: +jotai@2.9.3: version "2.9.3" resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.9.3.tgz#abcae49a737cd50e3144a6c9eb39840db077c727" integrity sha512-IqMWKoXuEzWSShjd9UhalNsRGbdju5G2FrqNLQJT+Ih6p41VNYe2sav5hnwQx4HJr25jq9wRqvGSWGviGG6Gjw== @@ -12580,7 +12636,7 @@ jsprim@^2.0.2: json-schema "0.4.0" verror "1.10.0" -"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.3: +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5: version "3.3.5" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== @@ -12638,17 +12694,17 @@ kruptein@^3.0.0: dependencies: asn1.js "^5.4.1" -language-subtag-registry@~0.3.2: - version "0.3.22" - resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" - integrity sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w== +language-subtag-registry@^0.3.20: + version "0.3.23" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz#23529e04d9e3b74679d70142df3fd2eb6ec572e7" + integrity sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ== -language-tags@=1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" - integrity sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ== +language-tags@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.9.tgz#1ffdcd0ec0fafb4b1be7f8b11f306ad0f9c08777" + integrity sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA== dependencies: - language-subtag-registry "~0.3.2" + language-subtag-registry "^0.3.20" lazy-ass@^1.6.0: version "1.6.0" @@ -13006,7 +13062,7 @@ lru-queue@^0.1.0: dependencies: es5-ext "~0.10.2" -luxon@^3.5.0: +luxon@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.5.0.tgz#6b6f65c5cd1d61d1fd19dbf07ee87a50bf4b8e20" integrity sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ== @@ -13664,7 +13720,7 @@ node-version@^1.0.0: version "1.2.0" resolved "https://registry.npmjs.org/node-version/-/node-version-1.2.0.tgz" -nodemailer@^6.9.14: +nodemailer@6.9.15: version "6.9.15" resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.15.tgz#57b79dc522be27e0e47ac16cc860aa0673e62e04" integrity sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ== @@ -13821,7 +13877,7 @@ object.defaults@^1.1.0: for-own "^1.0.0" isobject "^3.0.0" -object.entries@^1.1.1, object.entries@^1.1.5, object.entries@^1.1.6, object.entries@^1.1.8: +object.entries@^1.1.1, object.entries@^1.1.5, object.entries@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== @@ -13830,7 +13886,7 @@ object.entries@^1.1.1, object.entries@^1.1.5, object.entries@^1.1.6, object.entr define-properties "^1.2.1" es-object-atoms "^1.0.0" -object.fromentries@^2.0.6, object.fromentries@^2.0.7, object.fromentries@^2.0.8: +object.fromentries@^2.0.7, object.fromentries@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== @@ -14180,6 +14236,11 @@ parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz" +parse-srcset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1" + integrity sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q== + parse5@^3.0.1: version "3.0.3" resolved "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz" @@ -14353,7 +14414,7 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" -pdfjs-dist@^4.6.82: +pdfjs-dist@4.6.82: version "4.6.82" resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-4.6.82.tgz#4f171289f1810f324cd17c58bc59bd5bd0e0db7e" integrity sha512-BUOryeRFwvbLe0lOU6NhkJNuVQUp06WxlJVVCsxdmJ4y5cU3O3s3/0DunVdK1PMm7v2MUw52qKYaidhDH1Z9+w== @@ -14390,11 +14451,6 @@ picocolors@^1.0.0, picocolors@^1.0.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== -picocolors@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" - integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== - picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.0, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -14745,10 +14801,10 @@ postcss-ordered-values@^7.0.0: cssnano-utils "^5.0.0" postcss-value-parser "^4.2.0" -postcss-prefix-selector@^1.16.1: - version "1.16.1" - resolved "https://registry.yarnpkg.com/postcss-prefix-selector/-/postcss-prefix-selector-1.16.1.tgz#87a77523838b79c0e8aec29f173234b2987cdc04" - integrity sha512-Umxu+FvKMwlY6TyDzGFoSUnzW+NOfMBLyC1tAkIjgX+Z/qGspJeRjVC903D7mx7TuBpJlwti2ibXtWuA7fKMeQ== +postcss-prefix-selector@^1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/postcss-prefix-selector/-/postcss-prefix-selector-1.16.0.tgz#ad5b56f9a73a2c090ca7161049632c9d89bcb404" + integrity sha512-rdVMIi7Q4B0XbXqNUEI+Z4E+pueiu/CS5E6vRCQommzdQ/sgsS4dK42U7GX8oJR+TJOtT+Qv3GkNo6iijUMp3Q== postcss-reduce-initial@^7.0.0: version "7.0.0" @@ -14793,6 +14849,15 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== +postcss@8.4.45: + version "8.4.45" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.45.tgz#538d13d89a16ef71edbf75d895284ae06b79e603" + integrity sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.1" + source-map-js "^1.2.0" + postcss@^8.3.11, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.33, postcss@^8.4.38: version "8.4.41" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.41.tgz#d6104d3ba272d882fe18fc07d15dc2da62fa2681" @@ -14802,15 +14867,6 @@ postcss@^8.3.11, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.33, postcss@^8.4 picocolors "^1.0.1" source-map-js "^1.2.0" -postcss@^8.4.45: - version "8.4.47" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" - integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== - dependencies: - nanoid "^3.3.7" - picocolors "^1.1.0" - source-map-js "^1.2.1" - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" @@ -15770,7 +15826,7 @@ regenerator-transform@^0.15.2: dependencies: "@babel/runtime" "^7.8.4" -regexp.prototype.flags@^1.5.2: +regexp.prototype.flags@^1.5.1, regexp.prototype.flags@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== @@ -16113,6 +16169,18 @@ sanitize-filename@^1.6.1, sanitize-filename@^1.6.3: dependencies: truncate-utf8-bytes "^1.0.0" +sanitize-html@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.13.0.tgz#71aedcdb777897985a4ea1877bf4f895a1170dae" + integrity sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA== + dependencies: + deepmerge "^4.2.2" + escape-string-regexp "^4.0.0" + htmlparser2 "^8.0.0" + is-plain-object "^5.0.0" + parse-srcset "^1.0.2" + postcss "^8.3.11" + sass-loader@16.0.1: version "16.0.1" resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-16.0.1.tgz#57049c1787076e923b21a1dccc612546ecaf4295" @@ -16120,7 +16188,7 @@ sass-loader@16.0.1: dependencies: neo-async "^2.6.2" -sass@^1.78.0: +sass@1.78.0: version "1.78.0" resolved "https://registry.yarnpkg.com/sass/-/sass-1.78.0.tgz#cef369b2f9dc21ea1d2cf22c979f52365da60841" integrity sha512-AaIqGSrjo5lA2Yg7RvFZrlXDBCp3nV4XP73GrLGvdRWWwk+8H3l0SDvq/5bA4eF+0RFPLuWUk3E+P1U/YqnpsQ== @@ -16528,11 +16596,6 @@ socket.io@^2.2.0: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== -source-map-js@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" - integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== - source-map-support@0.5.13: version "0.5.13" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" @@ -16646,7 +16709,14 @@ stdin-discarder@^0.2.1: resolved "https://registry.yarnpkg.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz#390037f44c4ae1a1ae535c5fe38dc3aba8d997be" integrity sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ== -stopword@^3.0.1: +stop-iteration-iterator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" + integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== + dependencies: + internal-slot "^1.0.4" + +stopword@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/stopword/-/stopword-3.1.1.tgz#ce3cf748cafd962904902dd050f6f16ca42d3ec0" integrity sha512-TzJdIuzqJNo6IaFvrF3fYqu08uJ/0VMsdABl6d6+dt6daD7QeHJnMt9sPqhVIxEmNaaeE8+eandVPJv9RhAL5Q== @@ -16709,16 +16779,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - 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" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -16745,6 +16806,14 @@ string-width@^7.0.0: get-east-asian-width "^1.0.0" strip-ansi "^7.1.0" +string.prototype.includes@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.includes/-/string.prototype.includes-2.0.0.tgz#8986d57aee66d5460c144620a6d873778ad7289f" + integrity sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string.prototype.matchall@^4.0.11: version "4.0.11" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" @@ -16813,14 +16882,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -17026,10 +17088,10 @@ tailwind-merge@2.3.0: dependencies: "@babel/runtime" "^7.24.1" -tailwindcss@^3.4.10: - version "3.4.11" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.11.tgz#4d6df41acc05a1d0291b1319490db8df375ab709" - integrity sha512-qhEuBcLemjSJk5ajccN9xJFtM/h0AVCPaA6C92jNP+M2J8kX+eMJHI7R2HFKUvvAsMpcfLILMCFYSeDwpMmlUg== +tailwindcss@3.4.10: + version "3.4.10" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.10.tgz#70442d9aeb78758d1f911af29af8255ecdb8ffef" + integrity sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w== dependencies: "@alloc/quick-lru" "^5.2.0" arg "^5.0.2" @@ -18058,10 +18120,10 @@ webpack-cli@5.1.4: rechoir "^0.8.0" webpack-merge "^5.7.3" -webpack-dev-middleware@7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.3.0.tgz#5975ea41271083dc5678886b99d4c058382fb311" - integrity sha512-xD2qnNew+F6KwOGZR7kWdbIou/ud7cVqLEXeK1q0nHcNsX/u7ul/fSdlOTX4ntSL5FNFy7ZJJXbf0piF591JYw== +webpack-dev-middleware@7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz#40e265a3d3d26795585cff8207630d3a8ff05877" + integrity sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA== dependencies: colorette "^2.0.10" memfs "^4.6.0" @@ -18225,7 +18287,7 @@ which-collection@^1.0.1: is-weakmap "^2.0.1" is-weakset "^2.0.1" -which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.2, which-typed-array@^1.1.9: +which-typed-array@^1.1.13, which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.2, which-typed-array@^1.1.9: version "1.1.15" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== @@ -18279,7 +18341,7 @@ world-countries@5.0.0: resolved "https://registry.yarnpkg.com/world-countries/-/world-countries-5.0.0.tgz#6f75ebcce3d5224d84e9117eaf0d75a7726b6501" integrity sha512-wAfOT9Y5i/xnxNOdKJKXdOCw9Q3yQLahBUeuRol+s+o20F6h2a4tLEbJ1lBCYwEQ30Sf9Meqeipk1gib3YwF5w== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -18297,15 +18359,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"