diff --git a/api/package.json b/api/package.json index e13fda5e8e9..6185b199756 100644 --- a/api/package.json +++ b/api/package.json @@ -121,6 +121,7 @@ "eslint-plugin-prettier": "^4.0.0", "jest": "~29.6.2", "jest-extended": "^7.0.0", + "juice": "^12.1.1", "prettier": "^2.3.2", "supertest": "^6.1.3", "ts-jest": "~29.1.1", diff --git a/api/prisma/seed-staging/seed-bridge-bay.ts b/api/prisma/seed-staging/seed-bridge-bay.ts index 985b3b92ee7..6838b191461 100644 --- a/api/prisma/seed-staging/seed-bridge-bay.ts +++ b/api/prisma/seed-staging/seed-bridge-bay.ts @@ -4,6 +4,7 @@ import { MultiselectQuestionsApplicationSectionEnum, MultiselectQuestionsStatusEnum, PrismaClient, + UserRoleEnum, } from '@prisma/client'; import { randomInt } from 'crypto'; import dayjs from 'dayjs'; @@ -2585,6 +2586,7 @@ export const createBridgeBayJurisdictions = async ( publicSiteBaseURL, featureFlags: [...optionalV2MSQ, ...featureFlags], languages: languages, + listingApprovalPermissions: [UserRoleEnum.admin], listingFeaturesConfiguration: defaultListingFeatureConfiguration, raceEthnicityConfiguration: raceEthnicityConfiguration, visibleSpokenLanguages: visibleSpokenLanguages, diff --git a/api/src/modules/email.module.ts b/api/src/modules/email.module.ts index c31c4b67332..5e5ab1e7f50 100644 --- a/api/src/modules/email.module.ts +++ b/api/src/modules/email.module.ts @@ -1,12 +1,14 @@ import { Logger, Module } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +import { AwsSesService } from '../services/aws-ses.service'; +import { EmailProvider } from '../services/email-provider.service'; import { EmailService } from '../services/email.service'; -import { JurisdictionService } from '../services/jurisdiction.service'; -import { TranslationService } from '../services/translation.service'; import { GoogleTranslateService } from '../services/google-translate.service'; -import { EmailProvider } from '../services/email-provider.service'; +import { GovDeliveryService } from '../services/gov-delivery.service'; +import { JurisdictionService } from '../services/jurisdiction.service'; import { SendGridService } from '../services/sendgrid.service'; -import { AwsSesService } from '../services/aws-ses.service'; +import { TranslationService } from '../services/translation.service'; +import { HttpModule, HttpService } from '@nestjs/axios'; const emailProvider = { provide: EmailProvider, @@ -14,7 +16,7 @@ const emailProvider = { }; @Module({ - imports: [], + imports: [HttpModule], controllers: [], providers: [ EmailService, @@ -22,6 +24,7 @@ const emailProvider = { TranslationService, ConfigService, GoogleTranslateService, + GovDeliveryService, Logger, emailProvider, ], diff --git a/api/src/services/email.service.ts b/api/src/services/email.service.ts index a74ba3094b1..e7d871307b5 100644 --- a/api/src/services/email.service.ts +++ b/api/src/services/email.service.ts @@ -36,6 +36,7 @@ import { } from '../utilities/listing-data-formatters'; import { unitTypeMapping } from '../../prisma/seed-helpers/unit-type-factory'; import { UnitAccessibilityPriorityTypeEnum } from '../enums/units/accessibility-priority-type-enum'; +import { GovDeliveryService } from './gov-delivery.service'; dayjs.extend(utc); dayjs.extend(tz); dayjs.extend(advanced); @@ -56,6 +57,7 @@ export class EmailService { private readonly emailProvider: EmailProvider, private readonly translationService: TranslationService, private readonly jurisdictionService: JurisdictionService, + private readonly govDeliveryService: GovDeliveryService, @Inject(Logger) private logger = new Logger(EmailService.name), ) { @@ -1052,7 +1054,11 @@ export class EmailService { listingUnitsSummary: ListingUnitsSummary, variant: ListingNotificationVariant = 'standard', ): { label: string; value: string | number }[] { - const listingDetails: { label: string; value: string | number }[] = []; + const listingDetails: { + label: string; + value: string | number; + bolded?: boolean; + }[] = []; if (listing?.reservedCommunityTypes?.name) { listingDetails.push({ @@ -1074,10 +1080,10 @@ export class EmailService { } else if (listing?.applicationDueDate) { listingDetails.push({ label: this.polyglot.t('rentalOpportunity.applicationsDue'), - value: this.formatLocalDate( - listing.applicationDueDate, - 'MMMM DD, YYYY', - ), + value: dayjs(listing.applicationDueDate) + .tz(process.env.TIME_ZONE) + .format('MMMM D, YYYY [at] h:mma z'), + bolded: true, }); } @@ -1302,7 +1308,91 @@ export class EmailService { ); } } catch (err) { - console.log('listing approval email failed', err); + console.log('rental opportunity email failed', err); + throw new HttpException('email failed', 500); + } + } + + public async listingPublishNotificationViaGovDelivery( + jurisdictionId: IdDTO, + listing: Listing, + priorityTypes: UnitAccessibilityPriorityTypeEnum[], + variant: ListingNotificationVariant = 'standard', + ) { + try { + const jurisdiction = await this.getJurisdiction([jurisdictionId]); + const listingUnitsSummary = summarizeListingUnitsByType(listing.units); + const subjectKey = + variant === 'comingSoon' + ? 'rentalOpportunity.comingSoon.subject' + : 'rentalOpportunity.subject'; + const introKey = + variant === 'comingSoon' + ? 'rentalOpportunity.comingSoon.intro' + : 'rentalOpportunity.intro'; + + void (await this.loadTranslations(jurisdiction, 'en')); + + this.logger.log( + `Sending lottery published govDelivery email for listing ${listing.name}`, + ); + + const listingDetails = this.buildListingDetails( + listing, + priorityTypes, + listingUnitsSummary, + variant, + ); + + const emailButtons = jurisdiction.languages.map((code) => ({ + name: this.polyglot.t(`rentalOpportunity.viewButton.${code}`), + url: `${jurisdiction.publicUrl}/${code}/listing/${listing.id}/${listing.urlSlug}`, + })); + + const footerLinks = []; + let hasFooterLinks = true; + while (hasFooterLinks) { + if ( + this.polyglot.has( + `rentalOpportunity.footer.additionalLink${footerLinks.length}.text`, + ) + ) { + footerLinks.push({ + text: this.polyglot.t( + `rentalOpportunity.footer.additionalLink${footerLinks.length}.text`, + ), + name: this.polyglot.t( + `rentalOpportunity.footer.additionalLink${footerLinks.length}.name`, + ), + url: this.polyglot.t( + `rentalOpportunity.footer.additionalLink${footerLinks.length}.url`, + ), + }); + } else { + hasFooterLinks = false; + } + } + + await this.govDeliveryService.send({ + to: [], + from: process.env.GOVDELIVERY_FROM_EMAIL_ID, + subject: this.polyglot.t(subjectKey, { + listingName: listing.name, + }), + body: this.template('listing-opportunity')({ + listingName: listing.name, + introKey, + tableRows: listingDetails, + languageUrls: emailButtons, + accessibleMarketingFlyerUrl: listing.accessibleMarketingFlyer, + disclaimerText: this.polyglot.has('rentalOpportunity.disclaimer') + ? this.polyglot.t('rentalOpportunity.disclaimer') + : undefined, + footerLinks, + }), + }); + } catch (err) { + console.log('govDelivery rental opportunity email failed', err); throw new HttpException('email failed', 500); } } diff --git a/api/src/services/gov-delivery.service.ts b/api/src/services/gov-delivery.service.ts new file mode 100644 index 00000000000..720a3ad3f8d --- /dev/null +++ b/api/src/services/gov-delivery.service.ts @@ -0,0 +1,49 @@ +import { HttpService } from '@nestjs/axios'; +import { Injectable } from '@nestjs/common'; +import juice from 'juice'; +import { firstValueFrom } from 'rxjs'; +import { SendEmailInput } from './email-provider.service'; + +@Injectable() +export class GovDeliveryService { + constructor(private readonly httpService: HttpService) {} + + async send(input: SendEmailInput): Promise { + const { + GOVDELIVERY_API_URL, + GOVDELIVERY_USERNAME, + GOVDELIVERY_PASSWORD, + GOVDELIVERY_TOPIC, + } = process.env; + const isGovConfigured = + !!GOVDELIVERY_API_URL && + !!GOVDELIVERY_USERNAME && + !!GOVDELIVERY_PASSWORD && + !!GOVDELIVERY_TOPIC; + if (!isGovConfigured) { + console.warn( + 'failed to configure Govdelivery, ensure that all env variables are provided', + ); + return; + } + + // If there is no from email address configured, govDelivery will use the default email address associated with the account + const fromEmailAddress = process.env.GOVDELIVERY_FROM_EMAIL_ID || ''; + + // juice inlines css to allow for email styling + const inlineHtml = juice(input.body); + const govEmailXml = `\n ${input.subject}\n \n \n ${fromEmailAddress}\n false\n true\n true\n true\n \n \n ${GOVDELIVERY_TOPIC}\n \n \n \n `; + + await firstValueFrom( + this.httpService.post(GOVDELIVERY_API_URL, govEmailXml, { + headers: { + 'Content-Type': 'application/xml', + Authorization: `Basic ${Buffer.from( + `${GOVDELIVERY_USERNAME}:${GOVDELIVERY_PASSWORD}`, + ).toString('base64')}`, + }, + }), + ); + } +} diff --git a/api/src/services/listing.service.ts b/api/src/services/listing.service.ts index 2d1cce85f29..e544aacfda5 100644 --- a/api/src/services/listing.service.ts +++ b/api/src/services/listing.service.ts @@ -1935,6 +1935,19 @@ export class ListingService implements OnModuleInit { rawJurisdiction.publicUrl || '', ); await this.sendListingPublishNotification(mappedListing); + if ( + doJurisdictionHaveFeatureFlagSet( + rawJurisdiction as unknown as Jurisdiction, + FeatureFlagEnum.enableListingOpportunity, + ) + ) { + await this.emailService.listingPublishNotificationViaGovDelivery( + { id: rawJurisdiction.id }, + mappedListing, + [], + 'standard', + ); + } } await this.cachePurge(undefined, dto.status, mappedListing.id); return mappedListing; @@ -3076,6 +3089,19 @@ export class ListingService implements OnModuleInit { mappedListing, useComingSoon ? 'comingSoon' : 'standard', ); + if ( + doJurisdictionHaveFeatureFlagSet( + rawJurisdiction as unknown as Jurisdiction, + FeatureFlagEnum.enableListingOpportunity, + ) + ) { + await this.emailService.listingPublishNotificationViaGovDelivery( + { id: rawJurisdiction.id }, + mappedListing, + [], + useComingSoon ? 'comingSoon' : 'standard', + ); + } } if (enableV2MSQ && mappedListing.status === ListingsStatusEnum.active) { diff --git a/api/src/views/listing-opportunity.hbs b/api/src/views/listing-opportunity.hbs index 69cbf9628bf..add28bbf83c 100644 --- a/api/src/views/listing-opportunity.hbs +++ b/api/src/views/listing-opportunity.hbs @@ -47,11 +47,20 @@ {{/if}} - - - {{t "rentalOpportunity.footer.unsubscribeAndEmailSettings"}} - - + {{#if disclaimerText}} + + +

{{disclaimerText}}

+ + + {{/if}} + {{#if emailSettingsUrl}} + + + {{t "rentalOpportunity.footer.unsubscribeAndEmailSettings"}} + + + {{/if}} {{/layout_default }} diff --git a/api/src/views/partials/footer.hbs b/api/src/views/partials/footer.hbs index ac448bffe5b..6f958cd4013 100644 --- a/api/src/views/partials/footer.hbs +++ b/api/src/views/partials/footer.hbs @@ -19,6 +19,16 @@
{{t "footer.line2"}} + {{#if footerLinks}} + + + {{#each footerLinks}} + {{this.text}}{{this.name}} +
+ {{/each}} + + + {{/if}} diff --git a/api/test/jest-e2e.config.js b/api/test/jest-e2e.config.js index 3895121d837..2105e8ed28b 100644 --- a/api/test/jest-e2e.config.js +++ b/api/test/jest-e2e.config.js @@ -11,5 +11,5 @@ module.exports = { }, ], }, - setupFilesAfterEnv: ['jest-extended/all'], + setupFilesAfterEnv: ['jest-extended/all', './test/jest.setup.js'], }; diff --git a/api/test/jest.config.js b/api/test/jest.config.js index 1af8d85b28c..f7ca72ec990 100644 --- a/api/test/jest.config.js +++ b/api/test/jest.config.js @@ -11,5 +11,5 @@ module.exports = { }, ], }, - setupFilesAfterEnv: ['jest-extended/all'], + setupFilesAfterEnv: ['jest-extended/all', './test/jest.setup.js'], }; diff --git a/api/test/jest.setup.js b/api/test/jest.setup.js new file mode 100644 index 00000000000..f1dfda81660 --- /dev/null +++ b/api/test/jest.setup.js @@ -0,0 +1,2 @@ +// the juice library is used to inline CSS in HTML emails, but it doesn't work in the test environment. This mock allows us to bypass that functionality for testing purposes. +jest.mock('juice', () => (html) => html); diff --git a/api/test/unit/services/application-bulk-upload.service.spec.ts b/api/test/unit/services/application-bulk-upload.service.spec.ts index 5ae351a6af7..caf18c7c620 100644 --- a/api/test/unit/services/application-bulk-upload.service.spec.ts +++ b/api/test/unit/services/application-bulk-upload.service.spec.ts @@ -118,7 +118,7 @@ describe('Testing application bulk upload services', () => { mockApplication({ id: randomUUID(), position: 1, - submissionDate: new Date(2026, 4, 19, 22, 0, 0), + submissionDate: new Date(1779228000000), applicant: { firstName: 'Colleen', lastName: 'Tawnee', @@ -132,7 +132,7 @@ describe('Testing application bulk upload services', () => { mockApplication({ id: randomUUID(), position: 2, - submissionDate: new Date(2026, 3, 2, 10, 0, 0), + submissionDate: new Date(1775124000000), applicant: { firstName: 'Erin', lastName: 'Patsy', @@ -143,7 +143,7 @@ describe('Testing application bulk upload services', () => { mockApplication({ id: randomUUID(), position: 3, - submissionDate: new Date(2026, 6, 23, 15, 30, 0), + submissionDate: new Date(1784820600000), applicant: { firstName: 'Nanny', lastName: 'Hayley', diff --git a/api/test/unit/services/email.service.spec.ts b/api/test/unit/services/email.service.spec.ts index 340bc5c1a2f..111f92e7336 100644 --- a/api/test/unit/services/email.service.spec.ts +++ b/api/test/unit/services/email.service.spec.ts @@ -15,6 +15,7 @@ import { ListingNotificationVariant, } from '../../../src/services/email.service'; import { EmailProvider } from '../../../src/services/email-provider.service'; +import { GovDeliveryService } from '../../../src/services/gov-delivery.service'; import { TranslationService } from '../../../src/services/translation.service'; import { JurisdictionService } from '../../../src/services/jurisdiction.service'; import { GoogleTranslateService } from '../../../src/services/google-translate.service'; @@ -32,10 +33,12 @@ import { import { UnitAccessibilityPriorityTypeEnum } from '../../../src/enums/units/accessibility-priority-type-enum'; let sendMock; +let govDeliverySendMock; +const getMergedTranslationsMock = jest + .fn() + .mockReturnValue(translationFactory().translations); const translationServiceMock = { - getMergedTranslations: () => { - return translationFactory().translations; - }, + getMergedTranslations: getMergedTranslationsMock, }; const jurisdictionServiceMock = { @@ -68,6 +71,10 @@ describe('Testing email service', () => { provide: JurisdictionService, useValue: jurisdictionServiceMock, }, + { + provide: GovDeliveryService, + useValue: { send: jest.fn() }, + }, GoogleTranslateService, ], }).compile(); @@ -78,7 +85,14 @@ describe('Testing email service', () => { emailProvider = module.get(EmailProvider); sendMock = jest.fn(); emailProvider.send = sendMock; + const govDeliveryService = + module.get(GovDeliveryService); + govDeliverySendMock = jest.fn(); + govDeliveryService.send = govDeliverySendMock; service = await module.resolve(EmailService); + getMergedTranslationsMock + .mockClear() + .mockReturnValue(translationFactory().translations); }); const user = { @@ -1089,12 +1103,12 @@ describe('Testing email service', () => { it('includes applications due row when applicationDueDate is set', () => { const result = buildDetails( baseListing({ - applicationDueDate: new Date(2026, 4, 19), + applicationDueDate: new Date(1779228000000), } as Partial), ); const row = findByLabel(result, LABELS.applicationsDue); expect(row).toBeDefined(); - expect(row.value).toBe('May 19, 2026'); + expect(row.value).toBe('May 19, 2026 at 3:00pm PDT'); }); it('omits applications due row when applicationDueDate is absent', () => { @@ -1660,4 +1674,86 @@ describe('Testing email service', () => { expect(sendMock.mock.calls[0][0].body).not.toContain('Applications Due'); }); }); + + describe('listing publish notification via govDelivery', () => { + const notificationListing = { + id: 'listingId', + name: 'Test Listing', + urlSlug: 'test_listing', + units: [], + listingEvents: [], + listingsBuildingAddress: yellowstoneAddress, + applicationDueDate: new Date(2026, 4, 19), + } as unknown as Listing; + + let jurisdictionSpy: jest.SpyInstance; + + beforeEach(() => { + jurisdictionSpy = jest + .spyOn(jurisdictionServiceMock, 'findOne') + .mockReturnValue({ + id: 'jurisdictionId', + name: 'Jurisdiction 1', + publicUrl: 'https://example.com', + emailFromAddress: 'no-reply@example.com', + languages: [LanguagesEnum.en], + } as unknown as ReturnType); + }); + + afterEach(() => { + jurisdictionSpy.mockRestore(); + }); + + it('calls govDeliveryService.send with the correct subject and body for the standard variant', async () => { + await service.listingPublishNotificationViaGovDelivery( + { id: 'jurisdictionId' }, + notificationListing, + [], + ); + expect(govDeliverySendMock).toHaveBeenCalledTimes(1); + expect(govDeliverySendMock.mock.calls[0][0].subject).toEqual( + 'New rental opportunity at Test Listing', + ); + expect(govDeliverySendMock.mock.calls[0][0].body).toContain( + 'Rental opportunity at', + ); + expect(govDeliverySendMock.mock.calls[0][0].body).toContain( + 'Applications Due', + ); + expect(govDeliverySendMock.mock.calls[0][0].body).not.toContain( + 'Applications Open', + ); + }); + + it('calls govDeliveryService.send with the footer links if exist', async () => { + getMergedTranslationsMock.mockReturnValue({ + rentalOpportunity: { + footer: { + additionalLink0: { + text: 'explore more at', + name: 'Test Link', + url: 'https://example.com', + }, + additionalLink1: { + text: 'explore even more at', + name: 'Test Link 2', + url: 'https://example.com', + }, + }, + }, + }); + await service.listingPublishNotificationViaGovDelivery( + { id: 'jurisdictionId' }, + notificationListing, + [], + ); + expect(govDeliverySendMock).toHaveBeenCalledTimes(1); + expect(govDeliverySendMock.mock.calls[0][0].body).toContain( + 'explore more atTest Link', + ); + expect(govDeliverySendMock.mock.calls[0][0].body).toContain( + 'explore even more atTest Link 2', + ); + }); + }); }); diff --git a/api/test/unit/services/listing.service.spec.ts b/api/test/unit/services/listing.service.spec.ts index a215a29f508..56e5a9bab34 100644 --- a/api/test/unit/services/listing.service.spec.ts +++ b/api/test/unit/services/listing.service.spec.ts @@ -222,6 +222,7 @@ const lotteryReleasedMock = jest.fn(); const lotteryPublishedAdminMock = jest.fn(); const lotteryPublishedApplicantMock = jest.fn(); const listingPublishNotificationMock = jest.fn(); +const listingPublishNotificationViaGovDeliveryMock = jest.fn(); const canOrThrowMock = jest.fn(); @@ -291,6 +292,8 @@ describe('Testing listing service', () => { lotteryPublishedAdmin: lotteryPublishedAdminMock, lotteryPublishedApplicant: lotteryPublishedApplicantMock, listingPublishNotification: listingPublishNotificationMock, + listingPublishNotificationViaGovDelivery: + listingPublishNotificationViaGovDeliveryMock, }, }, { @@ -7289,6 +7292,73 @@ describe('Testing listing service', () => { jest.useRealTimers(); }); }); + + it('should call listingPublishNotificationViaGovDelivery when enableListingOpportunity flag is on', async () => { + prisma.jurisdictions.findUnique = jest.fn().mockResolvedValue({ + id: 'jurisdiction-id', + publicUrl: 'public.housing.gov', + featureFlags: [ + { name: FeatureFlagEnum.enableListingOpportunity, active: true }, + ], + listingApprovalPermissions: [], + }); + prisma.listings.findUnique = jest.fn().mockResolvedValue({ + id: 'listing-id', + name: 'listing name', + status: ListingsStatusEnum.pending, + jurisdictionId: 'jurisdiction-id', + listingMultiselectQuestions: [], + }); + prisma.listings.update = jest.fn().mockResolvedValue({ + id: 'listing-id', + name: 'listing name', + status: ListingsStatusEnum.active, + listingMultiselectQuestions: [], + units: [], + }); + prisma.listingEvents.findMany = jest.fn().mockResolvedValue([]); + prisma.listingSnapshot.create = jest + .fn() + .mockResolvedValue({ id: 'snapshot-id' }); + prisma.$transaction = jest.fn().mockResolvedValue([ + { + id: 'listing-id', + name: 'listing name', + status: ListingsStatusEnum.active, + listingMultiselectQuestions: [], + units: [], + }, + ]); + jest + .spyOn(service, 'getUserEmailInfo') + .mockResolvedValueOnce({ emails: ['partner@email.com'] }); + jest + .spyOn(service, 'sendListingPublishNotification') + .mockResolvedValueOnce(undefined); + + await service.update( + { + id: 'listing-id', + name: 'listing name', + depositMin: '5', + assets: [], + jurisdictions: { id: 'jurisdiction-id' }, + status: ListingsStatusEnum.active, + displayWaitlistSize: false, + unitsSummary: null, + listingEvents: [], + lastUpdatedByUser: user, + } as ListingUpdate, + user, + ); + + expect(listingPublishNotificationViaGovDeliveryMock).toHaveBeenCalledWith( + { id: 'jurisdiction-id' }, + expect.objectContaining({ id: 'listing-id' }), + [], + 'standard', + ); + }); }); describe('Test listingApprovalNotify endpoint', () => { diff --git a/api/test/unit/services/user.service.spec.ts b/api/test/unit/services/user.service.spec.ts index 08e46387fe3..ea827184f5f 100644 --- a/api/test/unit/services/user.service.spec.ts +++ b/api/test/unit/services/user.service.spec.ts @@ -32,6 +32,8 @@ import { AdvocateUserUpdate } from '../../../src/dtos/users/advocate-user-update import { UserOrderByKeys } from '../../../src/enums/listings/order-by-enum'; import { Jurisdiction } from '../../../src/dtos/jurisdictions/jurisdiction.dto'; import { UserNotificationPreferences } from '../../../src/dtos/users/user-notification-preferences.dto'; +import { GovDeliveryService } from '../../../src/services/gov-delivery.service'; +import { HttpService } from '@nestjs/axios'; describe('Testing user service', () => { let service: UserService; @@ -90,6 +92,9 @@ describe('Testing user service', () => { warn: jest.fn(), error: jest.fn(), }; + const HttpServiceMock = { + post: jest.fn(), + }; const canOrThrowMock = jest.fn(); @@ -109,10 +114,15 @@ describe('Testing user service', () => { SchedulerRegistry, CronJobService, SnapshotCreateService, + GovDeliveryService, { provide: EmailProvider, useValue: EmailProviderMock, }, + { + provide: HttpService, + useValue: HttpServiceMock, + }, { provide: GoogleTranslateService, useValue: googleTranslateServiceMock, diff --git a/api/yarn.lock b/api/yarn.lock index 67d543eaebc..010dce5d81f 100644 --- a/api/yarn.lock +++ b/api/yarn.lock @@ -1341,6 +1341,16 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@csstools/selector-resolve-nested@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/selector-resolve-nested/-/selector-resolve-nested-4.0.0.tgz#ccc38c2cdc14c3ddd1d94647803facef654d47e2" + integrity sha512-9vAPxmp+Dx3wQBIUwc1v7Mdisw1kbbaGqXUM8QLTgWg7SoPGYtXBsMXvsFs/0Bn5yoFhcktzxNZGNaUt0VjgjA== + +"@csstools/selector-specificity@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-6.0.0.tgz#ef28e27c1ded1d8e5c54879a9399e7055aed1920" + integrity sha512-4sSgl78OtOXEX/2d++8A83zHNTgwCJMaR24FvsYL7Uf/VS8HZk9PTwR51elTbGqMuwH3szLvvOXEaVnqn0Z3zA== + "@dabh/diagnostics@^2.0.8": version "2.0.8" resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.8.tgz#ead97e72ca312cf0e6dd7af0d300b58993a31a5e" @@ -3855,7 +3865,7 @@ ajv@^8.9.0: json-schema-traverse "^1.0.0" require-from-string "^2.0.2" -ansi-colors@4.1.3: +ansi-colors@4.1.3, ansi-colors@^4.1.1: version "4.1.3" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== @@ -4236,6 +4246,11 @@ body-parser@~1.20.3: type-is "~1.6.18" unpipe "~1.0.0" +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + bowser@^2.11.0: version "2.11.0" resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" @@ -4461,6 +4476,35 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +cheerio-select@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4" + integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g== + dependencies: + boolbase "^1.0.0" + css-select "^5.1.0" + css-what "^6.1.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + +cheerio@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.2.0.tgz#f23b777c49021ead7475dcf3390d3535a7f896d6" + integrity sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg== + dependencies: + cheerio-select "^2.1.0" + dom-serializer "^2.0.0" + domhandler "^5.0.3" + domutils "^3.2.2" + encoding-sniffer "^0.2.1" + htmlparser2 "^10.1.0" + parse5 "^7.3.0" + parse5-htmlparser2-tree-adapter "^7.1.0" + parse5-parser-stream "^7.1.2" + undici "^7.19.0" + whatwg-mimetype "^4.0.0" + chokidar@3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" @@ -4658,6 +4702,11 @@ commander@4.1.1: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== +commander@^14.0.3: + version "14.0.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-14.0.3.tgz#425d79b48f9af82fcd9e4fc1ea8af6c5ec07bbc2" + integrity sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw== + commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -4870,6 +4919,27 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +css-select@^5.1.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.2.2.tgz#01b6e8d163637bb2dd6c982ca4ed65863682786e" + integrity sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-what@^6.1.0: + version "6.2.2" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.2.2.tgz#cdcc8f9b6977719fdfbd1de7aec24abf756b9dea" + integrity sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + csv-parse@^5.3.5: version "5.5.2" resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-5.5.2.tgz#ab525e642093dccff7c5cca5c7b71fd3e99fe8f2" @@ -5053,7 +5123,7 @@ domhandler@^5.0.2, domhandler@^5.0.3: dependencies: domelementtype "^2.3.0" -domutils@^3.2.2: +domutils@^3.0.1, domutils@^3.1.0, domutils@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78" integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw== @@ -5155,6 +5225,14 @@ encodeurl@~2.0.0: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== +encoding-sniffer@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz#396ec97ac22ce5a037ba44af1992ac9d46a7b819" + integrity sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw== + dependencies: + iconv-lite "^0.6.3" + whatwg-encoding "^3.1.1" + end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -5170,16 +5248,26 @@ enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.1, enhanced-resolve@^5.19.0, enh graceful-fs "^4.2.4" tapable "^2.3.3" -entities@^4.2.0: +entities@^4.2.0, entities@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== +entities@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-6.0.1.tgz#c28c34a43379ca7f61d074130b2f5f7020a30694" + integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== + entities@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/entities/-/entities-7.0.1.tgz#26e8a88889db63417dcb9a1e79a3f1bc92b5976b" integrity sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA== +entities@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-8.0.0.tgz#c1df5fe3602429747fa233d0dd26f142f0ce4743" + integrity sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA== + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -5241,6 +5329,11 @@ escalade@^3.2.0: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== +escape-goat@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-3.0.0.tgz#e8b5fb658553fe8a3c4959c316c6ebb8c842b19c" + integrity sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw== + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -6213,6 +6306,16 @@ htmlparser2@^10.1, htmlparser2@^10.1.0: domutils "^3.2.2" entities "^7.0.1" +htmlparser2@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-9.1.0.tgz#cdb498d8a75a51f739b61d3f718136c369bc8c23" + integrity sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.1.0" + entities "^4.5.0" + http-errors@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" @@ -6272,6 +6375,13 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@0.6.3, iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -7120,6 +7230,20 @@ jszip@^3.10.1: readable-stream "~2.3.6" setimmediate "^1.0.5" +juice@^12.1.1: + version "12.1.1" + resolved "https://registry.yarnpkg.com/juice/-/juice-12.1.1.tgz#3e9ecd8207d063df620b3abb0b602413b6352d8f" + integrity sha512-AKmKwtRPtAq4K4uRL8yaH1WwyBAwTgYjYqGhlyE9c7wbM2IthmxDKHELKL6mGI3HR76lCSJZu/RVMffC0xSM+A== + dependencies: + cheerio "1.2.0" + commander "^14.0.3" + entities "^8.0.0" + postcss "^8.5.0" + postcss-nesting "^14.0.0" + postcss-safe-parser "^7.0.1" + postcss-selector-parser "^7.1.0" + web-resource-inliner "^8.0.0" + jwa@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" @@ -7520,7 +7644,7 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@2.6.0: +mime@2.6.0, mime@^2.4.6: version "2.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== @@ -7730,6 +7854,13 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + object-assign@^4, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -7890,6 +8021,28 @@ parse-srcset@^1.0.2: resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1" integrity sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q== +parse5-htmlparser2-tree-adapter@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz#b5a806548ed893a43e24ccb42fbb78069311e81b" + integrity sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g== + dependencies: + domhandler "^5.0.3" + parse5 "^7.0.0" + +parse5-parser-stream@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz#d7c20eadc37968d272e2c02660fff92dd27e60e1" + integrity sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow== + dependencies: + parse5 "^7.0.0" + +parse5@^7.0.0, parse5@^7.3.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.3.0.tgz#d7e224fa72399c7a175099f45fc2ad024b05ec05" + integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== + dependencies: + entities "^6.0.0" + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -8080,6 +8233,28 @@ pluralize@8.0.0: resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== +postcss-nesting@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-14.0.0.tgz#5400139a425da2ca8228bc406e67e0e8d344b137" + integrity sha512-YGFOfVrjxYfeGTS5XctP1WCI5hu8Lr9SmntjfRC+iX5hCihEO+QZl9Ra+pkjqkgoVdDKvb2JccpElcowhZtzpw== + dependencies: + "@csstools/selector-resolve-nested" "^4.0.0" + "@csstools/selector-specificity" "^6.0.0" + postcss-selector-parser "^7.1.1" + +postcss-safe-parser@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz#36e4f7e608111a0ca940fd9712ce034718c40ec0" + integrity sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A== + +postcss-selector-parser@^7.1.0, postcss-selector-parser@^7.1.1: + version "7.1.4" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz#69dc7a526517572ff6b150e352b36a016017b485" + integrity sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + postcss@^8.3.11: version "8.5.15" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.15.tgz#d1eaf677a324e9ec02196da2d3fecf4a0b9a735c" @@ -8089,6 +8264,15 @@ postcss@^8.3.11: picocolors "^1.1.1" source-map-js "^1.2.1" +postcss@^8.5.0: + version "8.5.16" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.16.tgz#1230ce0b5df354c24c0ea45f99ce5f6a88279d28" + integrity sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg== + dependencies: + nanoid "^3.3.12" + picocolors "^1.1.1" + source-map-js "^1.2.1" + postgres-array@3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-3.0.2.tgz#68d6182cb0f7f152a7e60dc6a6889ed74b0a5f98" @@ -8470,7 +8654,7 @@ safe-stable-stringify@^2.3.1: resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd" integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -8792,16 +8976,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@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", 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== @@ -8833,14 +9008,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== @@ -9276,6 +9444,11 @@ uid@2.0.2: dependencies: "@lukeed/csprng" "^1.0.0" +undici@^7.19.0: + version "7.28.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-7.28.0.tgz#97d64564198b285bc281f0e8e29597e3d11fe7ec" + integrity sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA== + universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" @@ -9317,7 +9490,7 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -util-deprecate@^1.0.1, util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== @@ -9351,6 +9524,11 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" +valid-data-url@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/valid-data-url/-/valid-data-url-3.0.1.tgz#826c1744e71b5632e847dd15dbd45b9fb38aa34f" + integrity sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA== + validator@^13.7.0: version "13.15.35" resolved "https://registry.yarnpkg.com/validator/-/validator-13.15.35.tgz#81cf455c51f15b69d8d340be5914f3fab00dbf7f" @@ -9390,6 +9568,17 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +web-resource-inliner@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/web-resource-inliner/-/web-resource-inliner-8.0.0.tgz#72c2856ab52b82939f538736bca58082f8c2fd76" + integrity sha512-Ezr98sqXW/+OCGoUEXuOKVR+oVFlSdn1tIySEEJdiSAw4IjrW8hQkwARSSBJTSB5Us5dnytDgL0ZDliAYBhaNA== + dependencies: + ansi-colors "^4.1.1" + escape-goat "^3.0.0" + htmlparser2 "^9.1.0" + mime "^2.4.6" + valid-data-url "^3.0.0" + web-streams-polyfill@^3.0.3: version "3.3.3" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" @@ -9470,6 +9659,18 @@ webpack@^5.105.2: watchpack "^2.5.1" webpack-sources "^3.3.3" +whatwg-encoding@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5" + integrity sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ== + dependencies: + iconv-lite "0.6.3" + +whatwg-mimetype@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" + integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -9521,7 +9722,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"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== @@ -9539,15 +9740,6 @@ wrap-ansi@^6.0.1, 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"