diff --git a/apps/backend/jest.config.ts b/apps/backend/jest.config.ts index 3a31ab8..a7aadd0 100644 --- a/apps/backend/jest.config.ts +++ b/apps/backend/jest.config.ts @@ -8,4 +8,5 @@ export default { }, moduleFileExtensions: ['ts', 'js', 'html'], coverageDirectory: '../../coverage/apps/backend', + testMatch: ['/src/**/*.spec.ts', '/test/**/*.spec.ts', '/test/**/*.e2e-spec.ts'], }; diff --git a/apps/backend/src/donations/donation.entity.ts b/apps/backend/src/donations/donation.entity.ts index fc9a168..906a013 100644 --- a/apps/backend/src/donations/donation.entity.ts +++ b/apps/backend/src/donations/donation.entity.ts @@ -1,4 +1,10 @@ -import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, +} from 'typeorm'; export enum DonationType { ONE_TIME = 'one_time', @@ -36,7 +42,7 @@ export class Donation { @Column() email: string; - @Column({ type: 'numeric', precision: 10, scale: 2 }) + @Column({ type: 'int' }) amount: number; @Column({ default: false }) @@ -60,9 +66,9 @@ export class Donation { @Column({ nullable: true }) transactionId: string | null; - @Column() + @CreateDateColumn({ type: 'timestamp', default: () => 'now()' }) createdAt: Date; - @Column() + @UpdateDateColumn({ type: 'timestamp', default: () => 'now()' }) updatedAt: Date; } diff --git a/apps/backend/src/donations/donations.controller.spec.ts b/apps/backend/src/donations/donations.controller.spec.ts index 9d48d18..7ab7a9c 100644 --- a/apps/backend/src/donations/donations.controller.spec.ts +++ b/apps/backend/src/donations/donations.controller.spec.ts @@ -9,7 +9,8 @@ import { DonationStatus, } from './donation.entity'; import { Donation as DomainDonation } from './mappers'; - +import { INestApplication, BadRequestException } from '@nestjs/common'; +import request from 'supertest'; describe('DonationsController', () => { let controller: DonationsController; let service: DonationsService; @@ -295,3 +296,638 @@ describe('DonationsController', () => { }); }); }); + +interface TestDonation { + id: number; + firstName: string; + lastName: string; + email: string; + amount: number; + isAnonymous: boolean; + donationType: DonationType; + recurringInterval: RecurringInterval | null; + dedicationMessage?: string | null; + showDedicationPublicly: boolean; + status: DonationStatus; + createdAt: Date; + updatedAt: Date; + transactionId?: string | null; +} + +describe('Donation Integration', () => { + // Increase Jest timeout for slower CI/initialization (DB + Nest app init) + // Default is 5000ms which is often too small for integration tests. + jest.setTimeout(30000); + + let app: INestApplication; + // We use an in-memory array to simulate stored donations for controller tests + let inMemoryDonations: TestDonation[] = []; + let nextId = 1; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let mockService: any; + + beforeAll(async () => { + // Create a testing module that instantiates the DonationsController but + // uses a simple in-memory mock for the DonationsService so tests don't + // depend on TypeORM behavior during controller-level validation tests. + inMemoryDonations = []; + nextId = 1; + + mockService = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + create: jest.fn(async (request: any) => { + // Validation to match real service behavior + if ( + request.donationType === 'recurring' && + !request.recurringInterval + ) { + throw new BadRequestException( + 'Recurring donation must specify interval.', + ); + } + + const now = new Date(); + const donation = { + id: nextId++, + firstName: request.firstName, + lastName: request.lastName, + email: request.email, + amount: request.amount, + isAnonymous: request.isAnonymous ?? false, + donationType: + request.donationType === 'one_time' + ? DonationType.ONE_TIME + : DonationType.RECURRING, + recurringInterval: request.recurringInterval ?? null, + dedicationMessage: request.dedicationMessage ?? undefined, + showDedicationPublicly: request.showDedicationPublicly ?? false, + status: DonationStatus.SUCCEEDED, + createdAt: now, + updatedAt: now, + }; + inMemoryDonations.push(donation); + return donation; + }), + findPublic: jest.fn(async (limit?: number) => { + return inMemoryDonations + .filter( + (d) => d.status === DonationStatus.SUCCEEDED && !d.isAnonymous, + ) + .slice(0, limit ?? 50); + }), + getTotalDonations: jest.fn(async () => { + const succeeded = inMemoryDonations.filter( + (d) => d.status === DonationStatus.SUCCEEDED, + ); + const total = succeeded.reduce((s, d) => s + (d.amount || 0), 0); + return { total, count: succeeded.length }; + }), + }; + + const moduleFixture: TestingModule = await Test.createTestingModule({ + controllers: [DonationsController], + providers: [ + { provide: DonationsService, useValue: mockService }, + { provide: DonationsRepository, useValue: {} }, + ], + }).compile(); + + app = moduleFixture.createNestApplication(); + // Match runtime API prefix used by the real application + app.setGlobalPrefix('api'); + await app.init(); + }); + + afterAll(async () => { + if (app) { + await app.close(); + } + }); + + // Helper sample payloads used in the stubs below + const oneTimePayload = { + firstName: 'Jane', + lastName: 'Doe', + email: 'jane.doe@example.com', + amount: 50, + isAnonymous: false, + donationType: DonationType.ONE_TIME, + }; + + const recurringPayload = { + firstName: 'Alice', + lastName: 'Smith', + email: 'alice.smith@example.com', + amount: 25, + isAnonymous: false, + donationType: DonationType.RECURRING, + recurringInterval: RecurringInterval.MONTHLY, + }; + + // Reusable seeds and helpers for in-memory donation construction + const donationASeed: Pick = { + email: 'a@example.com', + amount: 10, + }; + const donationBSeed: Pick = { + email: 'b@example.com', + amount: 15, + }; + + function buildTestDonation( + seed: Pick, + now: Date, + overrides: Partial = {}, + ): TestDonation { + return { + id: nextId++, + firstName: oneTimePayload.firstName, + lastName: oneTimePayload.lastName, + email: seed.email, + amount: seed.amount, + isAnonymous: oneTimePayload.isAnonymous, + donationType: oneTimePayload.donationType, + recurringInterval: null, + dedicationMessage: null, + showDedicationPublicly: false, + status: DonationStatus.SUCCEEDED, + createdAt: now, + updatedAt: now, + transactionId: null, + ...overrides, + }; + } + + // ---------- DTO shape validators ---------- + const isISODateString = (value: unknown): boolean => { + if (typeof value !== 'string') return false; + const dt = new Date(value); + return !Number.isNaN(dt.getTime()); + }; + + const expectDonationResponseDtoShape = ( + obj: Record, + expected: { + donationType: DonationType; + recurringInterval?: RecurringInterval | null; + email?: string; + firstName?: string; + lastName?: string; + transactionIdPresent?: boolean; + }, + ) => { + expect(typeof obj.id).toBe('number'); + expect(typeof obj.firstName).toBe('string'); + expect(typeof obj.lastName).toBe('string'); + expect(typeof obj.email).toBe('string'); + expect(typeof obj.amount).toBe('number'); + expect(typeof obj.isAnonymous).toBe('boolean'); + expect(obj.donationType).toBe(expected.donationType); + if (expected.recurringInterval) { + expect(obj.recurringInterval).toBe(expected.recurringInterval); + } else { + // Could be null or undefined depending on mapper; both acceptable + expect(['undefined', 'string', 'object']).toContain( + typeof obj.recurringInterval as string, + ); + if (typeof obj.recurringInterval === 'string') { + expect([ + RecurringInterval.WEEKLY, + RecurringInterval.MONTHLY, + RecurringInterval.BIMONTHLY, + RecurringInterval.QUARTERLY, + RecurringInterval.ANNUALLY, + ]).toContain(obj.recurringInterval); + } else if (typeof obj.recurringInterval === 'object') { + // JSON null + expect(obj.recurringInterval).toBeNull(); + } + } + // dedicationMessage is optional + if (obj.dedicationMessage !== undefined && obj.dedicationMessage !== null) { + expect(typeof obj.dedicationMessage).toBe('string'); + } + expect(typeof obj.showDedicationPublicly).toBe('boolean'); + expect(['pending', 'succeeded', 'failed', 'cancelled']).toContain( + obj.status as string, + ); + expect(isISODateString(String(obj.createdAt))).toBe(true); + expect(isISODateString(String(obj.updatedAt))).toBe(true); + if (expected.transactionIdPresent) { + expect(typeof obj.transactionId).toBe('string'); + } else { + // Can be absent or null + expect(['undefined', 'string']).toContain( + typeof obj.transactionId as string, + ); + } + }; + + const expectPublicDonationDtoShape = ( + obj: Record, + opts: { anonymous: boolean; hasDedication: boolean }, + ) => { + expect(typeof obj.id).toBe('number'); + expect(typeof obj.amount).toBe('number'); + expect(typeof obj.isAnonymous).toBe('boolean'); + expect([DonationType.ONE_TIME, DonationType.RECURRING]).toContain( + obj.donationType as DonationType, + ); + if (obj.recurringInterval !== undefined && obj.recurringInterval !== null) { + expect([ + RecurringInterval.WEEKLY, + RecurringInterval.MONTHLY, + RecurringInterval.BIMONTHLY, + RecurringInterval.QUARTERLY, + RecurringInterval.ANNUALLY, + ]).toContain(obj.recurringInterval as RecurringInterval); + } + expect(['pending', 'succeeded', 'failed', 'cancelled']).toContain( + obj.status as string, + ); + expect(isISODateString(String(obj.createdAt))).toBe(true); + + if (opts.anonymous) { + expect(obj).not.toHaveProperty('donorName'); + } else { + expect(typeof obj.donorName).toBe('string'); + expect((obj.donorName as string).length).toBeGreaterThan(0); + } + + if (opts.hasDedication) { + expect(typeof obj.dedicationMessage).toBe('string'); + } else { + expect(obj).not.toHaveProperty('dedicationMessage'); + } + + // Ensure sensitive fields are not leaked in public DTO + expect(obj).not.toHaveProperty('email'); + expect(obj).not.toHaveProperty('firstName'); + expect(obj).not.toHaveProperty('lastName'); + expect(obj).not.toHaveProperty('transactionId'); + }; + + it('smoke: GET / (should 404 or 200 depending on routes)', async () => { + const res = await request(app.getHttpServer()).get('/'); + expect([200, 404]).toContain(res.status); + }); + + describe('POST /api/donations', () => { + it('rejects a negative amount (returns 400)', async () => { + const payload = { ...oneTimePayload, amount: -10 }; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + }); + + it('rejects an invalid email format amount (returns 400)', async () => { + const payload = { ...oneTimePayload, email: 'not-an-email' }; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + }); + + it('rejects a donation marked recurring if recurringInterval is missing', async () => { + const payload: Partial = { ...recurringPayload }; + delete payload.recurringInterval; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + }); + + it('rejects a one-time donation that has a recurring interval (returns 400)', async () => { + const payload: Record = { + ...oneTimePayload, + recurringInterval: 'MONTHLY', + }; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + }); + + it('throws 500 server error if the database errors', async () => { + // Simulate a DB failure by making the mocked service throw + mockService.create.mockRejectedValueOnce( + new Error('Simulated DB failure'), + ); + const payload = { ...oneTimePayload }; + try { + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(500); + expect(res.body).toHaveProperty('statusCode', 500); + expect(res.body).toHaveProperty('message'); + } finally { + mockService.create.mockClear(); + } + }); + + it('gracefully rejects a payload that is missing the first name', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any = { ...oneTimePayload }; + delete payload.firstName; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + expect(String(res.body.message).toLowerCase()).toContain( + 'firstName'.toLowerCase(), + ); + }); + + it('gracefully rejects a payload that is missing the last name', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any = { ...oneTimePayload }; + delete payload.lastName; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + expect(String(res.body.message).toLowerCase()).toContain( + 'lastName'.toLowerCase(), + ); + }); + + it('gracefully rejects a payload that is missing the email', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any = { ...oneTimePayload }; + delete payload.email; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + expect(String(res.body.message).toLowerCase()).toContain( + 'email'.toLowerCase(), + ); + }); + + it('gracefully rejects a payload that is missing the amount', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any = { ...oneTimePayload }; + delete payload.amount; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + expect(String(res.body.message).toLowerCase()).toContain( + 'amount'.toLowerCase(), + ); + }); + + it('Successfuly commits a one-time donation creation even if isAnonymous is missing', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any = { ...oneTimePayload }; + delete payload.isAnonymous; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(201); + + expect(res.body).toHaveProperty('id'); + expect(res.body.amount).toBe(payload.amount); + + const created = inMemoryDonations.find((d) => d.email === payload.email); + expect(created).toBeDefined(); + expect(created!.amount).toBe(payload.amount); + }); + + it('gracefully rejects a payload that is missing the donationType', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any = { ...oneTimePayload }; + delete payload.donationType; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + expect(String(res.body.message).toLowerCase()).toContain( + 'donationType'.toLowerCase(), + ); + }); + + it('gracefully rejects a payload that is contains the wrong recurring interval (not the enum)', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any = { ...oneTimePayload, recurring: 'invalid' }; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + expect(String(res.body.message).toLowerCase()).toContain( + 'recurring'.toLowerCase(), + ); + }); + + it('gracefully rejects a payload that is contains the wrong donation type (not the enum)', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any = { ...oneTimePayload, donationType: 'invalid' }; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + expect(String(res.body.message).toLowerCase()).toContain( + 'donationType'.toLowerCase(), + ); + }); + }); + + describe('GET /api/donations/public', () => { + it('returns only non-anonymous donations', async () => { + const now = new Date(); + inMemoryDonations.push({ + ...oneTimePayload, + email: 'public@example.com', + isAnonymous: false, + status: DonationStatus.SUCCEEDED, + createdAt: now, + updatedAt: now, + id: nextId++, + } as TestDonation); + inMemoryDonations.push({ + ...oneTimePayload, + email: 'anon@example.com', + isAnonymous: true, + status: DonationStatus.SUCCEEDED, + createdAt: now, + updatedAt: now, + id: nextId++, + } as TestDonation); + + const res = await request(app.getHttpServer()) + .get('/api/donations/public') + .expect(200); + + expect(Array.isArray(res.body)).toBe(true); + expect( + res.body.every( + (d: { isAnonymous: boolean }) => d.isAnonymous === false, + ), + ).toBe(true); + // Validate public DTO shape for the first item + if (res.body.length > 0) { + expectPublicDonationDtoShape(res.body[0], { + anonymous: false, + hasDedication: false, + }); + } + }); + + it('returns no donations if there are none in the database', async () => { + const res = await request(app.getHttpServer()) + .get('/api/donations/public') + .expect(200); + + expect(Array.isArray(res.body)).toBe(true); + expect( + res.body.every( + (d: { isAnonymous: boolean }) => d.isAnonymous === false, + ), + ).toBe(true); + }); + + it('throws 500 server error if the database errors', async () => { + // Simulate DB find/query failures by making the mocked service throw + mockService.findPublic.mockRejectedValueOnce( + new Error('Simulated DB failure'), + ); + try { + const res = await request(app.getHttpServer()) + .get('/api/donations/public') + .expect(500); + expect(res.body).toHaveProperty('statusCode', 500); + expect(res.body).toHaveProperty('message'); + } finally { + mockService.findPublic.mockClear(); + } + }); + + it('Returns items with correct DTO (expected keys)', async () => { + inMemoryDonations.length = 0; + const now = new Date(); + inMemoryDonations.push( + buildTestDonation({ email: 'x@example.com', amount: 11 }, now, { + isAnonymous: false, + showDedicationPublicly: true, + dedicationMessage: 'Nice work', + donationType: DonationType.RECURRING, + recurringInterval: RecurringInterval.MONTHLY, + }), + ); + + const res = await request(app.getHttpServer()) + .get('/api/donations/public') + .expect(200); + + expect(Array.isArray(res.body)).toBe(true); + if (res.body.length > 0) { + const item = res.body[0]; + const keys = Object.keys(item); + const required = [ + 'id', + 'amount', + 'isAnonymous', + 'donationType', + 'status', + 'createdAt', + ]; + const optional = [ + 'donorName', + 'recurringInterval', + 'dedicationMessage', + ]; + const allowed = [...required, ...optional]; + + required.forEach((k) => expect(keys).toContain(k)); + keys.forEach((k) => expect(allowed).toContain(k)); + } + }); + }); + + describe('GET /api/donations/stats', () => { + it('successfully returns the correct total and count', async () => { + // Example: seed two donations and verify totals endpoint + inMemoryDonations.length = 0; // reset + const now = new Date(); + inMemoryDonations.push(buildTestDonation(donationASeed, now)); + inMemoryDonations.push(buildTestDonation(donationBSeed, now)); + + const res = await request(app.getHttpServer()) + .get('/api/donations/stats') + .expect(200); + + expect(res.body).toEqual({ total: 25, count: 2 }); + }); + + it('successfully returns the correct total and count even if the database is empty', async () => { + inMemoryDonations.length = 0; + const res = await request(app.getHttpServer()) + .get('/api/donations/stats') + .expect(200); + + expect(res.body).toEqual({ total: 0, count: 0 }); + }); + + it('throws 500 server error if the database errors', async () => { + mockService.getTotalDonations.mockRejectedValueOnce( + new Error('Simulated DB failure'), + ); + try { + const res = await request(app.getHttpServer()) + .get('/api/donations/stats') + .expect(500); + expect(res.body).toHaveProperty('statusCode', 500); + expect(res.body).toHaveProperty('message'); + } finally { + mockService.getTotalDonations.mockClear(); + } + }); + }); +}); diff --git a/apps/backend/src/donations/donations.controller.ts b/apps/backend/src/donations/donations.controller.ts index 83ce6d4..cba94bc 100644 --- a/apps/backend/src/donations/donations.controller.ts +++ b/apps/backend/src/donations/donations.controller.ts @@ -114,7 +114,8 @@ export class DonationsController { }, }) async getStats(): Promise<{ total: number; count: number }> { - return this.donationsService.getTotalDonations(); + const stats = await this.donationsService.getTotalDonations(); + return stats; } @Get() diff --git a/apps/backend/src/donations/donations.service.ts b/apps/backend/src/donations/donations.service.ts index 3bff2f0..46d3860 100644 --- a/apps/backend/src/donations/donations.service.ts +++ b/apps/backend/src/donations/donations.service.ts @@ -1,7 +1,12 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { DonationResponseDto } from './dtos/donation-response-dto'; import { InjectRepository } from '@nestjs/typeorm'; -import { Donation, DonationType, RecurringInterval } from './donation.entity'; +import { + Donation, + DonationType, + RecurringInterval, + DonationStatus, +} from './donation.entity'; import { Repository } from 'typeorm'; import { CreateDonationRequest, Donation as DomainDonation } from './mappers'; @@ -62,33 +67,39 @@ export class DonationsService { showDedicationPublicly: createDonationRequest.showDedicationPublicly, }); + // Reload from database so any DB-side defaults are reflected const savedDonation = await this.donationRepository.save(donation); + const reloaded = await this.donationRepository.findOne({ + where: { id: savedDonation.id }, + }); + + const finalDonation = reloaded ?? savedDonation; return { - id: savedDonation.id, - firstName: savedDonation.firstName, - lastName: savedDonation.lastName, - email: savedDonation.email, - amount: savedDonation.amount, - isAnonymous: savedDonation.isAnonymous, - donationType: savedDonation.donationType as 'one_time' | 'recurring', - recurringInterval: savedDonation.recurringInterval as + id: finalDonation.id, + firstName: finalDonation.firstName, + lastName: finalDonation.lastName, + email: finalDonation.email, + amount: finalDonation.amount, + isAnonymous: finalDonation.isAnonymous, + donationType: finalDonation.donationType as 'one_time' | 'recurring', + recurringInterval: finalDonation.recurringInterval as | 'weekly' | 'monthly' | 'bimonthly' | 'quarterly' | 'annually' | undefined, - dedicationMessage: savedDonation.dedicationMessage || undefined, - showDedicationPublicly: savedDonation.showDedicationPublicly, - status: savedDonation.status as + dedicationMessage: finalDonation.dedicationMessage || undefined, + showDedicationPublicly: finalDonation.showDedicationPublicly, + status: finalDonation.status as | 'pending' | 'succeeded' | 'failed' | 'cancelled', - createdAt: savedDonation.createdAt, - updatedAt: savedDonation.updatedAt, - transactionId: savedDonation.transactionId || undefined, + createdAt: finalDonation.createdAt, + updatedAt: finalDonation.updatedAt, + transactionId: finalDonation.transactionId || undefined, }; } @@ -120,7 +131,9 @@ export class DonationsService { } async findPublic(limit = 50): Promise { + // Return only non-anonymous, succeeded donations for public display const donations: Donation[] = await this.donationRepository.find({ + where: { isAnonymous: false, status: DonationStatus.SUCCEEDED }, take: limit, order: { createdAt: 'DESC' }, }); @@ -185,6 +198,16 @@ export class DonationsService { `SELECT COUNT(amount) AS count, SUM(amount) AS total FROM donations`, ); - return { total: donations.total, count: donations.count }; + // SQL SUM returns null when no rows exist; coerce to numbers with sensible defaults + const total = + donations.total !== null && donations.total !== undefined + ? Number(donations.total) + : 0; + const count = + donations.count !== null && donations.count !== undefined + ? Number(donations.count) + : 0; + + return { total, count }; } } diff --git a/apps/backend/src/migrations/1759151447065-add_donations.ts b/apps/backend/src/migrations/1759151447065-add_donations.ts deleted file mode 100644 index acc9503..0000000 --- a/apps/backend/src/migrations/1759151447065-add_donations.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class AddDonations1759151447065 implements MigrationInterface { - name = 'AddDonations1759151447065'; - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `CREATE TABLE "donations" ( - "id" integer GENERATED ALWAYS AS IDENTITY NOT NULL, - "firstName" character varying NOT NULL, - "lastName" character varying NOT NULL, - "email" character varying NOT NULL, - "amount" numeric(10,2) NOT NULL, - "isAnonymous" boolean NOT NULL DEFAULT false, - "donationType" character varying NOT NULL, - "recurringInterval" character varying, - "dedicationMessage" character varying, - "showDedicationPublicly" boolean NOT NULL DEFAULT false, - "status" character varying NOT NULL DEFAULT 'pending', - "transactionId" character varying, - "createdAt" TIMESTAMP NOT NULL DEFAULT now(), - "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), - CONSTRAINT "PK_c01355d6f6f50fc6d1b4a946abf" PRIMARY KEY ("id") - )`, - ); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`DROP TABLE "donations"`); - } -} diff --git a/apps/backend/src/migrations/1763769154611-add_donations.ts b/apps/backend/src/migrations/1763769154611-add_donations.ts new file mode 100644 index 0000000..ed6c1bf --- /dev/null +++ b/apps/backend/src/migrations/1763769154611-add_donations.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddDonations1763769154611 implements MigrationInterface { + name = 'AddDonations1763769154611'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "donations" ("id" integer GENERATED ALWAYS AS IDENTITY NOT NULL, "firstName" character varying NOT NULL, "lastName" character varying NOT NULL, "email" character varying NOT NULL, "amount" integer NOT NULL, "isAnonymous" boolean NOT NULL DEFAULT false, "donationType" character varying NOT NULL, "recurringInterval" character varying, "dedicationMessage" character varying, "showDedicationPublicly" boolean NOT NULL DEFAULT false, "status" character varying NOT NULL DEFAULT 'pending', "transactionId" character varying, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_c01355d6f6f50fc6d1b4a946abf" PRIMARY KEY ("id"))`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "donations"`); + } +} diff --git a/apps/backend/test/donations.e2e-spec.ts b/apps/backend/test/donations.e2e-spec.ts new file mode 100644 index 0000000..83426eb --- /dev/null +++ b/apps/backend/test/donations.e2e-spec.ts @@ -0,0 +1,731 @@ +import { INestApplication } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import request from 'supertest'; +import { + DonationType, + RecurringInterval, + DonationStatus, +} from '../src/donations/donation.entity'; +import { DonationsModule } from '../src/donations/donations.module'; +import { TypeOrmModule, getRepositoryToken } from '@nestjs/typeorm'; +import { Repository, DataSource, QueryRunner } from 'typeorm'; +import { Donation } from '../src/donations/donation.entity'; // adjust path if needed +import * as dotenv from 'dotenv'; + +dotenv.config(); + +interface TestDonation { + id: number; + firstName: string; + lastName: string; + email: string; + amount: number; + isAnonymous: boolean; + donationType: DonationType; + recurringInterval: RecurringInterval | null; + dedicationMessage?: string | null; + showDedicationPublicly: boolean; + status: DonationStatus; + createdAt: Date; + updatedAt: Date; + transactionId?: string | null; +} + +describe('Donations (e2e) - expanded stubs', () => { + // Increase Jest timeout for slower CI/initialization (DB + Nest app init) + // Default is 5000ms which is often too small for integration tests. + jest.setTimeout(30000); + + let app: INestApplication; + let nextId = 1; + let donationRepository: Repository; + let dataSource: DataSource; + let queryRunner: QueryRunner; + + beforeAll(async () => { + nextId = 1; + + // Use actual Postgres DB for tests. IMPORTANT: Make sure these env vars + // point to a dedicated test database (never run tests against production DB). + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot({ + type: 'postgres', + host: process.env.NX_DB_HOST || 'localhost', + port: parseInt(process.env.NX_DB_PORT || '5432', 10), + username: process.env.NX_DB_USERNAME || 'postgres', + password: process.env.NX_DB_PASSWORD || 'postgres', + database: process.env.NX_DB_DATABASE || 'fcc_dev', + entities: [__dirname + '/../src/**/*.entity{.ts,.js}'], + // Prefer running migrations in tests for parity; set migrationsRun to true + // if you keep migrations up-to-date. If you want schema auto-sync for + // a test DB, set `synchronize: true` manually here. + synchronize: false, + dropSchema: true, + migrationsRun: true, + migrations: [__dirname + '/../src/migrations/*{.ts,.js}'], + logging: false, + }), + DonationsModule, + ], + }).compile(); + + app = moduleFixture.createNestApplication(); + // Match runtime API prefix used by the real application + app.setGlobalPrefix('api'); + await app.init(); + + donationRepository = moduleFixture.get>( + getRepositoryToken(Donation), + ); + dataSource = moduleFixture.get(DataSource); + }); + + beforeEach(async () => { + queryRunner = dataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + await donationRepository.clear(); + }); + + afterEach(async () => { + await queryRunner.rollbackTransaction(); + await queryRunner.release(); + }); + + afterAll(async () => { + if (app) { + await app.close(); + } + if (dataSource && dataSource.isInitialized) { + await dataSource.destroy(); + } + }); + + // Helper sample payloads used in the stubs below + const oneTimePayload = { + firstName: 'Jane', + lastName: 'Doe', + email: 'jane.doe@example.com', + amount: 50, + isAnonymous: false, + donationType: DonationType.ONE_TIME, + }; + + const recurringPayload = { + firstName: 'Alice', + lastName: 'Smith', + email: 'alice.smith@example.com', + amount: 25, + isAnonymous: false, + donationType: DonationType.RECURRING, + recurringInterval: RecurringInterval.MONTHLY, + }; + + // ---------- DTO shape validators ---------- + const isValidDateValue = (value: unknown): boolean => { + console.log('value:', value); + console.log(typeof value); + // Accept either a Date object or a string that can be parsed as a date. + if (value instanceof Date) { + return !Number.isNaN(value.getTime()); + } + if (typeof value === 'string') { + const dt = new Date(value); + return !Number.isNaN(dt.getTime()); + } + // Some DB drivers may return date-like objects; try to stringify/parse as fallback + if (value && typeof value === 'object') { + try { + const asString = (value as any).toISOString + ? (value as any).toISOString() + : String(value); + const dt = new Date(asString); + return !Number.isNaN(dt.getTime()); + } catch { + return false; + } + } + + return false; + }; + + const expectDonationResponseDtoShape = ( + obj: Record, + expected: { + donationType: DonationType; + recurringInterval?: RecurringInterval | null; + email?: string; + firstName?: string; + lastName?: string; + transactionIdPresent?: boolean; + }, + ) => { + expect(typeof obj.id).toBe('number'); + expect(typeof obj.firstName).toBe('string'); + expect(typeof obj.lastName).toBe('string'); + expect(typeof obj.email).toBe('string'); + expect(typeof obj.amount).toBe('number'); + expect(typeof obj.isAnonymous).toBe('boolean'); + expect(obj.donationType).toBe(expected.donationType); + if (expected.recurringInterval) { + expect(obj.recurringInterval).toBe(expected.recurringInterval); + } else { + // Could be null or undefined depending on mapper; both acceptable + expect(['undefined', 'string', 'object']).toContain( + typeof obj.recurringInterval as string, + ); + if (typeof obj.recurringInterval === 'string') { + expect([ + RecurringInterval.WEEKLY, + RecurringInterval.MONTHLY, + RecurringInterval.BIMONTHLY, + RecurringInterval.QUARTERLY, + RecurringInterval.ANNUALLY, + ]).toContain(obj.recurringInterval); + } else if (typeof obj.recurringInterval === 'object') { + // JSON null + expect(obj.recurringInterval).toBeNull(); + } + } + // dedicationMessage is optional + if (obj.dedicationMessage !== undefined && obj.dedicationMessage !== null) { + expect(typeof obj.dedicationMessage).toBe('string'); + } + expect(typeof obj.showDedicationPublicly).toBe('boolean'); + expect(['pending', 'succeeded', 'failed', 'cancelled']).toContain( + obj.status as string, + ); + expect(isValidDateValue(obj.createdAt)).toBe(true); + expect(isValidDateValue(obj.updatedAt)).toBe(true); + if (expected.transactionIdPresent) { + expect(typeof obj.transactionId).toBe('string'); + } else { + // Can be absent or null + expect(['undefined', 'string']).toContain( + typeof obj.transactionId as string, + ); + } + }; + + const expectPublicDonationDtoShape = ( + obj: Record, + opts: { anonymous: boolean; hasDedication: boolean }, + ) => { + expect(typeof obj.id).toBe('number'); + expect(typeof obj.amount).toBe('number'); + expect(typeof obj.isAnonymous).toBe('boolean'); + expect([DonationType.ONE_TIME, DonationType.RECURRING]).toContain( + obj.donationType as DonationType, + ); + if (obj.recurringInterval !== undefined && obj.recurringInterval !== null) { + expect([ + RecurringInterval.WEEKLY, + RecurringInterval.MONTHLY, + RecurringInterval.BIMONTHLY, + RecurringInterval.QUARTERLY, + RecurringInterval.ANNUALLY, + ]).toContain(obj.recurringInterval as RecurringInterval); + } + expect(['pending', 'succeeded', 'failed', 'cancelled']).toContain( + obj.status as string, + ); + expect(isValidDateValue(obj.createdAt)).toBe(true); + + if (opts.anonymous) { + expect(obj).not.toHaveProperty('donorName'); + } else { + expect(typeof obj.donorName).toBe('string'); + expect((obj.donorName as string).length).toBeGreaterThan(0); + } + + if (opts.hasDedication) { + expect(typeof obj.dedicationMessage).toBe('string'); + } else { + expect(obj).not.toHaveProperty('dedicationMessage'); + } + + // Ensure sensitive fields are not leaked in public DTO + expect(obj).not.toHaveProperty('email'); + expect(obj).not.toHaveProperty('firstName'); + expect(obj).not.toHaveProperty('lastName'); + expect(obj).not.toHaveProperty('transactionId'); + }; + + it('smoke: GET / (should 404 or 200 depending on routes)', async () => { + const res = await request(app.getHttpServer()).get('/'); + expect([200, 404]).toContain(res.status); + }); + + describe('POST /api/donations', () => { + it('Successfuly commits a one-time donation creation', async () => { + const payload = { ...oneTimePayload }; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(201); + + expectDonationResponseDtoShape(res.body, { + donationType: DonationType.ONE_TIME, + recurringInterval: null, + }); + + // Validate DB state (repository query) + const created = await donationRepository.findOne({ + where: { email: payload.email }, + }); + expect(created).toBeDefined(); + expect(created).toBeInstanceOf(Donation); + if (created instanceof Donation) { + expect((created as Donation).id).toBeDefined(); + expect((created as Donation).firstName).toBe(payload.firstName); + expect((created as Donation).lastName).toBe(payload.lastName); + expect((created as Donation).email).toBe(payload.email); + expect((created as Donation).amount).toBe(payload.amount); + expect((created as Donation).isAnonymous).toBe(payload.isAnonymous); + expect((created as Donation).donationType).toBe(payload.donationType); + expect((created as Donation).recurringInterval).toBeNull(); + expect((created as Donation).dedicationMessage).toBeNull(); + expect((created as Donation).showDedicationPublicly).toBe(false); + expect((created as Donation).status).toBe('pending'); + expect(isValidDateValue((created as Donation).createdAt)).toBe(true); + expect(isValidDateValue((created as Donation).updatedAt)).toBe(true); + expect((created as Donation).transactionId).toBeNull(); + } + }); + + it('Successfuly creates a recurring donation with interval', async () => { + const payload = { ...recurringPayload }; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(201); + expectDonationResponseDtoShape(res.body, { + donationType: DonationType.RECURRING, + recurringInterval: RecurringInterval.MONTHLY, + }); + + // Validate DB state (repository query) + const created = await donationRepository.findOne({ + where: { email: payload.email }, + }); + + expect(created).toBeDefined(); + expect(created).toBeInstanceOf(Donation); + if (created instanceof Donation) { + expect((created as Donation).id).toBeDefined(); + expect((created as Donation).firstName).toBe(payload.firstName); + expect((created as Donation).lastName).toBe(payload.lastName); + expect((created as Donation).email).toBe(payload.email); + expect((created as Donation).amount).toBe(payload.amount); + expect((created as Donation).isAnonymous).toBe(payload.isAnonymous); + expect((created as Donation).donationType).toBe(payload.donationType); + expect((created as Donation).recurringInterval).toBe( + payload.recurringInterval, + ); + expect((created as Donation).dedicationMessage).toBeNull(); + expect((created as Donation).showDedicationPublicly).toBe(false); + expect((created as Donation).status).toBe('pending'); + expect(isValidDateValue((created as Donation).createdAt)).toBe(true); + expect(isValidDateValue((created as Donation).updatedAt)).toBe(true); + expect((created as Donation).transactionId).toBeNull(); + } + }); + + it('rejects a negative amount (returns 400)', async () => { + const payload = { ...oneTimePayload, amount: -10 }; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + + const dbCount = await donationRepository.count(); + expect(dbCount).toBe(0); + }); + + it('rejects an invalid email format amount (returns 400)', async () => { + const payload = { ...oneTimePayload, email: 'not-an-email' }; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + + const dbCount = await donationRepository.count(); + expect(dbCount).toBe(0); + }); + + it('rejects a donation marked recurring if recurringInterval is missing', async () => { + const payload: Partial = { ...recurringPayload }; + delete payload.recurringInterval; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + + const dbCount = await donationRepository.count(); + expect(dbCount).toBe(0); + }); + + it('rejects a one-time donation that has a recurring interval (returns 400)', async () => { + const payload: Record = { + ...oneTimePayload, + recurringInterval: 'MONTHLY', + }; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + + const dbCount = await donationRepository.count(); + expect(dbCount).toBe(0); + }); + + it('gracefully rejects a payload that is missing the first name', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any = { ...oneTimePayload }; + delete payload.firstName; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + expect(String(res.body.message).toLowerCase()).toContain( + 'firstName'.toLowerCase(), + ); + + const dbCount = await donationRepository.count(); + expect(dbCount).toBe(0); + }); + + it('gracefully rejects a payload that is missing the last name', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any = { ...oneTimePayload }; + delete payload.lastName; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + expect(String(res.body.message).toLowerCase()).toContain( + 'lastName'.toLowerCase(), + ); + + const dbCount = await donationRepository.count(); + expect(dbCount).toBe(0); + }); + + it('gracefully rejects a payload that is missing the email', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any = { ...oneTimePayload }; + delete payload.email; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + expect(String(res.body.message).toLowerCase()).toContain( + 'email'.toLowerCase(), + ); + + const dbCount = await donationRepository.count(); + expect(dbCount).toBe(0); + }); + + it('gracefully rejects a payload that is missing the amount', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any = { ...oneTimePayload }; + delete payload.amount; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + expect(String(res.body.message).toLowerCase()).toContain( + 'amount'.toLowerCase(), + ); + + const dbCount = await donationRepository.count(); + expect(dbCount).toBe(0); + }); + + it('Successfuly commits a one-time donation creation even if isAnonymous is missing', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any = { ...oneTimePayload }; + delete payload.isAnonymous; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(201); + + expect(res.body).toHaveProperty('id'); + expect(res.body.amount).toBe(payload.amount); + + // Validate DB state (repository query) + const created = await donationRepository.findOne({ + where: { email: payload.email }, + }); + + expect(created).toBeDefined(); + expect(created).toBeInstanceOf(Donation); + if (created instanceof Donation) { + expect((created as Donation).id).toBeDefined(); + expect((created as Donation).firstName).toBe(payload.firstName); + expect((created as Donation).lastName).toBe(payload.lastName); + expect((created as Donation).email).toBe(payload.email); + expect((created as Donation).amount).toBe(payload.amount); + expect((created as Donation).isAnonymous).toBe(false); + expect((created as Donation).donationType).toBe(payload.donationType); + expect((created as Donation).recurringInterval).toBeNull(); + expect((created as Donation).dedicationMessage).toBeNull(); + expect((created as Donation).showDedicationPublicly).toBe(false); + expect((created as Donation).status).toBe('pending'); + expect(isValidDateValue((created as Donation).createdAt)).toBe(true); + expect(isValidDateValue((created as Donation).updatedAt)).toBe(true); + expect((created as Donation).transactionId).toBeNull(); + } + }); + + it('gracefully rejects a payload that is missing the donationType', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any = { ...oneTimePayload }; + delete payload.donationType; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + expect(String(res.body.message).toLowerCase()).toContain( + 'donationType'.toLowerCase(), + ); + + const dbCount = await donationRepository.count(); + expect(dbCount).toBe(0); + }); + + it('gracefully rejects a payload that is contains the wrong recurring interval (not the enum)', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any = { ...oneTimePayload, recurring: 'invalid' }; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + expect(String(res.body.message).toLowerCase()).toContain( + 'recurring'.toLowerCase(), + ); + + const dbCount = await donationRepository.count(); + expect(dbCount).toBe(0); + }); + + it('gracefully rejects a payload that is contains the wrong donation type (not the enum)', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any = { ...oneTimePayload, donationType: 'invalid' }; + + const res = await request(app.getHttpServer()) + .post('/api/donations') + .send(payload) + .expect(400); + + expect(res.body).toHaveProperty('statusCode', 400); + expect(res.body).toHaveProperty('message'); + expect(String(res.body.message).toLowerCase()).toContain( + 'donationType'.toLowerCase(), + ); + + const dbCount = await donationRepository.count(); + expect(dbCount).toBe(0); + }); + }); + + describe('GET /api/donations/public', () => { + it('returns only non-anonymous donations', async () => { + // Seed anonymous and non-anonymous donations + const now = new Date(); + await donationRepository.save([ + { + firstName: 'Sam', + lastName: 'Nie', + email: 'nie.sa@example.com', + amount: 10, + isAnonymous: true, + donationType: DonationType.ONE_TIME, + recurringInterval: null, + dedicationMessage: null, + showDedicationPublicly: false, + status: DonationStatus.SUCCEEDED, + transactionId: null, + createdAt: now, + updatedAt: now, + }, + { + firstName: 'Rex', + lastName: 'Jeff', + email: 'Re.Je@example.com', + amount: 15, + isAnonymous: false, + donationType: DonationType.ONE_TIME, + recurringInterval: null, + dedicationMessage: null, + showDedicationPublicly: false, + status: DonationStatus.SUCCEEDED, + transactionId: null, + createdAt: now, + updatedAt: now, + }, + ] as Partial[]); + + const res = await request(app.getHttpServer()) + .get('/api/donations/public') + .expect(200); + expect(Array.isArray(res.body)).toBe(true); + expect( + res.body.every( + (d: { isAnonymous: boolean }) => d.isAnonymous === false, + ), + ).toBe(true); + + // Validate public DTO shape for the first item + if (res.body.length > 0) { + expectPublicDonationDtoShape(res.body[0], { + anonymous: false, + hasDedication: false, + }); + } + }); + + it('returns no donations if there are none in the database', async () => { + const res = await request(app.getHttpServer()) + .get('/api/donations/public') + .expect(200); + + expect(Array.isArray(res.body)).toBe(true); + expect( + res.body.every( + (d: { isAnonymous: boolean }) => d.isAnonymous === false, + ), + ).toBe(true); + }); + + it('Returns items with correct DTO (expected keys)', async () => { + const now = new Date(); + + const res = await request(app.getHttpServer()) + .get('/api/donations/public') + .expect(200); + + expect(Array.isArray(res.body)).toBe(true); + if (res.body.length > 0) { + const item = res.body[0]; + const keys = Object.keys(item); + const required = [ + 'id', + 'amount', + 'isAnonymous', + 'donationType', + 'status', + 'createdAt', + ]; + const optional = [ + 'donorName', + 'recurringInterval', + 'dedicationMessage', + ]; + const allowed = [...required, ...optional]; + + required.forEach((k) => expect(keys).toContain(k)); + keys.forEach((k) => expect(allowed).toContain(k)); + } + }); + }); + + describe('GET /api/donations/stats', () => { + it('successfully returns the correct total and count', async () => { + // Seed two donations so the total is 25 and count is 2 + const now = new Date(); + await donationRepository.save([ + { + firstName: 'Sam', + lastName: 'Nie', + email: 'nie.sa@example.com', + amount: 10, + isAnonymous: false, + donationType: DonationType.ONE_TIME, + recurringInterval: null, + dedicationMessage: null, + showDedicationPublicly: false, + status: DonationStatus.SUCCEEDED, + transactionId: null, + createdAt: now, + updatedAt: now, + }, + { + firstName: 'Rex', + lastName: 'Jeff', + email: 'Re.Je@example.com', + amount: 15, + isAnonymous: false, + donationType: DonationType.ONE_TIME, + recurringInterval: null, + dedicationMessage: null, + showDedicationPublicly: false, + status: DonationStatus.SUCCEEDED, + transactionId: null, + createdAt: now, + updatedAt: now, + }, + ] as Partial[]); + + const res = await request(app.getHttpServer()) + .get('/api/donations/stats') + .expect(200); + + expect(res.body).toEqual({ total: 25, count: 2 }); + }); + + it('successfully returns the correct total and count even if the database is empty', async () => { + const res = await request(app.getHttpServer()) + .get('/api/donations/stats') + .expect(200); + + expect(res.body).toEqual({ total: 0, count: 0 }); + }); + }); +}); diff --git a/package.json b/package.json index 2589e88..0e15453 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@nestjs/swagger": "^7.1.12", "@nestjs/typeorm": "^10.0.0", "@types/pg": "^8.15.5", + "@types/supertest": "^6.0.3", "amazon-cognito-identity-js": "^6.3.5", "axios": "^1.5.0", "class-transformer": "^0.5.1", @@ -57,6 +58,8 @@ "react-router-dom": "^6.15.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.0", + "sqlite3": "^5.1.7", + "supertest": "^7.1.4", "stripe": "^19.1.0", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", diff --git a/yarn.lock b/yarn.lock index 7014637..f764208 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1913,6 +1913,11 @@ resolved "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz" integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== +"@gar/promisify@^1.0.1": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" + integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== + "@humanwhocodes/config-array@^0.13.0": version "0.13.0" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz" @@ -2959,6 +2964,11 @@ dependencies: uuid "9.0.0" +"@noble/hashes@^1.1.5": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" + integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" @@ -2980,6 +2990,22 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@npmcli/fs@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" + integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + +"@npmcli/move-file@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + "@nuxtjs/opencollective@0.3.2": version "0.3.2" resolved "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz" @@ -3295,6 +3321,13 @@ tslib "^2.3.0" yargs-parser "21.1.1" +"@paralleldrive/cuid2@^2.2.2": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz#3d62ea9e7be867d3fa94b9897fab5b0ae187d784" + integrity sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw== + dependencies: + "@noble/hashes" "^1.1.5" + "@parcel/watcher-android-arm64@2.5.1": version "2.5.1" resolved "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz" @@ -4193,6 +4226,11 @@ dependencies: "@babel/runtime" "^7.12.5" +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + "@tootallnate/once@2": version "2.0.0" resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz" @@ -4300,6 +4338,11 @@ dependencies: "@types/node" "*" +"@types/cookiejar@^2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78" + integrity sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q== + "@types/eslint-scope@^3.7.3", "@types/eslint-scope@^3.7.7": version "3.7.7" resolved "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz" @@ -4412,6 +4455,11 @@ dependencies: "@types/node" "*" +"@types/methods@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547" + integrity sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ== + "@types/mime@^1": version "1.3.4" resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz" @@ -4547,6 +4595,24 @@ resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== +"@types/superagent@^8.1.0": + version "8.1.9" + resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-8.1.9.tgz#28bfe4658e469838ed0bf66d898354bcab21f49f" + integrity sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ== + dependencies: + "@types/cookiejar" "^2.1.5" + "@types/methods" "^1.1.4" + "@types/node" "*" + form-data "^4.0.0" + +"@types/supertest@^6.0.3": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-6.0.3.tgz#d736f0e994b195b63e1c93e80271a2faf927388c" + integrity sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w== + dependencies: + "@types/methods" "^1.1.4" + "@types/superagent" "^8.1.0" + "@types/validator@^13.7.10": version "13.11.5" resolved "https://registry.npmjs.org/@types/validator/-/validator-13.11.5.tgz" @@ -5058,6 +5124,11 @@ abab@^2.0.6: resolved "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + accepts@^1.3.8, accepts@~1.3.4, accepts@~1.3.8: version "1.3.8" resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" @@ -5103,13 +5174,20 @@ adm-zip@^0.5.10: resolved "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz" integrity sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ== -agent-base@6: +agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: debug "4" +agentkeepalive@^4.1.3: + version "4.6.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" + integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== + dependencies: + humanize-ms "^1.2.1" + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz" @@ -5252,11 +5330,24 @@ append-field@^1.0.0: resolved "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz" integrity sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw== +"aproba@^1.0.3 || ^2.0.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.1.0.tgz#75500a190313d95c64e871e7e4284c6ac219f0b1" + integrity sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew== + arch@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz" integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== +are-we-there-yet@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" + integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + arg@^4.1.0: version "4.1.3" resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" @@ -5378,6 +5469,11 @@ arraybuffer.prototype.slice@^1.0.4: get-intrinsic "^1.2.6" is-array-buffer "^3.0.4" +asap@^2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + asn1@~0.2.3: version "0.2.6" resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz" @@ -5679,6 +5775,13 @@ binary-extensions@^2.0.0: resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + bl@^4.0.3, bl@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" @@ -5891,6 +5994,30 @@ cac@^6.7.14: resolved "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz" integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== +cacache@^15.2.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== + dependencies: + "@npmcli/fs" "^1.0.0" + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.0.2" + unique-filename "^1.1.1" + cachedir@^2.3.0: version "2.4.0" resolved "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz" @@ -6050,6 +6177,16 @@ chokidar@^4.0.0: dependencies: readdirp "^4.0.1" +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + chrome-trace-event@^1.0.2: version "1.0.3" resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz" @@ -6204,6 +6341,11 @@ color-name@~1.1.4: resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-support@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + colord@^2.9.1: version "2.9.3" resolved "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz" @@ -6295,6 +6437,11 @@ commondir@^1.0.1: resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== +component-emitter@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" + integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== + compressible@~2.0.18: version "2.0.18" resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz" @@ -6357,6 +6504,11 @@ consola@^2.15.0: resolved "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz" integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw== +console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + content-disposition@0.5.4, content-disposition@~0.5.4: version "0.5.4" resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" @@ -6389,6 +6541,11 @@ cookie@0.7.1: resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz" integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== +cookiejar@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" + integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== + cookies@~0.9.1: version "0.9.1" resolved "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz" @@ -6833,9 +6990,9 @@ debug@^3.1.0, debug@^3.2.6, debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.3.6, debug@^4.4.1: +debug@^4.3.3, debug@^4.3.6, debug@^4.3.7, debug@^4.4.1: version "4.4.3" - resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: ms "^2.1.3" @@ -6845,6 +7002,13 @@ decimal.js@^10.4.3: resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + dedent@^1.6.0: version "1.7.0" resolved "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz" @@ -6862,6 +7026,11 @@ deep-equal@~1.0.1: resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz" integrity sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw== +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" @@ -6950,6 +7119,11 @@ detect-libc@^1.0.3: resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz" integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== +detect-libc@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" + integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== + detect-newline@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" @@ -6968,6 +7142,14 @@ detect-port@^1.5.1: address "^1.0.1" debug "4" +dezalgo@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" + integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== + dependencies: + asap "^2.0.0" + wrappy "1" + diff-sequences@^29.6.3: version "29.6.3" resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz" @@ -7176,9 +7358,9 @@ encodeurl@~1.0.2: resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== -encoding@^0.1.13: +encoding@^0.1.12, encoding@^0.1.13: version "0.1.13" - resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== dependencies: iconv-lite "^0.6.2" @@ -7215,6 +7397,16 @@ entities@^4.2.0, entities@^4.4.0: resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + errno@^0.1.1: version "0.1.8" resolved "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz" @@ -7773,6 +7965,11 @@ exit-x@^0.2.2: resolved "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz" integrity sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ== +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + expand-tilde@^2.0.0, expand-tilde@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz" @@ -7939,7 +8136,7 @@ fast-levenshtein@^2.0.6: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fast-safe-stringify@2.1.1: +fast-safe-stringify@2.1.1, fast-safe-stringify@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== @@ -8016,6 +8213,11 @@ file-loader@^6.2.0: loader-utils "^2.0.0" schema-utils "^3.0.0" +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + filelist@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz" @@ -8202,6 +8404,15 @@ form-data@^4.0.0, form-data@^4.0.4, form-data@~4.0.4: hasown "^2.0.2" mime-types "^2.1.12" +formidable@^3.5.4: + version "3.5.4" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-3.5.4.tgz#ac9a593b951e829b3298f21aa9a2243932f32ed9" + integrity sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug== + dependencies: + "@paralleldrive/cuid2" "^2.2.2" + dezalgo "^1.0.4" + once "^1.4.0" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" @@ -8257,6 +8468,13 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs-monkey@^1.0.4: version "1.0.5" resolved "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz" @@ -8294,6 +8512,20 @@ functions-have-names@^1.2.3: resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +gauge@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" + integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.3" + console-control-strings "^1.1.0" + has-unicode "^2.0.1" + signal-exit "^3.0.7" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.5" + generator-function@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz" @@ -8390,6 +8622,11 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" @@ -8646,6 +8883,11 @@ has-tostringtag@^1.0.2: dependencies: has-symbols "^1.0.3" +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + hasown@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" @@ -8700,6 +8942,11 @@ http-assert@^1.5.0: deep-equal "~1.0.1" http-errors "~1.8.0" +http-cache-semantics@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz#205f4db64f8562b76a4ff9235aa5279839a09dd5" + integrity sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ== + http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz" @@ -8742,6 +8989,15 @@ http-parser-js@>=0.5.1: resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz" integrity sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA== +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + http-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz" @@ -8811,9 +9067,9 @@ http-signature@~1.4.0: jsprim "^2.0.2" sshpk "^1.18.0" -https-proxy-agent@^5.0.1: +https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: version "5.0.1" - resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== dependencies: agent-base "6" @@ -8839,6 +9095,13 @@ human-signals@^5.0.0: resolved "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz" integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + husky@^8.0.3: version "8.0.3" resolved "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz" @@ -8950,6 +9213,11 @@ indent-string@^4.0.0: resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== +infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" @@ -8973,9 +9241,9 @@ ini@2.0.0: resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== -ini@^1.3.4: +ini@^1.3.4, ini@~1.3.0: version "1.3.8" - resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== inquirer@8.2.4: @@ -9034,6 +9302,11 @@ interpret@^1.0.0: resolved "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +ip-address@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.0.1.tgz#a8180b783ce7788777d796286d61bce4276818ed" + integrity sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA== + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" @@ -9195,6 +9468,11 @@ is-interactive@^1.0.0: resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" + integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== + is-map@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz" @@ -10801,6 +11079,13 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + "lru-cache@^9.1.1 || ^10.0.0": version "10.0.1" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz" @@ -10873,6 +11158,28 @@ make-error@^1.1.1, make-error@^1.3.6: resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +make-fetch-happen@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" + integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== + dependencies: + agentkeepalive "^4.1.3" + cacache "^15.2.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^6.0.0" + minipass "^3.1.3" + minipass-collect "^1.0.2" + minipass-fetch "^1.3.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.2" + promise-retry "^2.0.1" + socks-proxy-agent "^6.0.0" + ssri "^8.0.0" + makeerror@1.0.12: version "1.0.12" resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" @@ -10949,7 +11256,7 @@ merge2@^1.2.3, merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -methods@~1.1.2: +methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== @@ -10999,6 +11306,11 @@ mime@1.6.0, mime@^1.4.1, mime@^1.6.0: resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" @@ -11009,6 +11321,11 @@ mimic-fn@^4.0.0: resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + min-document@^2.19.0: version "2.19.0" resolved "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz" @@ -11068,21 +11385,85 @@ minimatch@^9.0.4: dependencies: brace-expansion "^2.0.1" -minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8: +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-fetch@^1.3.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" + integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== + dependencies: + minipass "^3.1.0" + minipass-sized "^1.0.3" + minizlib "^2.0.0" + optionalDependencies: + encoding "^0.1.12" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + minipass@^4.2.4: version "4.2.8" resolved "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz" integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + "minipass@^5.0.0 || ^6.0.2 || ^7.0.0": version "7.0.4" resolved "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz" integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== +minizlib@^2.0.0, minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp@^0.5.4: version "0.5.6" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz" @@ -11090,6 +11471,11 @@ mkdirp@^0.5.4: dependencies: minimist "^1.2.6" +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + mkdirp@^2.1.3: version "2.1.6" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz" @@ -11120,7 +11506,7 @@ ms@2.1.2, ms@^2.1.1: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.3: +ms@2.1.3, ms@^2.0.0, ms@^2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -11165,6 +11551,11 @@ nanoid@^3.3.11: resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz" integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== +napi-build-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e" + integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA== + napi-postinstall@^0.3.0: version "0.3.4" resolved "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz" @@ -11189,9 +11580,9 @@ negotiator@0.6.3: resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== -negotiator@~0.6.4: +negotiator@^0.6.2, negotiator@~0.6.4: version "0.6.4" - resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== neo-async@^2.6.2: @@ -11207,6 +11598,13 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" +node-abi@^3.3.0: + version "3.80.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.80.0.tgz#d7390951f27caa129cceeec01e1c20fc9f07581c" + integrity sha512-LyPuZJcI9HVwzXK1GPxWNzrr+vr8Hp/3UqlmWxxh8p54U1ZbclOqbSog9lWHaCX+dBaiGi6n/hIX+mKu74GmPA== + dependencies: + semver "^7.3.5" + node-abort-controller@^3.0.1: version "3.1.1" resolved "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz" @@ -11236,6 +11634,22 @@ node-forge@^1: resolved "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz" integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== +node-gyp@8.x: + version "8.4.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" + integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^9.1.0" + nopt "^5.0.0" + npmlog "^6.0.0" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" @@ -11276,6 +11690,13 @@ nodemon@^3.0.1: touch "^3.1.0" undefsafe "^2.0.5" +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" @@ -11305,6 +11726,16 @@ npm-run-path@^5.1.0: dependencies: path-key "^4.0.0" +npmlog@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" + integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== + dependencies: + are-we-there-yet "^3.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.3" + set-blocking "^2.0.0" + nth-check@^2.0.1: version "2.1.1" resolved "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz" @@ -12480,6 +12911,24 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" +prebuild-install@^7.1.1: + version "7.1.3" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec" + integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^2.0.0" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" @@ -12532,6 +12981,19 @@ process@^0.11.10: resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== + +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + promise.series@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz" @@ -12616,9 +13078,9 @@ qs@6.13.0: dependencies: side-channel "^1.0.6" -qs@6.14.0: +qs@6.14.0, qs@^6.11.2: version "6.14.0" - resolved "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== dependencies: side-channel "^1.1.0" @@ -12670,6 +13132,16 @@ raw-body@2.5.2: iconv-lite "0.4.24" unpipe "1.0.0" +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + react-dom@^18.2.0: version "18.2.0" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz" @@ -12735,7 +13207,7 @@ readable-stream@^2.0.1, readable-stream@^2.2.2: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0: +readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -12937,6 +13409,11 @@ restore-cursor@^4.0.0: onetime "^5.1.0" signal-exit "^3.0.2" +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + retry@^0.13.1: version "0.13.1" resolved "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz" @@ -13426,6 +13903,11 @@ serve-static@1.16.2: parseurl "~1.3.3" send "0.19.0" +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + set-function-length@^1.2.2: version "1.2.2" resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz" @@ -13556,6 +14038,20 @@ signal-exit@^4.0.1, signal-exit@^4.1.0: resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + simple-update-notifier@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz" @@ -13608,6 +14104,11 @@ slice-ansi@^5.0.0: ansi-styles "^6.0.0" is-fullwidth-code-point "^4.0.0" +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + snake-case@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz" @@ -13625,6 +14126,23 @@ sockjs@^0.3.24: uuid "^8.3.2" websocket-driver "^0.7.4" +socks-proxy-agent@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" + integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ== + dependencies: + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" + +socks@^2.6.2: + version "2.8.7" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.7.tgz#e2fb1d9a603add75050a2067db8c381a0b5669ea" + integrity sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A== + dependencies: + ip-address "^10.0.1" + smart-buffer "^4.2.0" + sorted-array-functions@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz" @@ -13710,6 +14228,18 @@ sprintf-js@~1.0.2: resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +sqlite3@^5.1.7: + version "5.1.7" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.7.tgz#59ca1053c1ab38647396586edad019b1551041b7" + integrity sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog== + dependencies: + bindings "^1.5.0" + node-addon-api "^7.0.0" + prebuild-install "^7.1.1" + tar "^6.1.11" + optionalDependencies: + node-gyp "8.x" + sshpk@^1.18.0: version "1.18.0" resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz" @@ -13725,6 +14255,13 @@ sshpk@^1.18.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" +ssri@^8.0.0, ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + stable@^0.1.8: version "0.1.8" resolved "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz" @@ -13797,7 +14334,16 @@ string-length@^4.0.2: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"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.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -13878,7 +14424,14 @@ string_decoder@^1.1.1, string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -13917,6 +14470,11 @@ strip-json-comments@^3.1.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + strip-literal@^2.0.0: version "2.1.1" resolved "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz" @@ -13962,6 +14520,29 @@ stylehacks@^6.0.0: browserslist "^4.21.4" postcss-selector-parser "^6.0.4" +superagent@^10.2.3: + version "10.2.3" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-10.2.3.tgz#d1e4986f2caac423c37e38077f9073ccfe73a59b" + integrity sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig== + dependencies: + component-emitter "^1.3.1" + cookiejar "^2.1.4" + debug "^4.3.7" + fast-safe-stringify "^2.1.1" + form-data "^4.0.4" + formidable "^3.5.4" + methods "^1.1.2" + mime "2.6.0" + qs "^6.11.2" + +supertest@^7.1.4: + version "7.1.4" + resolved "https://registry.yarnpkg.com/supertest/-/supertest-7.1.4.tgz#3175e2539f517ca72fdc7992ffff35b94aca7d34" + integrity sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg== + dependencies: + methods "^1.1.2" + superagent "^10.2.3" + supports-color@^5.5.0: version "5.5.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" @@ -14057,9 +14638,19 @@ tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1, tapable@^2.3.0: resolved "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz" integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== -tar-stream@~2.2.0: +tar-fs@^2.0.0: + version "2.1.4" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.4.tgz#800824dbf4ef06ded9afea4acafe71c67c76b930" + integrity sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4, tar-stream@~2.2.0: version "2.2.0" - resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== dependencies: bl "^4.0.3" @@ -14068,6 +14659,18 @@ tar-stream@~2.2.0: inherits "^2.0.3" readable-stream "^3.1.1" +tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + terser-webpack-plugin@^5.3.11, terser-webpack-plugin@^5.3.3, terser-webpack-plugin@^5.3.7: version "5.3.14" resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz" @@ -14587,6 +15190,20 @@ union@~0.5.0: dependencies: qs "^6.4.0" +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + universalify@^0.1.0: version "0.1.2" resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz" @@ -15064,7 +15681,7 @@ which@^1.2.14: dependencies: isexe "^2.0.0" -which@^2.0.1: +which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== @@ -15079,6 +15696,13 @@ why-is-node-running@^2.2.2: siginfo "^2.0.0" stackback "0.0.2" +wide-align@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + windows-release@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/windows-release/-/windows-release-4.0.0.tgz" @@ -15091,7 +15715,7 @@ wordwrap@^1.0.0: resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -15109,6 +15733,15 @@ 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.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" @@ -15173,6 +15806,11 @@ yallist@^3.0.2: resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yaml@2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz"