feat: export historie jídel uživatele
CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 25s
CI / Build server (push) Successful in 28s
CI / Build client (push) Successful in 41s
CI / Playwright E2E tests (push) Successful in 1m53s
CI / Build and push Docker image (push) Successful in 1m1s
CI / Notify (push) Successful in 3s

This commit is contained in:
Ondřej Anděl
2026-08-12 13:53:36 +02:00
parent 065ccaf38a
commit eeb1630391
13 changed files with 1513 additions and 8 deletions
+3
View File
@@ -0,0 +1,3 @@
[
"Na stránce statistik lze stáhnout vlastní přehled stravování za vybraný měsíc ve formátu Excel, CSV nebo JSON (včetně vybraného jídla, poznámky a částky u objednávek po slevě)"
]
+1
View File
@@ -36,6 +36,7 @@
"cheerio": "^1.1.2",
"cors": "^2.8.5",
"dotenv": "^17.2.3",
"exceljs": "^4.4.0",
"express": "^5.1.0",
"jsonwebtoken": "^9.0.0",
"redis": "^5.9.0",
+24
View File
@@ -2,10 +2,34 @@ import express, { Request, Response } from "express";
import { getLogin } from "../auth";
import { parseToken } from "../utils";
import { getStats } from "../stats";
import { EXPORT_FORMATS, generateUserExport, isExportFormat } from "../userExport";
import { WeeklyStats } from "../../../types/gen/types.gen";
const router = express.Router();
/**
* Vrátí přehled stravování přihlášeného uživatele za vybraný měsíc (XLSX, CSV nebo JSON).
*/
router.get("/export", async (req: Request<{}, any, undefined>, res: Response) => {
const login = getLogin(parseToken(req));
const year = Number(req.query.year);
const month = Number(req.query.month);
const format = req.query.format ?? 'xlsx';
if (!Number.isInteger(year) || year < 2000 || year > 2100) {
return res.status(400).json({ error: "Neplatný rok" });
}
if (!Number.isInteger(month) || month < 1 || month > 12) {
return res.status(400).json({ error: "Neplatný měsíc" });
}
if (!isExportFormat(format)) {
return res.status(400).json({ error: `Neplatný formát exportu, podporované jsou: ${EXPORT_FORMATS.join(', ')}` });
}
const { content, mimeType, fileName } = await generateUserExport(login, year, month, format);
res.setHeader('Content-Type', mimeType);
res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`);
return res.status(200).send(content);
});
router.get("/", async (req: Request<{}, any, undefined>, res: Response<WeeklyStats>) => {
getLogin(parseToken(req));
if (typeof req.query.startDate === 'string' && typeof req.query.endDate === 'string') {
+58
View File
@@ -52,6 +52,64 @@ test('GET /stats s budoucím datem vrátí 400', async () => {
expect(res.status).toBe(400);
});
test('GET /stats/export vrátí XLSX soubor', async () => {
const res = await request(buildApp())
.get('/api/stats/export')
.query({ year: 2025, month: 3 })
.responseType('blob')
.set('Authorization', TOKEN);
expect(res.status).toBe(200);
expect(res.headers['content-type']).toContain('spreadsheetml');
expect(res.headers['content-disposition']).toContain('luncher-testuser-2025-03.xlsx');
// ZIP signatura XLSX souboru
expect(res.body.slice(0, 2).toString()).toBe('PK');
});
test('GET /stats/export?format=csv vrátí CSV', async () => {
const res = await request(buildApp())
.get('/api/stats/export')
.query({ year: 2025, month: 3, format: 'csv' })
.set('Authorization', TOKEN);
expect(res.status).toBe(200);
expect(res.headers['content-type']).toContain('text/csv');
expect(res.headers['content-disposition']).toContain('luncher-testuser-2025-03.csv');
expect(res.text).toContain('"Datum";"Den";"Typ"');
});
test('GET /stats/export?format=json vrátí JSON s metadaty', async () => {
const res = await request(buildApp())
.get('/api/stats/export')
.query({ year: 2025, month: 3, format: 'json' })
.set('Authorization', TOKEN);
expect(res.status).toBe(200);
expect(res.headers['content-type']).toContain('application/json');
expect(res.headers['content-disposition']).toContain('luncher-testuser-2025-03.json');
expect(res.body).toMatchObject({ login: 'testuser', year: 2025, month: 3, rowCount: 0, rows: [] });
});
test('GET /stats/export s neznámým formátem vrátí 400', async () => {
const res = await request(buildApp())
.get('/api/stats/export')
.query({ year: 2025, month: 3, format: 'pdf' })
.set('Authorization', TOKEN);
expect(res.status).toBe(400);
});
test('GET /stats/export s neplatným měsícem vrátí 400', async () => {
const res = await request(buildApp())
.get('/api/stats/export')
.query({ year: 2025, month: 13 })
.set('Authorization', TOKEN);
expect(res.status).toBe(400);
});
test('GET /stats/export bez parametrů vrátí 400', async () => {
const res = await request(buildApp())
.get('/api/stats/export')
.set('Authorization', TOKEN);
expect(res.status).toBe(400);
});
test('GET /stats bez tokenu vrátí chybu', async () => {
const res = await request(buildApp())
.get('/api/stats')
+310
View File
@@ -0,0 +1,310 @@
import getStorage from '../storage';
import { resetMemoryStorage } from '../storage/memory';
import { getUserExportRows, computeMemberAmount, getUserExportFileName, buildCsv, buildJson, generateUserExport, isExportFormat, UserExportRow } from '../userExport';
import { ClientData, GroupState, LunchChoice, OrderGroup, PizzaDayState, WeekMenu } from '../../../types/gen/types.gen';
const storage = getStorage();
const USER = 'petr';
const OTHER = 'tomas';
// 2025-03-03 je pondělí, 2025-03-04 úterý, ...
const YEAR = 2025;
const MONTH = 3;
/** Uloží denní data pro slot oběd. */
async function saveLunch(isoDate: string, choices: ClientData["choices"], pizzaDay?: ClientData["pizzaDay"]) {
await storage.setData<Partial<ClientData>>(isoDate, { choices, pizzaDay });
}
/** Uloží data slotu extra s předanými skupinami. */
async function saveExtra(isoDate: string, groups: OrderGroup[]) {
await storage.setData<Partial<ClientData>>(`${isoDate}_extra`, { choices: {}, groups });
}
/** Uloží týdenní menu tak, aby daný den obsahoval předaná jídla. */
async function saveWeekMenu(menuKey: string, dayIndex: number, foodNames: string[]) {
const week: WeekMenu = [{}, {}, {}, {}, {}] as WeekMenu;
week[dayIndex] = {
TECHTOWER: {
lastUpdate: 0,
closed: false,
food: foodNames.map(name => ({ name, isSoup: false })),
},
};
await storage.setData(menuKey, week);
}
beforeEach(() => {
resetMemoryStorage();
});
describe('getUserExportRows', () => {
test('vynechá dny bez záznamu i volbu "mám vlastní/neobědvám"', async () => {
await saveLunch('2025-03-03', { NEOBEDVAM: { [USER]: {} } });
await saveLunch('2025-03-04', { SPSE: { [USER]: {} } });
// den, kde má volbu jen jiný uživatel
await saveLunch('2025-03-05', { SPSE: { [OTHER]: {} } });
const rows = await getUserExportRows(USER, YEAR, MONTH);
expect(rows).toHaveLength(1);
expect(rows[0]).toMatchObject({ date: '2025-03-04', type: 'SPŠE' });
});
test('vyplní vybrané jídlo z uloženého menu i poznámku', async () => {
await saveWeekMenu('menu_2025_10', 0, ['Polévka', 'Svíčková', 'Guláš']);
await saveLunch('2025-03-03', {
TECHTOWER: { [USER]: { selectedFoods: [1, 2], note: 'bez knedlíku' } },
});
const rows = await getUserExportRows(USER, YEAR, MONTH);
expect(rows).toHaveLength(1);
expect(rows[0]).toMatchObject({
date: '2025-03-03',
dayOfWeek: 'pondělí',
type: 'TechTower',
food: 'Svíčková + Guláš',
note: 'bez knedlíku',
});
});
test('u volby "budu objednávat" sloučí záznam s objednávkovou skupinou včetně ceny po slevě', async () => {
await saveLunch('2025-03-06', { OBJEDNAVAM: { [USER]: { note: 'platím kartou' } } });
await saveExtra('2025-03-06', [{
id: 'g1',
name: 'KFC',
creatorLogin: OTHER,
state: GroupState.ORDERED,
orderedAt: '11:30',
shipping: 4000,
discountType: 'percent',
discountValue: 10,
members: {
[USER]: { amount: 20000, note: 'Twister menu', paid: true },
[OTHER]: { amount: 20000 },
},
}]);
const rows = await getUserExportRows(USER, YEAR, MONTH);
expect(rows).toHaveLength(1);
expect(rows[0]).toMatchObject({
date: '2025-03-06',
type: 'Budu objednávat',
food: 'Twister menu',
note: 'platím kartou',
store: 'KFC',
orderedBy: OTHER,
// 200 Kč + 20 Kč doprava (poloviny ze 40) - 20 Kč sleva (10 %)
amount: 20000 + 2000 - 2000,
});
});
test('objednávku bez volby oběda vypíše jako samostatný záznam', async () => {
await saveExtra('2025-03-07', [{
id: 'g2',
name: 'Bageterie',
creatorLogin: USER,
state: GroupState.OPEN,
members: { [USER]: { amount: 15000, surchargeText: 'sýr', surchargeAmount: 2000 } },
}]);
const rows = await getUserExportRows(USER, YEAR, MONTH);
expect(rows).toHaveLength(1);
expect(rows[0]).toMatchObject({
date: '2025-03-07',
type: 'Objednávka',
store: 'Bageterie',
orderedBy: USER,
food: 'příplatek: sýr',
amount: 17000,
});
});
test('u Pizza day vypíše objednané pizzy a celkovou cenu', async () => {
await saveLunch('2025-03-10', { PIZZA: { [USER]: {} } }, {
state: PizzaDayState.DELIVERED,
creator: OTHER,
orders: [{
customer: USER,
pizzaList: [{ varId: 1, name: 'Margherita', size: '32cm', price: 18000 }],
fee: { text: 'kuřecí maso', price: 3000 },
totalPrice: 21000,
note: 'bez oliv',
hasQr: true,
}],
} as ClientData["pizzaDay"]);
const rows = await getUserExportRows(USER, YEAR, MONTH);
expect(rows).toHaveLength(1);
expect(rows[0]).toMatchObject({
type: 'Pizza day',
food: 'Margherita (32cm) + příplatek: kuřecí maso',
note: 'bez oliv',
amount: 21000,
});
});
test('více skupin v jeden den vypíše jako samostatné řádky', async () => {
await saveLunch('2025-03-11', { OBJEDNAVAM: { [USER]: {} } });
await saveExtra('2025-03-11', [
{ id: 'a', name: 'KFC', creatorLogin: USER, state: GroupState.OPEN, members: { [USER]: { amount: 10000 } } },
{ id: 'b', name: 'Bageterie', creatorLogin: USER, state: GroupState.OPEN, members: { [USER]: { amount: 5000 } } },
]);
const rows = await getUserExportRows(USER, YEAR, MONTH);
expect(rows).toHaveLength(2);
// V rámci dne je nejdřív volba oběda (se sloučenou první skupinou), pak zbytek objednávek
expect(rows.map(r => r.type)).toEqual(['Budu objednávat', 'Objednávka']);
expect(rows.map(r => r.store)).toEqual(['KFC', 'Bageterie']);
});
test('nezahrne data mimo vybraný měsíc', async () => {
await saveLunch('2025-02-28', { SPSE: { [USER]: {} } });
await saveLunch('2025-03-03', { SPSE: { [USER]: {} } });
await saveLunch('2025-04-01', { SPSE: { [USER]: {} } });
const rows = await getUserExportRows(USER, YEAR, MONTH);
expect(rows.map(r => r.date)).toEqual(['2025-03-03']);
});
});
test('člen skupiny, který si nic neobjednal, nemá vyplněnou částku', async () => {
await saveExtra('2025-03-12', [{
id: 'g3',
name: 'KFC',
creatorLogin: USER,
state: GroupState.ORDERED,
members: { [USER]: {}, [OTHER]: { amount: 15000 } },
}]);
const rows = await getUserExportRows(USER, YEAR, MONTH);
expect(rows).toHaveLength(1);
// Objednávající je uveden, ale částka zůstává prázdná — není co platit
expect(rows[0].orderedBy).toBe(USER);
expect(rows[0].amount).toBeUndefined();
expect(buildCsv(rows).split('\r\n')[1].split(';')[7]).toBe('');
expect(buildJson(USER, YEAR, MONTH, rows).rows[0].amount).toBeUndefined();
});
describe('computeMemberAmount', () => {
const group = (overrides: Partial<OrderGroup>): OrderGroup => ({
id: 'g',
name: 'KFC',
creatorLogin: OTHER,
state: GroupState.OPEN,
members: {},
...overrides,
});
test('neaktivní člen (nic si neobjednal) platí nula', () => {
const g = group({ shipping: 4000, members: { [USER]: {}, [OTHER]: { amount: 10000 } } });
expect(computeMemberAmount(g, USER)).toBe(0);
});
test('pevná sleva se dělí mezi aktivní členy', () => {
const g = group({
discountType: 'fixed',
discountValue: 5000,
members: { [USER]: { amount: 10000 }, [OTHER]: { amount: 10000 } },
});
expect(computeMemberAmount(g, USER)).toBe(10000 - 2500);
});
test('poplatky se rozpočítají jen mezi aktivní členy', () => {
const g = group({
fees: 1000,
shipping: 5000,
tip: 0,
members: { [USER]: { amount: 10000 }, [OTHER]: {} },
});
expect(computeMemberAmount(g, USER)).toBe(16000);
});
});
test('getUserExportFileName očistí login a doplní měsíc s příponou dle formátu', () => {
expect(getUserExportFileName('domena\\petr', 2025, 3)).toBe('luncher-domena_petr-2025-03.xlsx');
expect(getUserExportFileName('petr', 2025, 3, 'csv')).toBe('luncher-petr-2025-03.csv');
expect(getUserExportFileName('petr', 2025, 12, 'json')).toBe('luncher-petr-2025-12.json');
});
test('isExportFormat propustí jen podporované formáty', () => {
expect(isExportFormat('xlsx')).toBe(true);
expect(isExportFormat('csv')).toBe(true);
expect(isExportFormat('json')).toBe(true);
expect(isExportFormat('pdf')).toBe(false);
expect(isExportFormat(undefined)).toBe(false);
});
describe('buildCsv', () => {
const rows: UserExportRow[] = [
{ date: '2025-03-03', dayOfWeek: 'pondělí', type: 'TechTower', food: 'Svíčková', note: 'bez knedlíku' },
{
date: '2025-03-06', dayOfWeek: 'čtvrtek', type: 'Budu objednávat', food: 'Twister; s "extra" sýrem',
store: 'KFC', orderedBy: 'tomas', amount: 20000,
},
];
test('začíná BOM a hlavičkou oddělenou středníkem', () => {
const csv = buildCsv(rows);
expect(csv.charCodeAt(0)).toBe(0xFEFF);
expect(csv.slice(1).split('\r\n')[0]).toBe('"Datum";"Den";"Typ";"Vybrané jídlo";"Poznámka";"Objednávka";"Objednával";"Částka"');
});
test('formátuje datum, částku s desetinnou čárkou a escapuje uvozovky i středníky', () => {
const lines = buildCsv(rows).split('\r\n');
expect(lines[1]).toBe('"03.03.2025";"pondělí";"TechTower";"Svíčková";"bez knedlíku";;;');
expect(lines[2]).toBe('"06.03.2025";"čtvrtek";"Budu objednávat";"Twister; s ""extra"" sýrem";;"KFC";"tomas";"200,00"');
});
test('u záznamu bez objednávky nechá sloupce objednávky prázdné', () => {
const csv = buildCsv([{ date: '2025-03-03', dayOfWeek: 'pondělí', type: 'Pizza day', amount: 21000 }]);
const cells = csv.split('\r\n')[1].split(';');
expect(cells[5]).toBe('');
expect(cells[6]).toBe('');
expect(cells[7]).toBe('"210,00"');
});
});
describe('buildJson', () => {
test('obsahuje metadata, součet v korunách a jen vyplněná pole', () => {
const rows: UserExportRow[] = [
{ date: '2025-03-03', dayOfWeek: 'pondělí', type: 'SPŠE' },
{ date: '2025-03-06', dayOfWeek: 'čtvrtek', type: 'Objednávka', store: 'KFC', orderedBy: 'tomas', amount: 22000 },
];
const json = buildJson('petr', 2025, 3, rows);
expect(json).toMatchObject({ login: 'petr', year: 2025, month: 3, rowCount: 2, totalAmount: 220 });
expect(json.rows[0]).toEqual({ date: '2025-03-03', dayOfWeek: 'pondělí', type: 'SPŠE' });
expect(json.rows[1]).toEqual({
date: '2025-03-06', dayOfWeek: 'čtvrtek', type: 'Objednávka',
store: 'KFC', orderedBy: 'tomas', amount: 220,
});
});
});
describe('generateUserExport', () => {
beforeEach(async () => {
await storage.setData('2025-03-03', { choices: { SPSE: { [USER]: { note: 'polévka' } } } });
});
test('xlsx vrátí ZIP obsah se správným MIME a názvem', async () => {
const result = await generateUserExport(USER, YEAR, MONTH);
expect(result.content.subarray(0, 2).toString()).toBe('PK');
expect(result.mimeType).toContain('spreadsheetml');
expect(result.fileName).toBe('luncher-petr-2025-03.xlsx');
});
test('csv vrátí textový obsah s daty uživatele', async () => {
const result = await generateUserExport(USER, YEAR, MONTH, 'csv');
expect(result.mimeType).toBe('text/csv; charset=utf-8');
expect(result.fileName).toBe('luncher-petr-2025-03.csv');
expect(result.content.toString('utf-8')).toContain('"03.03.2025";"pondělí";"SPŠE"');
});
test('json vrátí parsovatelný obsah s metadaty', async () => {
const result = await generateUserExport(USER, YEAR, MONTH, 'json');
expect(result.mimeType).toBe('application/json; charset=utf-8');
expect(result.fileName).toBe('luncher-petr-2025-03.json');
const parsed = JSON.parse(result.content.toString('utf-8'));
expect(parsed).toMatchObject({ login: USER, year: YEAR, month: MONTH, rowCount: 1 });
expect(parsed.rows[0]).toMatchObject({ type: 'SPŠE', note: 'polévka' });
});
});
+385
View File
@@ -0,0 +1,385 @@
import ExcelJS from "exceljs";
import { ClientData, LunchChoice, OrderGroup, OrderGroupMember, Restaurant, UserExport, WeekMenu } from "../../types/gen/types.gen";
import getStorage from "./storage";
import { formatDate, getDayOfWeekIndex } from "./utils";
import { getMenuKey, getToday } from "./service";
const storage = getStorage();
const DAY_OF_WEEK_FORMAT = new Intl.DateTimeFormat('cs-CZ', { weekday: 'long' });
/** Lidsky čitelné názvy voleb stravování (serverová obdoba klientského enums.ts). */
const CHOICE_NAMES: Record<LunchChoice, string> = {
SLADOVNICKA: 'Sladovnická',
TECHTOWER: 'TechTower',
ZASTAVKAUMICHALA: 'Zastávka u Michala',
SENKSERIKOVA: 'Šenk Šeříková',
SPSE: 'SPŠE',
PIZZA: 'Pizza day',
OBJEDNAVAM: 'Budu objednávat',
NEOBEDVAM: 'Mám vlastní/neobědvám',
ROZHODUJI: 'Rozhoduji se',
};
/** Název typu záznamu pro objednávku bez odpovídající volby oběda. */
const ORDER_ONLY_LABEL = 'Objednávka';
/** Volby, které v přehledu nechceme (uživatel si nic neobjednal ani nikam nešel). */
const EXCLUDED_CHOICES: LunchChoice[] = [LunchChoice.NEOBEDVAM];
/**
* Jeden řádek přehledu stravování uživatele — interní podoba.
* Částky jsou zde v haléřích, na koruny se převádějí až při generování výstupu.
*/
export type UserExportRow = {
/** Datum záznamu ve formátu YYYY-MM-DD */
date: string;
/** Den v týdnu (pondělí, ...) */
dayOfWeek: string;
/** Název typu záznamu (podnik, "Budu objednávat", "Objednávka", ...) */
type: string;
/** Konkrétní vybrané jídlo (jídla z menu, pizzy, položky objednávky) */
food?: string;
/** Poznámka uživatele k volbě */
note?: string;
/** Obchod/restaurace objednávkové skupiny */
store?: string;
/** Login objednávajícího (zakladatele objednávkové skupiny) */
orderedBy?: string;
/** Částka k úhradě po slevě v haléřích */
amount?: number;
};
/**
* Vrátí true, pokud si člen skupiny reálně něco objednal.
* Duplikát klientského {@link ../../client/src/utils/groupFees.ts} — server a klient
* nemají společný modul pro doménovou logiku, výpočet ale musí dávat stejné výsledky.
*/
function isActiveMember(member: OrderGroupMember): boolean {
return (member.amount ?? 0) + (member.surchargeAmount ?? 0) > 0;
}
/**
* Vypočte částku člena skupiny po rozpočítání poplatků a slevy (v haléřích).
* Poplatky i pevná sleva se dělí pouze mezi členy, kteří si něco objednali.
*/
export function computeMemberAmount(group: OrderGroup, login: string): number {
const member = group.members[login];
if (!member || !isActiveMember(member)) return 0;
const activeCount = Object.values(group.members).filter(isActiveMember).length;
const totalFees = (group.fees ?? 0) + (group.shipping ?? 0) + (group.tip ?? 0);
const feeShare = activeCount > 0 ? Math.round(totalFees / activeCount) : 0;
const base = member.amount ?? 0;
const surcharge = member.surchargeAmount ?? 0;
const discountValue = group.discountValue ?? 0;
const discount = discountValue > 0
? (group.discountType === 'percent'
? Math.round((base + surcharge) * discountValue / 100)
: Math.round(discountValue / activeCount))
: 0;
return base + surcharge + feeShare - discount;
}
/** Vrátí názvy jídel vybraných uživatelem v daném podniku, dle uloženého týdenního menu. */
function getSelectedFoodNames(weekMenu: WeekMenu | undefined, dayIndex: number, restaurant: Restaurant, indexes: number[]): string[] {
const food = weekMenu?.[dayIndex]?.[restaurant]?.food;
if (!food?.length) return [];
return indexes
.map(index => food[index]?.name)
.filter((name): name is string => !!name?.length);
}
/** Spojí neprázdné texty do jednoho řádku. */
function join(parts: (string | undefined)[], separator = ', '): string | undefined {
const filtered = parts.filter((part): part is string => !!part?.trim().length);
return filtered.length ? filtered.join(separator) : undefined;
}
/** Popis jednoho člena objednávky (co si objednal) — poznámka + případný příplatek. */
function getOrderItemText(member: OrderGroupMember): string | undefined {
return join([member.note, member.surchargeText ? `příplatek: ${member.surchargeText}` : undefined], ' + ');
}
/** Vrátí popis pizz objednaných uživatelem v rámci Pizza day. */
function getPizzaText(data: ClientData | undefined, login: string): { food?: string, note?: string, amount?: number } {
const order = data?.pizzaDay?.orders?.find(o => o.customer === login);
if (!order) return {};
const pizzas = (order.pizzaList ?? []).map(variant => `${variant.name} (${variant.size})`);
return {
food: join([...pizzas, order.fee?.text ? `příplatek: ${order.fee.text}` : undefined], ' + '),
note: order.note,
amount: order.totalPrice,
};
}
/**
* Sestaví přehled stravování jednoho uživatele za vybraný měsíc.
*
* Do přehledu se dostanou pouze dny, kdy uživatel něco zvolil (vynechány jsou dny bez záznamu
* a volba "Mám vlastní/neobědvám"), plus dny, kdy byl členem objednávkové skupiny.
* Volba "Budu objednávat" se slučuje s odpovídající objednávkovou skupinou do jednoho řádku;
* objednávky bez této volby se vypisují jako samostatné řádky.
*
* @param login přihlašovací jméno uživatele
* @param year rok
* @param month měsíc (1 = leden)
* @returns řádky přehledu seřazené dle data
*/
export async function getUserExportRows(login: string, year: number, month: number): Promise<UserExportRow[]> {
const rows: UserExportRow[] = [];
const lastDayOfMonth = new Date(year, month, 0).getDate();
const today = getToday();
today.setHours(23, 59, 59, 999);
const weekMenuCache = new Map<string, WeekMenu | undefined>();
for (let day = 1; day <= lastDayOfMonth; day++) {
const date = new Date(year, month - 1, day);
if (date > today) break;
const isoDate = formatDate(date);
const dayOfWeek = DAY_OF_WEEK_FORMAT.format(date);
const lunchData = await storage.getData<ClientData>(isoDate);
const extraData = await storage.getData<ClientData>(`${isoDate}_extra`);
// Volba oběda daného uživatele
let choice: LunchChoice | undefined;
let choiceRow: UserExportRow | undefined;
for (const key of Object.keys(lunchData?.choices ?? {})) {
const locationKey = key as LunchChoice;
const userChoice = lunchData!.choices[locationKey]?.[login];
if (!userChoice) continue;
choice = locationKey;
if (EXCLUDED_CHOICES.includes(locationKey)) break;
let food: string | undefined;
let note = userChoice.note;
let amount: number | undefined;
if (locationKey === LunchChoice.PIZZA) {
const pizza = getPizzaText(lunchData, login);
food = pizza.food;
note = join([note, pizza.note], ' | ');
amount = pizza.amount;
} else if (userChoice.selectedFoods?.length) {
const menuKey = getMenuKey(date);
if (!weekMenuCache.has(menuKey)) {
weekMenuCache.set(menuKey, await storage.getData<WeekMenu>(menuKey));
}
food = join(getSelectedFoodNames(weekMenuCache.get(menuKey), getDayOfWeekIndex(date), locationKey as Restaurant, userChoice.selectedFoods), ' + ');
}
choiceRow = {
date: isoDate,
dayOfWeek,
// Fallback na klíč — v datech se mohou vyskytnout i historické/neznámé volby
type: CHOICE_NAMES[locationKey] ?? locationKey,
food,
note,
amount,
};
break;
}
// Objednávkové skupiny, ve kterých byl uživatel členem
const groups = (extraData?.groups ?? []).filter(group => !!group.members[login]);
const groupRows: UserExportRow[] = [];
let mergedGroup = false;
for (const group of groups) {
const member = group.members[login];
const orderInfo = {
store: group.name,
orderedBy: group.creatorLogin,
amount: isActiveMember(member) ? computeMemberAmount(group, login) : undefined,
};
// První objednávku sloučíme s volbou "Budu objednávat", pokud ji uživatel má
if (!mergedGroup && choiceRow && choice === LunchChoice.OBJEDNAVAM) {
choiceRow.food = join([choiceRow.food, getOrderItemText(member)], ' + ');
Object.assign(choiceRow, orderInfo);
mergedGroup = true;
continue;
}
groupRows.push({
date: isoDate,
dayOfWeek,
type: ORDER_ONLY_LABEL,
food: getOrderItemText(member),
...orderInfo,
});
}
// V rámci dne je první volba oběda, až za ní případné samostatné objednávky
if (choiceRow) {
rows.push(choiceRow);
}
rows.push(...groupRows);
}
return rows.sort((a, b) => a.date.localeCompare(b.date));
}
const MONEY_FORMAT = '#,##0.00 "Kč"';
/** Převede částku v haléřích na koruny pro zápis do XLSX. */
function toCrowns(amount?: number): number | undefined {
return amount == null ? undefined : amount / 100;
}
/** Podporované formáty exportu. */
export const EXPORT_FORMATS = ['xlsx', 'csv', 'json'] as const;
export type ExportFormat = typeof EXPORT_FORMATS[number];
/** MIME typy jednotlivých formátů. */
const MIME_TYPES: Record<ExportFormat, string> = {
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
csv: 'text/csv; charset=utf-8',
json: 'application/json; charset=utf-8',
};
/** Vrátí true, pokud je předaná hodnota podporovaným formátem exportu. */
export function isExportFormat(value: unknown): value is ExportFormat {
return typeof value === 'string' && (EXPORT_FORMATS as readonly string[]).includes(value);
}
/** Hlavičky sloupců přehledu (společné pro XLSX i CSV). */
const COLUMNS: { header: string, key: keyof UserExportRow, width: number }[] = [
{ header: 'Datum', key: 'date', width: 12 },
{ header: 'Den', key: 'dayOfWeek', width: 12 },
{ header: 'Typ', key: 'type', width: 22 },
{ header: 'Vybrané jídlo', key: 'food', width: 45 },
{ header: 'Poznámka', key: 'note', width: 30 },
{ header: 'Objednávka', key: 'store', width: 20 },
{ header: 'Objednával', key: 'orderedBy', width: 16 },
{ header: 'Částka', key: 'amount', width: 14 },
];
/**
* Vygeneruje CSV přehled stravování uživatele.
*
* Formát je uzpůsobený českému Excelu: oddělovač ';', desetinná čárka a UTF-8 BOM,
* aby se soubor otevřel se správnou diakritikou i bez ručního nastavování importu.
*/
export function buildCsv(rows: UserExportRow[]): string {
const SEPARATOR = ';';
const escape = (value: string) => `"${value.replace(/"/g, '""')}"`;
const lines = [COLUMNS.map(column => escape(column.header)).join(SEPARATOR)];
for (const row of rows) {
const values = COLUMNS.map(column => {
if (column.key === 'date') {
const [y, m, d] = row.date.split('-');
return escape(`${d}.${m}.${y}`);
}
if (column.key === 'amount') {
const crowns = toCrowns(row.amount);
// Desetinná čárka — český Excel jinak částku načte jako text
return crowns == null ? '' : escape(crowns.toFixed(2).replace('.', ','));
}
const value = row[column.key];
return value == null ? '' : escape(String(value));
});
lines.push(values.join(SEPARATOR));
}
// BOM kvůli správnému rozpoznání UTF-8 v Excelu
return `${lines.join('\r\n')}\r\n`;
}
/**
* Vygeneruje JSON přehled stravování uživatele.
* Částky jsou v korunách (na rozdíl od interní reprezentace v haléřích).
*/
export function buildJson(login: string, year: number, month: number, rows: UserExportRow[]): UserExport {
return {
login,
year,
month,
rowCount: rows.length,
totalAmount: toCrowns(rows.reduce((sum, row) => sum + (row.amount ?? 0), 0))!,
rows: rows.map(row => ({
date: row.date,
dayOfWeek: row.dayOfWeek,
type: row.type,
...(row.food ? { food: row.food } : {}),
...(row.note ? { note: row.note } : {}),
...(row.store ? { store: row.store } : {}),
...(row.orderedBy ? { orderedBy: row.orderedBy } : {}),
...(row.amount != null ? { amount: toCrowns(row.amount) } : {}),
})),
};
}
/**
* Vygeneruje přehled stravování uživatele za vybraný měsíc ve vyžádaném formátu.
*
* @param login přihlašovací jméno uživatele
* @param year rok
* @param month měsíc (1 = leden)
* @param format formát exportu (výchozí xlsx)
* @returns obsah souboru, jeho MIME typ a název
*/
export async function generateUserExport(login: string, year: number, month: number, format: ExportFormat = 'xlsx'): Promise<{ content: Buffer, mimeType: string, fileName: string }> {
const rows = await getUserExportRows(login, year, month);
const result = {
mimeType: MIME_TYPES[format],
fileName: getUserExportFileName(login, year, month, format),
};
if (format === 'csv') {
return { ...result, content: Buffer.from(buildCsv(rows), 'utf-8') };
}
if (format === 'json') {
return { ...result, content: Buffer.from(JSON.stringify(buildJson(login, year, month, rows), null, 2), 'utf-8') };
}
return { ...result, content: await buildXlsx(login, year, month, rows) };
}
/** Vygeneruje XLSX podobu přehledu. */
async function buildXlsx(login: string, year: number, month: number, rows: UserExportRow[]): Promise<Buffer> {
const workbook = new ExcelJS.Workbook();
workbook.creator = 'Luncher';
const sheet = workbook.addWorksheet('Přehled');
sheet.columns = COLUMNS.map(column => ({
...column,
...(column.key === 'date' ? { style: { numFmt: 'dd.mm.yyyy' } } : {}),
...(column.key === 'amount' ? { style: { numFmt: MONEY_FORMAT } } : {}),
}));
sheet.getRow(1).font = { bold: true };
sheet.views = [{ state: 'frozen', ySplit: 1 }];
for (const row of rows) {
const [y, m, d] = row.date.split('-').map(Number);
sheet.addRow({
...row,
// UTC, aby Excel nezobrazil o den dřív vlivem časové zóny
date: new Date(Date.UTC(y, m - 1, d)),
amount: toCrowns(row.amount),
});
}
sheet.autoFilter = { from: { row: 1, column: 1 }, to: { row: 1, column: sheet.columns.length } };
// Souhrn: počty záznamů dle typu a celková zaplacená částka
const summary = workbook.addWorksheet('Souhrn');
summary.columns = [
{ header: 'Typ', key: 'type', width: 24 },
{ header: 'Počet záznamů', key: 'count', width: 15 },
{ header: 'Částka celkem', key: 'amount', width: 16, style: { numFmt: MONEY_FORMAT } },
];
summary.getRow(1).font = { bold: true };
const perType = new Map<string, { count: number, amount: number }>();
for (const row of rows) {
const entry = perType.get(row.type) ?? { count: 0, amount: 0 };
entry.count++;
entry.amount += row.amount ?? 0;
perType.set(row.type, entry);
}
for (const [type, entry] of perType) {
summary.addRow({ type, count: entry.count, amount: toCrowns(entry.amount) });
}
const totalAmount = rows.reduce((sum, row) => sum + (row.amount ?? 0), 0);
const totalRow = summary.addRow({ type: 'Celkem', count: rows.length, amount: toCrowns(totalAmount) });
totalRow.font = { bold: true };
const buffer = await workbook.xlsx.writeBuffer();
return Buffer.from(buffer);
}
/** Vrátí název souboru přehledu pro daného uživatele, měsíc a formát. */
export function getUserExportFileName(login: string, year: number, month: number, format: ExportFormat = 'xlsx'): string {
const safeLogin = login.replace(/[^a-zA-Z0-9._-]/g, '_');
return `luncher-${safeLogin}-${year}-${String(month).padStart(2, '0')}.${format}`;
}
+490 -5
View File
@@ -992,6 +992,31 @@
dependencies:
tslib "^2.4.0"
"@fast-csv/format@4.3.5":
version "4.3.5"
resolved "https://registry.yarnpkg.com/@fast-csv/format/-/format-4.3.5.tgz#90d83d1b47b6aaf67be70d6118f84f3e12ee1ff3"
integrity sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==
dependencies:
"@types/node" "^14.0.1"
lodash.escaperegexp "^4.1.2"
lodash.isboolean "^3.0.3"
lodash.isequal "^4.5.0"
lodash.isfunction "^3.0.9"
lodash.isnil "^4.0.0"
"@fast-csv/parse@4.3.6":
version "4.3.6"
resolved "https://registry.yarnpkg.com/@fast-csv/parse/-/parse-4.3.6.tgz#ee47d0640ca0291034c7aa94039a744cfb019264"
integrity sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==
dependencies:
"@types/node" "^14.0.1"
lodash.escaperegexp "^4.1.2"
lodash.groupby "^4.6.0"
lodash.isfunction "^3.0.9"
lodash.isnil "^4.0.0"
lodash.isundefined "^3.0.1"
lodash.uniq "^4.5.0"
"@isaacs/cliui@^8.0.2":
version "8.0.2"
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
@@ -1711,6 +1736,11 @@
dependencies:
undici-types "~7.16.0"
"@types/node@^14.0.1":
version "14.18.63"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.63.tgz#1788fa8da838dbb5f9ea994b834278205db6ca2b"
integrity sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==
"@types/node@^24.10.0":
version "24.13.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-24.13.3.tgz#49f18bd3c647866dcda51a0756c145e14590ce16"
@@ -2009,6 +2039,51 @@ anymatch@^3.1.3, anymatch@~3.1.2:
normalize-path "^3.0.0"
picomatch "^2.0.4"
archiver-utils@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2"
integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==
dependencies:
glob "^7.1.4"
graceful-fs "^4.2.0"
lazystream "^1.0.0"
lodash.defaults "^4.2.0"
lodash.difference "^4.5.0"
lodash.flatten "^4.4.0"
lodash.isplainobject "^4.0.6"
lodash.union "^4.6.0"
normalize-path "^3.0.0"
readable-stream "^2.0.0"
archiver-utils@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-3.0.4.tgz#a0d201f1cf8fce7af3b5a05aea0a337329e96ec7"
integrity sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==
dependencies:
glob "^7.2.3"
graceful-fs "^4.2.0"
lazystream "^1.0.0"
lodash.defaults "^4.2.0"
lodash.difference "^4.5.0"
lodash.flatten "^4.4.0"
lodash.isplainobject "^4.0.6"
lodash.union "^4.6.0"
normalize-path "^3.0.0"
readable-stream "^3.6.0"
archiver@^5.0.0:
version "5.3.2"
resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.2.tgz#99991d5957e53bd0303a392979276ac4ddccf3b0"
integrity sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==
dependencies:
archiver-utils "^2.1.0"
async "^3.2.4"
buffer-crc32 "^0.2.1"
readable-stream "^3.6.0"
readdir-glob "^1.1.2"
tar-stream "^2.2.0"
zip-stream "^4.1.0"
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
@@ -2041,6 +2116,11 @@ astring@^1.9.0:
resolved "https://registry.yarnpkg.com/astring/-/astring-1.9.0.tgz#cc73e6062a7eb03e7d19c22d8b0b3451fd9bfeef"
integrity sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==
async@^3.2.4:
version "3.2.6"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce"
integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -2150,6 +2230,11 @@ balanced-match@^4.0.2:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a"
integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
base64id@2.0.0, base64id@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6"
@@ -2160,11 +2245,38 @@ baseline-browser-mapping@^2.10.42:
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz#7b5d11590ce5acdbe4859443e3c940e81ce8c02d"
integrity sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==
big-integer@^1.6.17:
version "1.6.52"
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85"
integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==
binary-extensions@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
binary@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79"
integrity sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==
dependencies:
buffers "~0.1.1"
chainsaw "~0.1.0"
bl@^4.0.3:
version "4.1.0"
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
dependencies:
buffer "^5.5.0"
inherits "^2.0.4"
readable-stream "^3.4.0"
bluebird@~3.4.1:
version "3.4.7"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3"
integrity sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==
bn.js@^4.0.0:
version "4.12.3"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.3.tgz#2cc2c679188eb35b006f2d0d4710bed8437a769e"
@@ -2198,6 +2310,13 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
brace-expansion@^2.0.1:
version "2.1.4"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.1.4.tgz#589dab11c0018d0366be64cd8bf12c8dbecc8326"
integrity sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==
dependencies:
balanced-match "^1.0.0"
brace-expansion@^2.0.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.1.2.tgz#0bba2271feb7d458b0d31ad13625aaa4754431e2"
@@ -2237,6 +2356,11 @@ bser@2.1.1:
dependencies:
node-int64 "^0.4.0"
buffer-crc32@^0.2.1, buffer-crc32@^0.2.13:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
buffer-equal-constant-time@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
@@ -2247,6 +2371,24 @@ buffer-from@^1.0.0:
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
buffer-indexof-polyfill@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz#d2732135c5999c64b277fcf9b1abe3498254729c"
integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==
buffer@^5.5.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.1.13"
buffers@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
integrity sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==
bytes@^3.1.2, bytes@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
@@ -2288,6 +2430,13 @@ caniuse-lite@^1.0.30001803:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz#78d5d5968a69b7ff81af87a96d7ddc7ea6670b1e"
integrity sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==
chainsaw@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98"
integrity sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==
dependencies:
traverse ">=0.3.0 <0.4"
chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
@@ -2403,6 +2552,16 @@ component-emitter@^1.3.1:
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17"
integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==
compress-commons@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.2.tgz#6542e59cb63e1f46a8b21b0e06f9a32e4c8b06df"
integrity sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==
dependencies:
buffer-crc32 "^0.2.13"
crc32-stream "^4.0.2"
normalize-path "^3.0.0"
readable-stream "^3.6.0"
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -2445,6 +2604,11 @@ core-js-compat@^3.48.0:
dependencies:
browserslist "^4.28.1"
core-util-is@~1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
cors@^2.8.5:
version "2.8.6"
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.6.tgz#ff5dd69bd95e547503820d29aba4f8faf8dfec96"
@@ -2461,6 +2625,19 @@ cors@~2.8.5:
object-assign "^4"
vary "^1"
crc-32@^1.2.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff"
integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==
crc32-stream@^4.0.2:
version "4.0.3"
resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.3.tgz#85dd677eb78fa7cad1ba17cc506a597d41fc6f33"
integrity sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==
dependencies:
crc-32 "^1.2.0"
readable-stream "^3.4.0"
create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
@@ -2491,6 +2668,11 @@ css-what@^6.1.0:
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.2.2.tgz#cdcc8f9b6977719fdfbd1de7aec24abf756b9dea"
integrity sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==
dayjs@^1.8.34:
version "1.11.21"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.21.tgz#57f87562e62de76f3c704bd2b8d522fc33068eb2"
integrity sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==
debug@4, debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.5, debug@^4.3.7, debug@^4.4.0, debug@^4.4.1, debug@^4.4.3, debug@~4.4.1:
version "4.4.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
@@ -2587,6 +2769,13 @@ dunder-proto@^1.0.1:
es-errors "^1.3.0"
gopd "^1.2.0"
duplexer2@~0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
integrity sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==
dependencies:
readable-stream "^2.0.2"
eastasianwidth@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
@@ -2637,6 +2826,13 @@ encoding-sniffer@^0.2.1:
iconv-lite "^0.6.3"
whatwg-encoding "^3.1.1"
end-of-stream@^1.4.1:
version "1.4.5"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c"
integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==
dependencies:
once "^1.4.0"
engine.io-parser@~5.2.1:
version "5.2.3"
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f"
@@ -2753,6 +2949,21 @@ etag@^1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
exceljs@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/exceljs/-/exceljs-4.4.0.tgz#cfb1cb8dcc82c760a9fc9faa9e52dadab66b0156"
integrity sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==
dependencies:
archiver "^5.0.0"
dayjs "^1.8.34"
fast-csv "^4.3.1"
jszip "^3.10.1"
readable-stream "^3.6.0"
saxes "^5.0.1"
tmp "^0.2.0"
unzipper "^0.10.11"
uuid "^8.3.0"
execa@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
@@ -2831,6 +3042,14 @@ express@^5.1.0:
type-is "^2.0.1"
vary "^1.1.2"
fast-csv@^4.3.1:
version "4.3.6"
resolved "https://registry.yarnpkg.com/fast-csv/-/fast-csv-4.3.6.tgz#70349bdd8fe4d66b1130d8c91820b64a21bc4a63"
integrity sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==
dependencies:
"@fast-csv/format" "4.3.5"
"@fast-csv/parse" "4.3.6"
fast-json-stable-stringify@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
@@ -2928,6 +3147,11 @@ fresh@^2.0.0:
resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4"
integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -2938,6 +3162,16 @@ fsevents@^2.3.3, fsevents@~2.3.2:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
fstream@^1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045"
integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==
dependencies:
graceful-fs "^4.1.2"
inherits "~2.0.0"
mkdirp ">=0.5 0"
rimraf "2"
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
@@ -3006,7 +3240,7 @@ glob@^10.5.0:
package-json-from-dist "^1.0.0"
path-scurry "^1.11.1"
glob@^7.1.4:
glob@^7.1.3, glob@^7.1.4, glob@^7.2.3:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@@ -3023,7 +3257,7 @@ gopd@^1.2.0:
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
graceful-fs@^4.2.11:
graceful-fs@^4.1.2, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.2:
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
@@ -3123,11 +3357,21 @@ iconv-lite@^0.7.0, iconv-lite@~0.7.0:
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
ieee754@^1.1.13:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
ignore-by-default@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==
immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
import-in-the-middle@^3.0.0:
version "3.3.1"
resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-3.3.1.tgz#4fe3b46cc1b4573b00bc69343e5f9bbf25679743"
@@ -3158,7 +3402,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@^2.0.1, inherits@~2.0.4:
inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3, inherits@~2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -3224,6 +3468,11 @@ is-stream@^2.0.0:
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@@ -3741,6 +3990,16 @@ jsonwebtoken@^9.0.0:
ms "^2.1.1"
semver "^7.5.4"
jszip@^3.10.1:
version "3.10.1"
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2"
integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==
dependencies:
lie "~3.3.0"
pako "~1.0.2"
readable-stream "~2.3.6"
setimmediate "^1.0.5"
jwa@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.1.tgz#bf8176d1ad0cd72e0f3f58338595a13e110bc804"
@@ -3758,16 +4017,35 @@ jws@^4.0.0, jws@^4.0.1:
jwa "^2.0.1"
safe-buffer "^5.0.1"
lazystream@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638"
integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==
dependencies:
readable-stream "^2.0.5"
leven@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
lie@~3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
dependencies:
immediate "~3.0.5"
lines-and-columns@^1.1.6:
version "1.2.4"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
listenercount@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937"
integrity sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==
locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
@@ -3780,6 +4058,31 @@ lodash.debounce@^4.0.8:
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
lodash.defaults@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==
lodash.difference@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c"
integrity sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==
lodash.escaperegexp@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
integrity sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==
lodash.flatten@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==
lodash.groupby@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1"
integrity sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==
lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
@@ -3790,11 +4093,26 @@ lodash.isboolean@^3.0.3:
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
lodash.isfunction@^3.0.9:
version "3.0.9"
resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051"
integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==
lodash.isinteger@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==
lodash.isnil@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/lodash.isnil/-/lodash.isnil-4.0.0.tgz#49e28cd559013458c814c5479d3c663a21bfaa6c"
integrity sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==
lodash.isnumber@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
@@ -3810,11 +4128,26 @@ lodash.isstring@^4.0.1:
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
lodash.isundefined@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz#23ef3d9535565203a66cefd5b830f848911afb48"
integrity sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==
lodash.once@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
lodash.union@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88"
integrity sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==
lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==
lru-cache@^10.2.0:
version "10.4.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
@@ -3944,6 +4277,13 @@ minimatch@^3.0.4, minimatch@^3.1.1:
dependencies:
brace-expansion "^1.1.7"
minimatch@^5.1.0:
version "5.1.9"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.9.tgz#1293ef15db0098b394540e8f9f744f9fda8dee4b"
integrity sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==
dependencies:
brace-expansion "^2.0.1"
minimatch@^9.0.4:
version "9.0.9"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.9.tgz#9b0cb9fcb78087f6fd7eababe2511c4d3d60574e"
@@ -3951,7 +4291,7 @@ minimatch@^9.0.4:
dependencies:
brace-expansion "^2.0.2"
minimist@^1.2.5:
minimist@^1.2.5, minimist@^1.2.6:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
@@ -3961,6 +4301,13 @@ minimist@^1.2.5:
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b"
integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==
"mkdirp@>=0.5 0":
version "0.5.6"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
dependencies:
minimist "^1.2.6"
module-details-from-path@^1.0.3, module-details-from-path@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.4.tgz#b662fdcd93f6c83d3f25289da0ce81c8d9685b94"
@@ -4103,6 +4450,11 @@ package-json-from-dist@^1.0.0:
resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505"
integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==
pako@~1.0.2:
version "1.0.11"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
parse-json@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
@@ -4219,6 +4571,11 @@ pretty-format@30.4.1:
react-is-18 "npm:react-is@^18.3.1"
react-is-19 "npm:react-is@^19.2.5"
process-nextick-args@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
proxy-addr@^2.0.7:
version "2.0.7"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
@@ -4279,6 +4636,35 @@ react-is@^18.3.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@~2.3.6:
version "2.3.8"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.3"
isarray "~1.0.0"
process-nextick-args "~2.0.0"
safe-buffer "~5.1.1"
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
version "3.6.2"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
dependencies:
inherits "^2.0.3"
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readdir-glob@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.3.tgz#c3d831f51f5e7bfa62fa2ffbe4b508c640f09584"
integrity sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==
dependencies:
minimatch "^5.1.0"
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
@@ -4368,6 +4754,13 @@ resolve@^1.22.11:
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
rimraf@2:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
dependencies:
glob "^7.1.3"
router@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef"
@@ -4379,16 +4772,28 @@ router@^2.2.0:
parseurl "^1.3.3"
path-to-regexp "^8.0.0"
safe-buffer@^5.0.1, safe-buffer@^5.2.1:
safe-buffer@^5.0.1, safe-buffer@^5.2.1, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
"safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
saxes@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d"
integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==
dependencies:
xmlchars "^2.2.0"
semifies@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/semifies/-/semifies-1.0.0.tgz#b69569f32c2ba2ac04f705ea82831364289b2ae2"
@@ -4431,6 +4836,11 @@ serve-static@^2.2.0:
parseurl "^1.3.3"
send "^1.2.0"
setimmediate@^1.0.5, setimmediate@~1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==
setprototypeof@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
@@ -4609,6 +5019,20 @@ string-width@^5.0.1, string-width@^5.1.2:
emoji-regex "^9.2.2"
strip-ansi "^7.0.1"
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
dependencies:
safe-buffer "~5.2.0"
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
dependencies:
safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
@@ -4702,6 +5126,17 @@ synckit@^0.11.8:
dependencies:
"@pkgr/core" "^0.3.6"
tar-stream@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
dependencies:
bl "^4.0.3"
end-of-stream "^1.4.1"
fs-constants "^1.0.0"
inherits "^2.0.3"
readable-stream "^3.1.1"
test-exclude@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"
@@ -4711,6 +5146,11 @@ test-exclude@^6.0.0:
glob "^7.1.4"
minimatch "^3.0.4"
tmp@^0.2.0:
version "0.2.7"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.7.tgz#26f4db11d1601ce8012dcb8a798ece1c06a99059"
integrity sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==
tmpl@1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
@@ -4733,6 +5173,11 @@ touch@^3.1.0:
resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694"
integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==
"traverse@>=0.3.0 <0.4":
version "0.3.9"
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9"
integrity sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==
ts-node@^10.9.1:
version "10.9.2"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f"
@@ -4869,6 +5314,22 @@ unrs-resolver@^1.7.11:
"@unrs/resolver-binding-win32-ia32-msvc" "1.12.2"
"@unrs/resolver-binding-win32-x64-msvc" "1.12.2"
unzipper@^0.10.11:
version "0.10.14"
resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.14.tgz#d2b33c977714da0fbc0f82774ad35470a7c962b1"
integrity sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==
dependencies:
big-integer "^1.6.17"
binary "~0.3.0"
bluebird "~3.4.1"
buffer-indexof-polyfill "~1.0.0"
duplexer2 "~0.1.4"
fstream "^1.0.12"
graceful-fs "^4.2.2"
listenercount "~1.0.1"
readable-stream "~2.3.6"
setimmediate "~1.0.4"
update-browserslist-db@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d"
@@ -4877,6 +5338,16 @@ update-browserslist-db@^1.2.3:
escalade "^3.2.0"
picocolors "^1.1.1"
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
uuid@^8.3.0:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
v8-compile-cache-lib@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
@@ -4978,6 +5449,11 @@ ws@~8.18.3:
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472"
integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==
xmlchars@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
@@ -5015,3 +5491,12 @@ yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zip-stream@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.1.tgz#1337fe974dbaffd2fa9a1ba09662a66932bd7135"
integrity sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==
dependencies:
archiver-utils "^3.0.4"
compress-commons "^4.1.2"
readable-stream "^3.6.0"