From bbf1db8461cc414e01a15fd4b40ab5b096bc7c6e Mon Sep 17 00:00:00 2001 From: Teeeyanaa Date: Tue, 30 Jun 2026 01:20:47 +0000 Subject: [PATCH] feat(notifications): add scheduler service for time-based due-date alerts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #379 Adds SchedulerService that runs every 30 minutes (node-cron) and sends invoice.expiring_soon email reminders at the 72-hour and 24-hour thresholds before an invoice due date. Key implementation details: - Queries the invoice DB for status='Funded' rows whose due_date falls within the respective threshold window - Delivers reminders to all active email subscribers matching the invoice's freelancer, payer, and funder addresses - Idempotency enforced via a delivered_reminders table (PK: invoice_id + threshold_h) in the notifications DB, preventing duplicate sends across restarts and repeated cron firings - Exposes initDeliveredRemindersSchema() for schema migration - Accepts an injectable clock (now option) for deterministic tests - Configurable cron expression (default: */30 * * * *) Also fixes a pre-existing broken tsconfig.json extends path (../../tsconfig.base.json → ../tsconfig.base.json). Tests (22 cases): - Schema creation and idempotency - 72 h and 24 h reminder delivery - Correct filtering of non-Funded, out-of-window, inactive, unsubscribed, and wrong-eventType subscribers - Wildcard (*) eventType matching - Notifications sent to all three roles (freelancer, payer, funder) - Idempotency: second run is a no-op, persists across separate SchedulerService instances, records separate rows per threshold, writes idempotency record even when email delivery fails - Lifecycle: start/stop, custom cron expression - Logging assertions --- notifications/package.json | 2 + .../src/services/schedulerService.ts | 272 +++++++ notifications/tests/schedulerService.test.ts | 718 ++++++++++++++++++ notifications/tsconfig.json | 2 +- pnpm-lock.yaml | 706 +++++++++++++++++ 5 files changed, 1699 insertions(+), 1 deletion(-) create mode 100644 notifications/src/services/schedulerService.ts create mode 100644 notifications/tests/schedulerService.test.ts diff --git a/notifications/package.json b/notifications/package.json index ad3c39aa..97c988c6 100644 --- a/notifications/package.json +++ b/notifications/package.json @@ -18,11 +18,13 @@ "dependencies": { "better-sqlite3": "^12.11.1", "express": "^4.18.2", + "node-cron": "^3.0.3", "resend": "^3.2.0" }, "devDependencies": { "@types/better-sqlite3": "^7.6.8", "@types/express": "^4.17.21", + "@types/node-cron": "^3.0.11", "@types/supertest": "^6.0.2", "@types/node": "^20.0.0", "@vitest/coverage-v8": "^1.6.0", diff --git a/notifications/src/services/schedulerService.ts b/notifications/src/services/schedulerService.ts new file mode 100644 index 00000000..9ee1e80d --- /dev/null +++ b/notifications/src/services/schedulerService.ts @@ -0,0 +1,272 @@ +import cron from 'node-cron'; +import Database from 'better-sqlite3'; +import type { EmailSubscriptionStore } from '../subscriptions/emailSubscriptionStore.js'; +import type { EmailDeliveryService } from '../delivery/emailDelivery.js'; +import { buildInvoiceExpiringSoonEmail } from '../templates/invoiceExpiringSoon.js'; + +// ─── Types ──────────────────────────────────────────────────────────────────── + +export type ReminderThreshold = 72 | 24; + +export interface InvoiceRow { + id: number; + freelancer: string; + payer: string; + token: string; + amount: string; + due_date: number; + status: string; + funder: string | null; +} + +export interface SchedulerOptions { + /** + * SQLite database containing the `invoices` table (typically the indexer DB). + * The scheduler queries this for Funded invoices approaching their due date. + */ + invoiceDb: Database.Database; + + /** + * SQLite database used by the notifications service for idempotency tracking. + * The `delivered_reminders` table is created here on first use. + */ + notificationsDb: Database.Database; + + /** Store for e-mail subscriptions keyed by Stellar address. */ + emailStore: EmailSubscriptionStore; + + /** Service used to send e-mail notifications. */ + emailDelivery: EmailDeliveryService; + + /** Base URL for unsubscribe links embedded in e-mails. */ + publicUrl: string; + + /** Override the cron expression (default: every 30 minutes). */ + cronExpression?: string; + + /** Inject a custom clock for deterministic testing (returns Unix ms). */ + now?: () => number; + + /** Optional logger callback. */ + logger?: (msg: string) => void; +} + +// ─── Schema ─────────────────────────────────────────────────────────────────── + +/** + * Idempotency table stored in the notifications DB. + * + * Each row represents one delivered reminder so the scheduler never sends the + * same (invoice_id, threshold_hours) pair twice. + */ +export function initDeliveredRemindersSchema(db: Database.Database): void { + db.exec(` + CREATE TABLE IF NOT EXISTS delivered_reminders ( + invoice_id INTEGER NOT NULL, + threshold_h INTEGER NOT NULL, + delivered_at INTEGER NOT NULL, + PRIMARY KEY (invoice_id, threshold_h) + ); + + CREATE INDEX IF NOT EXISTS idx_delivered_reminders_delivered_at + ON delivered_reminders(delivered_at); + `); +} + +// ─── Service ────────────────────────────────────────────────────────────────── + +/** + * SchedulerService + * + * Runs a recurring cron job (default: every 30 minutes) that: + * 1. Queries the invoice DB for Funded invoices whose due date is within 72 h + * or 24 h of the current time. + * 2. For each matching invoice × threshold pair that has NOT already been + * delivered, sends an `invoice.expiring_soon` e-mail to every active + * subscriber whose Stellar address matches the invoice's freelancer, payer, + * or funder. + * 3. Records the delivery in `delivered_reminders` so subsequent runs are + * no-ops (idempotency). + */ +export class SchedulerService { + private readonly cronExpression: string; + private readonly now: () => number; + private readonly log: (msg: string) => void; + private task: ReturnType | null = null; + + constructor(private readonly opts: SchedulerOptions) { + this.cronExpression = opts.cronExpression ?? '*/30 * * * *'; + this.now = opts.now ?? (() => Date.now()); + this.log = opts.logger ?? (() => {}); + + initDeliveredRemindersSchema(opts.notificationsDb); + } + + // ── Public API ────────────────────────────────────────────────────────────── + + /** Start the cron scheduler. Idempotent – calling start() twice is harmless. */ + start(): void { + if (this.task) return; + this.task = cron.schedule(this.cronExpression, () => { + this.runChecks().catch((err) => { + this.log(`scheduler_error: ${err instanceof Error ? err.message : String(err)}`); + }); + }); + this.log(`scheduler_started cron="${this.cronExpression}"`); + } + + /** Stop the cron scheduler. */ + stop(): void { + if (!this.task) return; + this.task.stop(); + this.task = null; + this.log('scheduler_stopped'); + } + + /** + * Execute one check cycle immediately. + * Useful for testing and for an initial check on service start-up. + */ + async runChecks(): Promise { + const nowSec = Math.floor(this.now() / 1000); + this.log(`scheduler_run nowSec=${nowSec}`); + + const thresholds: ReminderThreshold[] = [72, 24]; + + for (const hours of thresholds) { + const windowStart = nowSec; + const windowEnd = nowSec + hours * 60 * 60; + + const invoices = this.queryFundedInvoicesDueBetween(windowStart, windowEnd); + this.log(`scheduler_found threshold=${hours}h count=${invoices.length}`); + + for (const invoice of invoices) { + await this.processInvoice(invoice, hours); + } + } + } + + // ── Private helpers ───────────────────────────────────────────────────────── + + /** + * Return all Funded invoices whose due_date falls in (windowStart, windowEnd]. + */ + private queryFundedInvoicesDueBetween( + windowStart: number, + windowEnd: number, + ): InvoiceRow[] { + return this.opts.invoiceDb + .prepare( + `SELECT id, freelancer, payer, token, amount, due_date, status, funder + FROM invoices + WHERE status = 'Funded' + AND due_date > ? + AND due_date <= ?`, + ) + .all(windowStart, windowEnd) as InvoiceRow[]; + } + + /** + * Check idempotency, then send e-mails for one invoice × threshold pair. + */ + private async processInvoice( + invoice: InvoiceRow, + threshold: ReminderThreshold, + ): Promise { + if (this.wasAlreadyDelivered(invoice.id, threshold)) { + this.log( + `scheduler_skip invoice_id=${invoice.id} threshold=${threshold}h already_delivered`, + ); + return; + } + + // Collect the unique Stellar addresses linked to this invoice. + const addresses = this.invoiceAddresses(invoice); + + // For each address find active e-mail subscribers interested in + // `invoice.expiring_soon`. + let sentCount = 0; + for (const address of addresses) { + const subs = this.opts.emailStore + .list() + .filter( + (s) => + s.address === address && + s.status === 'active' && + (s.eventTypes.includes('invoice.expiring_soon') || + s.eventTypes.includes('*')), + ); + + for (const sub of subs) { + const unsubscribeUrl = `${this.opts.publicUrl}/email/unsubscribe/${sub.id}`; + const email = buildInvoiceExpiringSoonEmail({ + invoiceId: invoice.id, + token: invoice.token, + amount: invoice.amount, + dueDate: invoice.due_date, + recipientAddress: address, + freelancer: invoice.freelancer, + payer: invoice.payer, + funder: invoice.funder ?? undefined, + reminderHours: threshold, + unsubscribeUrl, + }); + + const result = await this.opts.emailDelivery.send({ + to: sub.email, + subject: email.subject, + html: email.html, + text: email.text, + }); + + if (result.ok) { + sentCount += 1; + this.log( + `scheduler_email_sent invoice_id=${invoice.id} threshold=${threshold}h to=${sub.email}`, + ); + } else { + this.log( + `scheduler_email_failed invoice_id=${invoice.id} threshold=${threshold}h to=${sub.email} error=${result.error}`, + ); + } + } + } + + // Mark as delivered regardless of individual send results to avoid + // re-flooding subscribers if partial delivery occurred. + this.markDelivered(invoice.id, threshold); + this.log( + `scheduler_reminder_recorded invoice_id=${invoice.id} threshold=${threshold}h sent=${sentCount}`, + ); + } + + /** Returns true when a reminder for this (invoice_id, threshold) was already sent. */ + private wasAlreadyDelivered(invoiceId: number, threshold: ReminderThreshold): boolean { + const row = this.opts.notificationsDb + .prepare( + `SELECT 1 FROM delivered_reminders + WHERE invoice_id = ? AND threshold_h = ?`, + ) + .get(invoiceId, threshold); + return row !== undefined; + } + + /** Persist the idempotency record. */ + private markDelivered(invoiceId: number, threshold: ReminderThreshold): void { + this.opts.notificationsDb + .prepare( + `INSERT OR IGNORE INTO delivered_reminders (invoice_id, threshold_h, delivered_at) + VALUES (?, ?, ?)`, + ) + .run(invoiceId, threshold, Math.floor(this.now() / 1000)); + } + + /** Extract the set of unique Stellar addresses relevant to an invoice. */ + private invoiceAddresses(invoice: InvoiceRow): string[] { + const addresses = new Set(); + if (invoice.freelancer) addresses.add(invoice.freelancer); + if (invoice.payer) addresses.add(invoice.payer); + if (invoice.funder) addresses.add(invoice.funder); + return Array.from(addresses); + } +} diff --git a/notifications/tests/schedulerService.test.ts b/notifications/tests/schedulerService.test.ts new file mode 100644 index 00000000..e956f535 --- /dev/null +++ b/notifications/tests/schedulerService.test.ts @@ -0,0 +1,718 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import Database from 'better-sqlite3'; +import { + SchedulerService, + initDeliveredRemindersSchema, + type InvoiceRow, +} from '../src/services/schedulerService'; +import { EmailSubscriptionStore } from '../src/subscriptions/emailSubscriptionStore'; +import { EmailDeliveryService } from '../src/delivery/emailDelivery'; + +// ─── Helpers ────────────────────────────────────────────────────────────────── + +/** Initialise a minimal in-memory invoice DB (mirrors the indexer schema). */ +function makeInvoiceDb(): Database.Database { + const db = new Database(':memory:'); + db.pragma('foreign_keys = ON'); + db.exec(` + CREATE TABLE invoices ( + id INTEGER PRIMARY KEY, + freelancer TEXT NOT NULL, + payer TEXT NOT NULL, + token TEXT NOT NULL, + amount TEXT NOT NULL, + due_date INTEGER NOT NULL, + discount_rate INTEGER NOT NULL DEFAULT 0, + status TEXT NOT NULL DEFAULT 'Pending', + funder TEXT, + funded_at INTEGER, + amount_funded TEXT NOT NULL DEFAULT '0', + amount_paid TEXT NOT NULL DEFAULT '0', + referral_code TEXT, + submitter_reputation INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL + ); + `); + return db; +} + +/** Insert a minimal invoice row; returns the inserted ID. */ +function insertInvoice( + db: Database.Database, + overrides: Partial = {}, +): number { + const defaults = { + id: 1, + freelancer: 'GFREELANCER1', + payer: 'GPAYER1', + token: 'USDC', + amount: '1000000000', + due_date: 9999999999, + discount_rate: 0, + status: 'Funded', + funder: 'GFUNDER1', + created_at: 1000000, + }; + const row = { ...defaults, ...overrides }; + db.prepare( + `INSERT INTO invoices + (id, freelancer, payer, token, amount, due_date, discount_rate, + status, funder, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + ).run( + row.id, + row.freelancer, + row.payer, + row.token, + row.amount, + row.due_date, + row.discount_rate, + row.status, + row.funder ?? null, + row.created_at, + ); + return row.id; +} + +/** Build a fake EmailDeliveryService whose send() is a spy. */ +function makeEmailDelivery(sendResult = { ok: true, id: 'msg_1' }) { + const client = { send: vi.fn(async () => ({ id: 'msg_1' })) }; + const svc = new EmailDeliveryService(client as any, 'noreply@iln.dev'); + vi.spyOn(svc, 'send').mockResolvedValue(sendResult); + return svc; +} + +/** Convenience: create a SchedulerService wired to in-memory DBs. */ +function makeScheduler(opts: { + invoiceDb: Database.Database; + notificationsDb: Database.Database; + emailStore: EmailSubscriptionStore; + emailDelivery: EmailDeliveryService; + now?: () => number; + logger?: (msg: string) => void; +}): SchedulerService { + return new SchedulerService({ + ...opts, + publicUrl: 'http://localhost:3001', + }); +} + +// ─── Tests ─────────────────────────────────────────────────────────────────── + +describe('initDeliveredRemindersSchema', () => { + it('creates the delivered_reminders table when it does not exist', () => { + const db = new Database(':memory:'); + initDeliveredRemindersSchema(db); + + const tables = db + .prepare(`SELECT name FROM sqlite_master WHERE type='table'`) + .all() as { name: string }[]; + expect(tables.map((t) => t.name)).toContain('delivered_reminders'); + }); + + it('is idempotent – calling it twice does not throw', () => { + const db = new Database(':memory:'); + expect(() => { + initDeliveredRemindersSchema(db); + initDeliveredRemindersSchema(db); + }).not.toThrow(); + }); +}); + +describe('SchedulerService – schema init', () => { + it('creates delivered_reminders table on construction', () => { + const invoiceDb = makeInvoiceDb(); + const notificationsDb = new Database(':memory:'); + const emailStore = new EmailSubscriptionStore(notificationsDb); + const emailDelivery = makeEmailDelivery(); + + makeScheduler({ invoiceDb, notificationsDb, emailStore, emailDelivery }); + + const tables = notificationsDb + .prepare(`SELECT name FROM sqlite_master WHERE type='table'`) + .all() as { name: string }[]; + expect(tables.map((t) => t.name)).toContain('delivered_reminders'); + }); +}); + +describe('SchedulerService – runChecks() delivery', () => { + let invoiceDb: Database.Database; + let notificationsDb: Database.Database; + let emailStore: EmailSubscriptionStore; + let emailDelivery: ReturnType; + let logs: string[]; + + const BASE_NOW_SEC = 1_000_000; // arbitrary epoch in seconds + const BASE_NOW_MS = BASE_NOW_SEC * 1000; + + beforeEach(() => { + invoiceDb = makeInvoiceDb(); + notificationsDb = new Database(':memory:'); + emailStore = new EmailSubscriptionStore(notificationsDb); + emailDelivery = makeEmailDelivery(); + logs = []; + }); + + afterEach(() => { + invoiceDb.close(); + notificationsDb.close(); + }); + + it('sends a 72-hour reminder for a funded invoice due within 72 h', async () => { + // Invoice due exactly at the edge of the 72-hour window. + const dueDate = BASE_NOW_SEC + 72 * 3600; + insertInvoice(invoiceDb, { id: 1, status: 'Funded', due_date: dueDate }); + + // Create and activate a subscription for the freelancer address. + const sub = emailStore.create({ + address: 'GFREELANCER1', + email: 'freelancer@example.com', + eventTypes: ['invoice.expiring_soon'], + }); + emailStore.activate(sub.id); + + const scheduler = makeScheduler({ + invoiceDb, + notificationsDb, + emailStore, + emailDelivery, + now: () => BASE_NOW_MS, + logger: (m) => logs.push(m), + }); + + await scheduler.runChecks(); + + expect(emailDelivery.send).toHaveBeenCalledTimes(1); + const call = (emailDelivery.send as ReturnType).mock.calls[0]![0]; + expect(call.to).toBe('freelancer@example.com'); + expect(call.subject).toContain('72'); + }); + + it('sends a 24-hour reminder for a funded invoice due within 24 h', async () => { + const dueDate = BASE_NOW_SEC + 24 * 3600; + insertInvoice(invoiceDb, { id: 2, status: 'Funded', due_date: dueDate }); + + const sub = emailStore.create({ + address: 'GFREELANCER1', + email: 'fl@example.com', + eventTypes: ['invoice.expiring_soon'], + }); + emailStore.activate(sub.id); + + const scheduler = makeScheduler({ + invoiceDb, + notificationsDb, + emailStore, + emailDelivery, + now: () => BASE_NOW_MS, + }); + + await scheduler.runChecks(); + + // Invoice is within BOTH 72 h and 24 h windows. + // Should receive exactly 2 emails (one per threshold). + expect(emailDelivery.send).toHaveBeenCalledTimes(2); + }); + + it('does not send reminders for non-Funded invoices', async () => { + const dueDate = BASE_NOW_SEC + 24 * 3600; + insertInvoice(invoiceDb, { id: 3, status: 'Pending', due_date: dueDate }); + + const sub = emailStore.create({ + address: 'GFREELANCER1', + email: 'fl@example.com', + eventTypes: ['invoice.expiring_soon'], + }); + emailStore.activate(sub.id); + + const scheduler = makeScheduler({ + invoiceDb, + notificationsDb, + emailStore, + emailDelivery, + now: () => BASE_NOW_MS, + }); + + await scheduler.runChecks(); + + expect(emailDelivery.send).not.toHaveBeenCalled(); + }); + + it('does not send reminders for invoices outside both windows', async () => { + // Due in 100 hours – beyond the 72-hour window. + const dueDate = BASE_NOW_SEC + 100 * 3600; + insertInvoice(invoiceDb, { id: 4, status: 'Funded', due_date: dueDate }); + + const sub = emailStore.create({ + address: 'GFREELANCER1', + email: 'fl@example.com', + eventTypes: ['invoice.expiring_soon'], + }); + emailStore.activate(sub.id); + + const scheduler = makeScheduler({ + invoiceDb, + notificationsDb, + emailStore, + emailDelivery, + now: () => BASE_NOW_MS, + }); + + await scheduler.runChecks(); + + expect(emailDelivery.send).not.toHaveBeenCalled(); + }); + + it('does not send to inactive (pending) subscribers', async () => { + const dueDate = BASE_NOW_SEC + 24 * 3600; + insertInvoice(invoiceDb, { id: 5, status: 'Funded', due_date: dueDate }); + + // Subscription left in 'pending' state (never activated). + emailStore.create({ + address: 'GFREELANCER1', + email: 'pending@example.com', + eventTypes: ['invoice.expiring_soon'], + }); + + const scheduler = makeScheduler({ + invoiceDb, + notificationsDb, + emailStore, + emailDelivery, + now: () => BASE_NOW_MS, + }); + + await scheduler.runChecks(); + + expect(emailDelivery.send).not.toHaveBeenCalled(); + }); + + it('does not send to unsubscribed subscribers', async () => { + const dueDate = BASE_NOW_SEC + 24 * 3600; + insertInvoice(invoiceDb, { id: 6, status: 'Funded', due_date: dueDate }); + + const sub = emailStore.create({ + address: 'GFREELANCER1', + email: 'unsub@example.com', + eventTypes: ['invoice.expiring_soon'], + }); + emailStore.activate(sub.id); + emailStore.unsubscribe(sub.id); + + const scheduler = makeScheduler({ + invoiceDb, + notificationsDb, + emailStore, + emailDelivery, + now: () => BASE_NOW_MS, + }); + + await scheduler.runChecks(); + + expect(emailDelivery.send).not.toHaveBeenCalled(); + }); + + it('does not send to subscribers with non-matching eventTypes', async () => { + const dueDate = BASE_NOW_SEC + 24 * 3600; + insertInvoice(invoiceDb, { id: 7, status: 'Funded', due_date: dueDate }); + + const sub = emailStore.create({ + address: 'GFREELANCER1', + email: 'other@example.com', + eventTypes: ['invoice.paid'], // not invoice.expiring_soon + }); + emailStore.activate(sub.id); + + const scheduler = makeScheduler({ + invoiceDb, + notificationsDb, + emailStore, + emailDelivery, + now: () => BASE_NOW_MS, + }); + + await scheduler.runChecks(); + + expect(emailDelivery.send).not.toHaveBeenCalled(); + }); + + it('sends to subscribers with wildcard eventType "*"', async () => { + const dueDate = BASE_NOW_SEC + 24 * 3600; + insertInvoice(invoiceDb, { id: 8, status: 'Funded', due_date: dueDate }); + + const sub = emailStore.create({ + address: 'GFREELANCER1', + email: 'wildcard@example.com', + eventTypes: ['*'], + }); + emailStore.activate(sub.id); + + const scheduler = makeScheduler({ + invoiceDb, + notificationsDb, + emailStore, + emailDelivery, + now: () => BASE_NOW_MS, + }); + + await scheduler.runChecks(); + + // Wildcard matches both 72 h and 24 h thresholds. + expect(emailDelivery.send).toHaveBeenCalled(); + }); + + it('notifies payer and funder as well as freelancer', async () => { + const dueDate = BASE_NOW_SEC + 24 * 3600; + insertInvoice(invoiceDb, { + id: 9, + status: 'Funded', + due_date: dueDate, + freelancer: 'GFREELANCER1', + payer: 'GPAYER1', + funder: 'GFUNDER1', + }); + + for (const [address, email] of [ + ['GFREELANCER1', 'fl@example.com'], + ['GPAYER1', 'payer@example.com'], + ['GFUNDER1', 'funder@example.com'], + ]) { + const sub = emailStore.create({ + address, + email, + eventTypes: ['invoice.expiring_soon'], + }); + emailStore.activate(sub.id); + } + + const scheduler = makeScheduler({ + invoiceDb, + notificationsDb, + emailStore, + emailDelivery, + now: () => BASE_NOW_MS, + }); + + await scheduler.runChecks(); + + // Each of the 3 subscribers gets 2 emails (72 h + 24 h both match). + expect(emailDelivery.send).toHaveBeenCalledTimes(6); + const recipients = (emailDelivery.send as ReturnType).mock.calls.map( + (c) => c[0].to, + ); + expect(recipients).toContain('fl@example.com'); + expect(recipients).toContain('payer@example.com'); + expect(recipients).toContain('funder@example.com'); + }); +}); + +describe('SchedulerService – idempotency', () => { + let invoiceDb: Database.Database; + let notificationsDb: Database.Database; + let emailStore: EmailSubscriptionStore; + let emailDelivery: ReturnType; + + const BASE_NOW_SEC = 1_000_000; + const BASE_NOW_MS = BASE_NOW_SEC * 1000; + + beforeEach(() => { + invoiceDb = makeInvoiceDb(); + notificationsDb = new Database(':memory:'); + emailStore = new EmailSubscriptionStore(notificationsDb); + emailDelivery = makeEmailDelivery(); + }); + + afterEach(() => { + invoiceDb.close(); + notificationsDb.close(); + }); + + it('does not send duplicate reminders when runChecks() is called twice', async () => { + const dueDate = BASE_NOW_SEC + 48 * 3600; // only in 72 h window + insertInvoice(invoiceDb, { id: 10, status: 'Funded', due_date: dueDate }); + + const sub = emailStore.create({ + address: 'GFREELANCER1', + email: 'fl@example.com', + eventTypes: ['invoice.expiring_soon'], + }); + emailStore.activate(sub.id); + + const scheduler = makeScheduler({ + invoiceDb, + notificationsDb, + emailStore, + emailDelivery, + now: () => BASE_NOW_MS, + }); + + await scheduler.runChecks(); // first run → sends 1 email + await scheduler.runChecks(); // second run → idempotent, sends nothing + + expect(emailDelivery.send).toHaveBeenCalledTimes(1); + }); + + it('persists idempotency across separate SchedulerService instances', async () => { + const dueDate = BASE_NOW_SEC + 48 * 3600; + insertInvoice(invoiceDb, { id: 11, status: 'Funded', due_date: dueDate }); + + const sub = emailStore.create({ + address: 'GFREELANCER1', + email: 'fl@example.com', + eventTypes: ['invoice.expiring_soon'], + }); + emailStore.activate(sub.id); + + const sharedArgs = { + invoiceDb, + notificationsDb, + emailStore, + emailDelivery, + now: () => BASE_NOW_MS, + }; + + const scheduler1 = makeScheduler(sharedArgs); + await scheduler1.runChecks(); // delivers the 72 h reminder + + // Create a brand-new instance backed by the same notificationsDb. + const scheduler2 = makeScheduler(sharedArgs); + await scheduler2.runChecks(); // should be a no-op + + expect(emailDelivery.send).toHaveBeenCalledTimes(1); + }); + + it('stores a delivered_reminders row after successful run', async () => { + const dueDate = BASE_NOW_SEC + 48 * 3600; + insertInvoice(invoiceDb, { id: 12, status: 'Funded', due_date: dueDate }); + + const sub = emailStore.create({ + address: 'GFREELANCER1', + email: 'fl@example.com', + eventTypes: ['invoice.expiring_soon'], + }); + emailStore.activate(sub.id); + + const scheduler = makeScheduler({ + invoiceDb, + notificationsDb, + emailStore, + emailDelivery, + now: () => BASE_NOW_MS, + }); + + await scheduler.runChecks(); + + const rows = notificationsDb + .prepare(`SELECT invoice_id, threshold_h FROM delivered_reminders`) + .all() as { invoice_id: number; threshold_h: number }[]; + + expect(rows).toHaveLength(1); + expect(rows[0]).toMatchObject({ invoice_id: 12, threshold_h: 72 }); + }); + + it('records separate rows for the 72 h and 24 h thresholds', async () => { + // Invoice is within both the 72-h and 24-h windows. + const dueDate = BASE_NOW_SEC + 12 * 3600; + insertInvoice(invoiceDb, { id: 13, status: 'Funded', due_date: dueDate }); + + const sub = emailStore.create({ + address: 'GFREELANCER1', + email: 'fl@example.com', + eventTypes: ['invoice.expiring_soon'], + }); + emailStore.activate(sub.id); + + const scheduler = makeScheduler({ + invoiceDb, + notificationsDb, + emailStore, + emailDelivery, + now: () => BASE_NOW_MS, + }); + + await scheduler.runChecks(); + + const rows = notificationsDb + .prepare( + `SELECT threshold_h FROM delivered_reminders + WHERE invoice_id = 13 + ORDER BY threshold_h`, + ) + .all() as { threshold_h: number }[]; + + expect(rows.map((r) => r.threshold_h)).toEqual([24, 72]); + }); + + it('still records idempotency even when email delivery fails', async () => { + const failDelivery = makeEmailDelivery({ ok: false, error: 'SMTP error' }); + + const dueDate = BASE_NOW_SEC + 48 * 3600; + insertInvoice(invoiceDb, { id: 14, status: 'Funded', due_date: dueDate }); + + const sub = emailStore.create({ + address: 'GFREELANCER1', + email: 'fl@example.com', + eventTypes: ['invoice.expiring_soon'], + }); + emailStore.activate(sub.id); + + const scheduler = makeScheduler({ + invoiceDb, + notificationsDb, + emailStore, + emailDelivery: failDelivery, + now: () => BASE_NOW_MS, + }); + + await scheduler.runChecks(); + await scheduler.runChecks(); // should still skip on second run + + // send() was called once (first run) and not again (second run). + expect(failDelivery.send).toHaveBeenCalledTimes(1); + + const rows = notificationsDb + .prepare(`SELECT invoice_id FROM delivered_reminders WHERE invoice_id = 14`) + .all(); + expect(rows).toHaveLength(1); + }); +}); + +describe('SchedulerService – start() / stop()', () => { + it('starts and stops without errors', () => { + const invoiceDb = makeInvoiceDb(); + const notificationsDb = new Database(':memory:'); + const emailStore = new EmailSubscriptionStore(notificationsDb); + const emailDelivery = makeEmailDelivery(); + const logs: string[] = []; + + const scheduler = makeScheduler({ + invoiceDb, + notificationsDb, + emailStore, + emailDelivery, + logger: (m) => logs.push(m), + }); + + scheduler.start(); + expect(logs).toContain('scheduler_started cron="*/30 * * * *"'); + + scheduler.stop(); + expect(logs).toContain('scheduler_stopped'); + + invoiceDb.close(); + notificationsDb.close(); + }); + + it('start() is idempotent – calling twice does not create duplicate tasks', () => { + const invoiceDb = makeInvoiceDb(); + const notificationsDb = new Database(':memory:'); + const emailStore = new EmailSubscriptionStore(notificationsDb); + const emailDelivery = makeEmailDelivery(); + const logs: string[] = []; + + const scheduler = makeScheduler({ + invoiceDb, + notificationsDb, + emailStore, + emailDelivery, + logger: (m) => logs.push(m), + }); + + scheduler.start(); + scheduler.start(); // should be a no-op + + const startLogs = logs.filter((l) => l.startsWith('scheduler_started')); + expect(startLogs).toHaveLength(1); + + scheduler.stop(); + invoiceDb.close(); + notificationsDb.close(); + }); + + it('accepts a custom cron expression', () => { + const invoiceDb = makeInvoiceDb(); + const notificationsDb = new Database(':memory:'); + const emailStore = new EmailSubscriptionStore(notificationsDb); + const emailDelivery = makeEmailDelivery(); + const logs: string[] = []; + + const scheduler = new SchedulerService({ + invoiceDb, + notificationsDb, + emailStore, + emailDelivery, + publicUrl: 'http://localhost:3001', + cronExpression: '0 * * * *', // hourly + logger: (m) => logs.push(m), + }); + + scheduler.start(); + expect(logs).toContain('scheduler_started cron="0 * * * *"'); + scheduler.stop(); + + invoiceDb.close(); + notificationsDb.close(); + }); +}); + +describe('SchedulerService – logging', () => { + it('logs run metadata including found invoice count', async () => { + const invoiceDb = makeInvoiceDb(); + const notificationsDb = new Database(':memory:'); + const emailStore = new EmailSubscriptionStore(notificationsDb); + const emailDelivery = makeEmailDelivery(); + const logs: string[] = []; + + const BASE_NOW_SEC = 1_000_000; + const dueDate = BASE_NOW_SEC + 48 * 3600; + insertInvoice(invoiceDb, { id: 20, status: 'Funded', due_date: dueDate }); + + const scheduler = makeScheduler({ + invoiceDb, + notificationsDb, + emailStore, + emailDelivery, + now: () => BASE_NOW_SEC * 1000, + logger: (m) => logs.push(m), + }); + + await scheduler.runChecks(); + + expect(logs.some((l) => l.startsWith('scheduler_run'))).toBe(true); + expect(logs.some((l) => l.includes('threshold=72h') && l.includes('count=1'))).toBe(true); + expect(logs.some((l) => l.includes('threshold=24h') && l.includes('count=0'))).toBe(true); + + invoiceDb.close(); + notificationsDb.close(); + }); + + it('logs skip when reminder already delivered', async () => { + const invoiceDb = makeInvoiceDb(); + const notificationsDb = new Database(':memory:'); + const emailStore = new EmailSubscriptionStore(notificationsDb); + const emailDelivery = makeEmailDelivery(); + const logs: string[] = []; + + const BASE_NOW_SEC = 1_000_000; + const dueDate = BASE_NOW_SEC + 48 * 3600; + insertInvoice(invoiceDb, { id: 21, status: 'Funded', due_date: dueDate }); + + const scheduler = makeScheduler({ + invoiceDb, + notificationsDb, + emailStore, + emailDelivery, + now: () => BASE_NOW_SEC * 1000, + logger: (m) => logs.push(m), + }); + + await scheduler.runChecks(); // first run + logs.length = 0; // clear + await scheduler.runChecks(); // second run – should log skip + + expect(logs.some((l) => l.includes('already_delivered'))).toBe(true); + + invoiceDb.close(); + notificationsDb.close(); + }); +}); diff --git a/notifications/tsconfig.json b/notifications/tsconfig.json index d4df97e8..d7f13b6c 100644 --- a/notifications/tsconfig.json +++ b/notifications/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.base.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "target": "ES2022", "module": "ESNext", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d13aebc..c898ba6b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: devDependencies: + '@changesets/cli': + specifier: ^2.27.9 + version: 2.31.0(@types/node@20.19.43) turbo: specifier: ^2.0.0 version: 2.10.1 @@ -198,6 +201,10 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + '@babel/types@7.29.7': resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} @@ -209,6 +216,61 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@changesets/apply-release-plan@7.1.1': + resolution: {integrity: sha512-9qPCm/rLx/xoOFXIHGB229+4GOL76S4MC+7tyOuTsR6+1jYlfFDQORdvwR5hDA6y4FL2BPt3qpbcQIS+dW85LA==} + + '@changesets/assemble-release-plan@6.0.10': + resolution: {integrity: sha512-rSDcqdJ9KbVyjpBIuCidhvZNIiVt1XaIYp73ycVQRIA5n/j6wQaEk0ChRLMUQ1vkxZe51PTQ9OIhbg6HQMW45A==} + + '@changesets/changelog-git@0.2.1': + resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} + + '@changesets/cli@2.31.0': + resolution: {integrity: sha512-AhI4enNTgHu2IZr6K4WZyf0EPch4XVMn1yOMFmCD9gsfBGqMYaHXls5HyDv6/CL5axVQABz68eG30eCtbr2wFg==} + hasBin: true + + '@changesets/config@3.1.4': + resolution: {integrity: sha512-pf0bvD/v6WI2cRlZ6hzpjtZdSlXDXMAJ+Iz7xfFzV4ZxJ8OGGAON+1qYc99ZPrijnt4xp3VGG7eNvAOGS24V1Q==} + + '@changesets/errors@0.2.0': + resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} + + '@changesets/get-dependents-graph@2.1.4': + resolution: {integrity: sha512-ZsS00x6WvmHq3sQv8oCMwL0f/z3wbXCVuSVTJwCnnmbC/iBdNJGFx1EcbMG4PC6sXRyH69liM4A2WKXzn/kRPg==} + + '@changesets/get-release-plan@4.0.16': + resolution: {integrity: sha512-2K5Om6CrMPm45rtvckfzWo7e9jOVCKLCnXia5eUPaURH7/LWzri7pK1TycdzAuAtehLkW7VPbWLCSExTHmiI6g==} + + '@changesets/get-version-range-type@0.4.0': + resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} + + '@changesets/git@3.0.4': + resolution: {integrity: sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==} + + '@changesets/logger@0.1.1': + resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} + + '@changesets/parse@0.4.3': + resolution: {integrity: sha512-ZDmNc53+dXdWEv7fqIUSgRQOLYoUom5Z40gmLgmATmYR9NbL6FJJHwakcCpzaeCy+1D0m0n7mT4jj2B/MQPl7A==} + + '@changesets/pre@2.0.2': + resolution: {integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==} + + '@changesets/read@0.6.7': + resolution: {integrity: sha512-D1G4AUYGrBEk8vj8MGwf75k9GpN6XL3wg8i42P2jZZwFLXnlr2Pn7r9yuQNbaMCarP7ZQWNJbV6XLeysAIMhTA==} + + '@changesets/should-skip-package@0.1.2': + resolution: {integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==} + + '@changesets/types@4.1.0': + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + + '@changesets/types@6.1.0': + resolution: {integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==} + + '@changesets/write@0.4.0': + resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -659,6 +721,15 @@ packages: cpu: [x64] os: [win32] + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -684,6 +755,12 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@manypkg/find-root@1.1.0': + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + + '@manypkg/get-packages@1.1.3': + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + '@noble/ed25519@3.1.0': resolution: {integrity: sha512-pfcObRY3CtvwfaG9Mt5XqZdKmAQppl37tHUeuBhDUbiwJBCVY4/A4lbMvb1xKhMDx96AqAqZpMWuBX1HulhX4g==} @@ -695,6 +772,18 @@ packages: resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} engines: {node: '>= 20.19.0'} + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} @@ -951,6 +1040,9 @@ packages: '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + '@types/node@12.20.55': + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + '@types/node@20.19.43': resolution: {integrity: sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==} @@ -1057,6 +1149,10 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1083,9 +1179,19 @@ packages: any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} @@ -1141,6 +1247,10 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + better-path-resolve@1.0.0: + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} + engines: {node: '>=4'} + better-sqlite3@11.10.0: resolution: {integrity: sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==} @@ -1170,6 +1280,10 @@ packages: brace-expansion@2.1.1: resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -1210,6 +1324,9 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chardet@2.2.0: + resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} + check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} @@ -1341,6 +1458,10 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1352,6 +1473,10 @@ packages: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} @@ -1393,6 +1518,10 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -1434,6 +1563,11 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -1479,12 +1613,22 @@ packages: resolution: {integrity: sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==} engines: {node: '>= 0.10.0'} + extendable-error@0.1.7: + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + fast-deep-equal@2.0.1: resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1504,10 +1648,18 @@ packages: file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + finalhandler@1.3.2: resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} engines: {node: '>= 0.8'} + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + fix-dts-default-cjs-exports@1.0.1: resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} @@ -1550,6 +1702,14 @@ packages: fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -1618,6 +1778,10 @@ packages: github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me @@ -1627,10 +1791,17 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1668,6 +1839,10 @@ packages: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} + human-id@4.2.0: + resolution: {integrity: sha512-K3GbkIWqyvvlpfhBPlbEvD97TtqBpAYA4kt+cn2lD2x2HuohzZCibcA2nOlnJT6exqvJLggoB5nv2dNf192nEA==} + hasBin: true + human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} @@ -1680,9 +1855,17 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -1705,10 +1888,22 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} @@ -1725,6 +1920,10 @@ packages: resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} engines: {node: '>=18'} + is-subdir@1.2.0: + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} + engines: {node: '>=4'} + is-typed-array@1.1.15: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} @@ -1733,6 +1932,10 @@ packages: resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} engines: {node: '>=18'} + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -1779,9 +1982,20 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@3.15.0: + resolution: {integrity: sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==} + hasBin: true + + js-yaml@4.3.0: + resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} + hasBin: true + jsonc-parser@3.3.1: resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + leac@0.6.0: resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} @@ -1800,6 +2014,13 @@ packages: resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} engines: {node: '>=14'} + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -1845,10 +2066,18 @@ packages: merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -1895,6 +2124,10 @@ packages: mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -1956,13 +2189,39 @@ packages: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} + outdent@0.5.0: + resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + + p-filter@2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + p-limit@5.0.0: resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} engines: {node: '>=18'} + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-map@2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-manager-detector@0.2.11: + resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + parse-ms@4.0.0: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} @@ -1974,6 +2233,10 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -1993,6 +2256,10 @@ packages: path-to-regexp@0.1.13: resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -2008,10 +2275,18 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + picomatch@4.0.4: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -2051,6 +2326,11 @@ packages: deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. hasBin: true + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2077,6 +2357,12 @@ packages: resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} engines: {node: '>=0.6'} + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -2107,6 +2393,10 @@ packages: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} + read-yaml-file@1.1.0: + resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} + engines: {node: '>=6'} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -2127,11 +2417,18 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rollup@4.62.2: resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -2209,6 +2506,10 @@ packages: simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + smol-toml@1.7.0: resolution: {integrity: sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==} engines: {node: '>= 18'} @@ -2224,6 +2525,12 @@ packages: resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} engines: {node: '>= 12'} + spawndamnit@3.0.1: + resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -2256,6 +2563,10 @@ packages: resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} @@ -2305,6 +2616,10 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} + term-size@2.2.1: + resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} + engines: {node: '>=8'} + test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -2346,6 +2661,10 @@ packages: resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} engines: {node: '>= 0.4'} + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -2432,6 +2751,10 @@ packages: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -2618,6 +2941,8 @@ snapshots: dependencies: '@babel/types': 7.29.7 + '@babel/runtime@7.29.7': {} + '@babel/types@7.29.7': dependencies: '@babel/helper-string-parser': 7.29.7 @@ -2627,6 +2952,149 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} + '@changesets/apply-release-plan@7.1.1': + dependencies: + '@changesets/config': 3.1.4 + '@changesets/get-version-range-type': 0.4.0 + '@changesets/git': 3.0.4 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.8.8 + resolve-from: 5.0.0 + semver: 7.8.5 + + '@changesets/assemble-release-plan@6.0.10': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.4 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + semver: 7.8.5 + + '@changesets/changelog-git@0.2.1': + dependencies: + '@changesets/types': 6.1.0 + + '@changesets/cli@2.31.0(@types/node@20.19.43)': + dependencies: + '@changesets/apply-release-plan': 7.1.1 + '@changesets/assemble-release-plan': 6.0.10 + '@changesets/changelog-git': 0.2.1 + '@changesets/config': 3.1.4 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.4 + '@changesets/get-release-plan': 4.0.16 + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.7 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@changesets/write': 0.4.0 + '@inquirer/external-editor': 1.0.3(@types/node@20.19.43) + '@manypkg/get-packages': 1.1.3 + ansi-colors: 4.1.3 + enquirer: 2.4.1 + fs-extra: 7.0.1 + mri: 1.2.0 + package-manager-detector: 0.2.11 + picocolors: 1.1.1 + resolve-from: 5.0.0 + semver: 7.8.5 + spawndamnit: 3.0.1 + term-size: 2.2.1 + transitivePeerDependencies: + - '@types/node' + + '@changesets/config@3.1.4': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.4 + '@changesets/logger': 0.1.1 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.8 + + '@changesets/errors@0.2.0': + dependencies: + extendable-error: 0.1.7 + + '@changesets/get-dependents-graph@2.1.4': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + picocolors: 1.1.1 + semver: 7.8.5 + + '@changesets/get-release-plan@4.0.16': + dependencies: + '@changesets/assemble-release-plan': 6.0.10 + '@changesets/config': 3.1.4 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.7 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/get-version-range-type@0.4.0': {} + + '@changesets/git@3.0.4': + dependencies: + '@changesets/errors': 0.2.0 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.8 + spawndamnit: 3.0.1 + + '@changesets/logger@0.1.1': + dependencies: + picocolors: 1.1.1 + + '@changesets/parse@0.4.3': + dependencies: + '@changesets/types': 6.1.0 + js-yaml: 4.3.0 + + '@changesets/pre@2.0.2': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + + '@changesets/read@0.6.7': + dependencies: + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/parse': 0.4.3 + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + p-filter: 2.1.0 + picocolors: 1.1.1 + + '@changesets/should-skip-package@0.1.2': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/types@4.1.0': {} + + '@changesets/types@6.1.0': {} + + '@changesets/write@0.4.0': + dependencies: + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + human-id: 4.2.0 + prettier: 2.8.8 + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -2852,6 +3320,13 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true + '@inquirer/external-editor@1.0.3(@types/node@20.19.43)': + dependencies: + chardet: 2.2.0 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 20.19.43 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -2881,12 +3356,40 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@manypkg/find-root@1.1.0': + dependencies: + '@babel/runtime': 7.29.7 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + + '@manypkg/get-packages@1.1.3': + dependencies: + '@babel/runtime': 7.29.7 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + '@noble/ed25519@3.1.0': {} '@noble/hashes@1.8.0': {} '@noble/hashes@2.2.0': {} + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + '@one-ini/wasm@0.1.1': {} '@paralleldrive/cuid2@2.3.1': @@ -3103,6 +3606,8 @@ snapshots: '@types/mime@1.3.5': {} + '@types/node@12.20.55': {} + '@types/node@20.19.43': dependencies: undici-types: 6.21.0 @@ -3264,6 +3769,8 @@ snapshots: transitivePeerDependencies: - supports-color + ansi-colors@4.1.3: {} + ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} @@ -3280,8 +3787,16 @@ snapshots: any-promise@1.3.0: {} + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + array-flatten@1.1.1: {} + array-union@2.1.0: {} + asap@2.0.6: {} assertion-error@1.1.0: {} @@ -3340,6 +3855,10 @@ snapshots: base64-js@1.5.1: {} + better-path-resolve@1.0.0: + dependencies: + is-windows: 1.0.2 + better-sqlite3@11.10.0: dependencies: bindings: 1.5.0 @@ -3390,6 +3909,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -3438,6 +3961,8 @@ snapshots: chai@6.2.2: {} + chardet@2.2.0: {} + check-error@1.0.3: dependencies: get-func-name: 2.0.2 @@ -3533,6 +4058,8 @@ snapshots: destroy@1.2.0: {} + detect-indent@6.1.0: {} + detect-libc@2.1.2: {} dezalgo@1.0.4: @@ -3542,6 +4069,10 @@ snapshots: diff-sequences@29.6.3: {} + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 @@ -3587,6 +4118,11 @@ snapshots: dependencies: once: 1.4.0 + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + entities@4.5.0: {} es-define-property@1.0.1: {} @@ -3692,6 +4228,8 @@ snapshots: escape-html@1.0.3: {} + esprima@4.0.1: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 @@ -3778,10 +4316,24 @@ snapshots: transitivePeerDependencies: - supports-color + extendable-error@0.1.7: {} + fast-deep-equal@2.0.1: {} + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + fast-safe-stringify@2.1.1: {} + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 @@ -3796,6 +4348,10 @@ snapshots: file-uri-to-path@1.0.0: {} + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + finalhandler@1.3.2: dependencies: debug: 2.6.9 @@ -3808,6 +4364,11 @@ snapshots: transitivePeerDependencies: - supports-color + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + fix-dts-default-cjs-exports@1.0.1: dependencies: magic-string: 0.30.21 @@ -3852,6 +4413,18 @@ snapshots: fs-constants@1.0.0: {} + fs-extra@7.0.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -3917,6 +4490,10 @@ snapshots: github-from-package@0.0.0: {} + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + glob@10.5.0: dependencies: foreground-child: 3.3.1 @@ -3935,8 +4512,19 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + gopd@1.2.0: {} + graceful-fs@4.2.11: {} + has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -3985,6 +4573,8 @@ snapshots: transitivePeerDependencies: - supports-color + human-id@4.2.0: {} + human-signals@5.0.0: {} human-signals@8.0.1: {} @@ -3993,8 +4583,14 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + ieee754@1.2.1: {} + ignore@5.3.2: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -4010,8 +4606,16 @@ snapshots: is-callable@1.2.7: {} + is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + is-plain-obj@4.1.0: {} is-retry-allowed@3.0.0: {} @@ -4020,12 +4624,18 @@ snapshots: is-stream@4.0.1: {} + is-subdir@1.2.0: + dependencies: + better-path-resolve: 1.0.0 + is-typed-array@1.1.15: dependencies: which-typed-array: 1.1.22 is-unicode-supported@2.1.0: {} + is-windows@1.0.2: {} + isarray@2.0.5: {} isexe@2.0.0: {} @@ -4075,8 +4685,21 @@ snapshots: js-tokens@9.0.1: {} + js-yaml@3.15.0: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.3.0: + dependencies: + argparse: 2.0.1 + jsonc-parser@3.3.1: {} + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + leac@0.6.0: {} lilconfig@3.1.3: {} @@ -4090,6 +4713,12 @@ snapshots: mlly: 1.8.2 pkg-types: 1.3.1 + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + lodash.startcase@4.4.0: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -4132,8 +4761,15 @@ snapshots: merge-stream@2.0.0: {} + merge2@1.4.1: {} + methods@1.1.2: {} + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + mime-db@1.52.0: {} mime-types@2.1.35: @@ -4169,6 +4805,8 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.4 + mri@1.2.0: {} + ms@2.0.0: {} ms@2.1.3: {} @@ -4220,12 +4858,34 @@ snapshots: dependencies: mimic-fn: 4.0.0 + outdent@0.5.0: {} + + p-filter@2.1.0: + dependencies: + p-map: 2.1.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + p-limit@5.0.0: dependencies: yocto-queue: 1.2.2 + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-map@2.1.0: {} + + p-try@2.2.0: {} + package-json-from-dist@1.0.1: {} + package-manager-detector@0.2.11: + dependencies: + quansync: 0.2.11 + parse-ms@4.0.0: {} parseley@0.12.1: @@ -4235,6 +4895,8 @@ snapshots: parseurl@1.3.3: {} + path-exists@4.0.0: {} + path-is-absolute@1.0.1: {} path-key@3.1.1: {} @@ -4248,6 +4910,8 @@ snapshots: path-to-regexp@0.1.13: {} + path-type@4.0.0: {} + pathe@1.1.2: {} pathe@2.0.3: {} @@ -4258,8 +4922,12 @@ snapshots: picocolors@1.1.1: {} + picomatch@2.3.2: {} + picomatch@4.0.4: {} + pify@4.0.1: {} + pirates@4.0.7: {} pkg-types@1.3.1: @@ -4298,6 +4966,8 @@ snapshots: tar-fs: 2.1.5 tunnel-agent: 0.6.0 + prettier@2.8.8: {} + pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 @@ -4327,6 +4997,10 @@ snapshots: es-define-property: 1.0.1 side-channel: 1.1.1 + quansync@0.2.11: {} + + queue-microtask@1.2.3: {} + randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -4363,6 +5037,13 @@ snapshots: dependencies: loose-envify: 1.4.0 + read-yaml-file@1.1.0: + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.15.0 + pify: 4.0.1 + strip-bom: 3.0.0 + readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -4387,6 +5068,8 @@ snapshots: resolve-from@5.0.0: {} + reusify@1.1.0: {} + rollup@4.62.2: dependencies: '@types/estree': 1.0.9 @@ -4418,6 +5101,10 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.62.2 fsevents: 2.3.3 + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + safe-buffer@5.2.1: {} safer-buffer@2.1.2: {} @@ -4529,6 +5216,8 @@ snapshots: once: 1.4.0 simple-concat: 1.0.1 + slash@3.0.0: {} + smol-toml@1.7.0: {} sodium-native@4.3.3: @@ -4542,6 +5231,13 @@ snapshots: source-map@0.7.6: {} + spawndamnit@3.0.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + sprintf-js@1.0.3: {} + stackback@0.0.2: {} statuses@2.0.2: {} @@ -4574,6 +5270,8 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strip-bom@3.0.0: {} + strip-final-newline@3.0.0: {} strip-final-newline@4.0.0: {} @@ -4657,6 +5355,8 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + term-size@2.2.1: {} + test-exclude@6.0.0: dependencies: '@istanbuljs/schema': 0.1.6 @@ -4694,6 +5394,10 @@ snapshots: safe-buffer: 5.2.1 typed-array-buffer: 1.0.3 + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + toidentifier@1.0.1: {} toml@3.0.0: {} @@ -4782,6 +5486,8 @@ snapshots: unicorn-magic@0.3.0: {} + universalify@0.1.2: {} + unpipe@1.0.0: {} urijs@1.19.11: {}