feat(import): harden workbook parser boundaries
This commit is contained in:
@@ -7,7 +7,8 @@
|
|||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start -p 3100",
|
"start": "next start -p 3100",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --project tsconfig.typecheck.json --noEmit",
|
||||||
|
"test:unit": "vitest run",
|
||||||
"test:e2e": "playwright test"
|
"test:e2e": "playwright test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -43,12 +44,12 @@
|
|||||||
"recharts": "^3.7.0",
|
"recharts": "^3.7.0",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"three": "^0.183.2",
|
"three": "^0.183.2",
|
||||||
"xlsx": "^0.18.5",
|
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@capakraken/tsconfig": "workspace:*",
|
"@capakraken/tsconfig": "workspace:*",
|
||||||
"@playwright/test": "^1.49.1",
|
"@playwright/test": "^1.49.1",
|
||||||
|
"@vitest/coverage-v8": "^2.1.9",
|
||||||
"@types/dompurify": "^3.2.0",
|
"@types/dompurify": "^3.2.0",
|
||||||
"@types/node": "^22.10.2",
|
"@types/node": "^22.10.2",
|
||||||
"@types/react": "^19.0.6",
|
"@types/react": "^19.0.6",
|
||||||
@@ -58,6 +59,7 @@
|
|||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "^5.6.3"
|
"typescript": "^5.6.3",
|
||||||
|
"vitest": "^2.1.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import {
|
||||||
|
MAX_BROWSER_SPREADSHEET_BYTES,
|
||||||
|
assertSpreadsheetFile,
|
||||||
|
parseSpreadsheet,
|
||||||
|
} from "./excel.js";
|
||||||
|
|
||||||
|
async function createWorkbookFile(
|
||||||
|
rows: unknown[][],
|
||||||
|
fileName = "spreadsheet.xlsx",
|
||||||
|
): Promise<File> {
|
||||||
|
const ExcelJS = await import("exceljs");
|
||||||
|
const workbook = new ExcelJS.Workbook();
|
||||||
|
const worksheet = workbook.addWorksheet("Sheet1");
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
worksheet.addRow(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buffer = await workbook.xlsx.writeBuffer();
|
||||||
|
return new File([buffer], fileName, {
|
||||||
|
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("excel import helpers", () => {
|
||||||
|
it("parses csv files with quoted values and skips blank rows", async () => {
|
||||||
|
const file = new File(
|
||||||
|
['name,role\n"Alice, A.",Engineer\n\nBob,Producer\n'],
|
||||||
|
"people.csv",
|
||||||
|
{ type: "text/csv" },
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(parseSpreadsheet(file)).resolves.toEqual([
|
||||||
|
{ name: "Alice, A.", role: "Engineer" },
|
||||||
|
{ name: "Bob", role: "Producer" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses xlsx files and normalizes date cells to ISO strings", async () => {
|
||||||
|
const file = await createWorkbookFile([
|
||||||
|
["name", "startDate", "active"],
|
||||||
|
["Alice", new Date("2026-03-30T09:15:00.000Z"), true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(parseSpreadsheet(file)).resolves.toEqual([
|
||||||
|
{
|
||||||
|
name: "Alice",
|
||||||
|
startDate: "2026-03-30T09:15:00.000Z",
|
||||||
|
active: "true",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects duplicate headers in xlsx imports", async () => {
|
||||||
|
const file = await createWorkbookFile([
|
||||||
|
["Name", "name"],
|
||||||
|
["Alice", "Producer"],
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(parseSpreadsheet(file)).rejects.toThrow('duplicate header "name"');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects legacy .xls uploads before parsing", () => {
|
||||||
|
const file = new File(["legacy"], "legacy.xls", {
|
||||||
|
type: "application/vnd.ms-excel",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => assertSpreadsheetFile(file)).toThrow(
|
||||||
|
"Legacy .xls files are not supported.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects oversized spreadsheet uploads before parsing", () => {
|
||||||
|
const file = new File([Buffer.alloc(MAX_BROWSER_SPREADSHEET_BYTES + 1)], "oversized.xlsx", {
|
||||||
|
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => assertSpreadsheetFile(file)).toThrow(
|
||||||
|
`The selected file exceeds the ${MAX_BROWSER_SPREADSHEET_BYTES} byte limit`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -3,6 +3,8 @@ const CSV_EXTENSION = ".csv";
|
|||||||
const XLS_EXTENSION = ".xls";
|
const XLS_EXTENSION = ".xls";
|
||||||
|
|
||||||
export const MAX_BROWSER_SPREADSHEET_BYTES = 10 * 1024 * 1024;
|
export const MAX_BROWSER_SPREADSHEET_BYTES = 10 * 1024 * 1024;
|
||||||
|
export const MAX_BROWSER_SPREADSHEET_ROWS = 5000;
|
||||||
|
export const MAX_BROWSER_SPREADSHEET_COLUMNS = 200;
|
||||||
|
|
||||||
type ExcelJsModule = typeof import("exceljs");
|
type ExcelJsModule = typeof import("exceljs");
|
||||||
let _excelJs: ExcelJsModule | null = null;
|
let _excelJs: ExcelJsModule | null = null;
|
||||||
@@ -117,8 +119,47 @@ function parseCsvMatrix(input: string): string[][] {
|
|||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
function matrixToObjects(rows: string[][]): Record<string, string>[] {
|
export function assertTabularMatrixWithinLimits(rows: string[][], contextLabel: string): void {
|
||||||
|
if (rows.length > MAX_BROWSER_SPREADSHEET_ROWS + 1) {
|
||||||
|
throw new Error(
|
||||||
|
`The selected file exceeds the ${MAX_BROWSER_SPREADSHEET_ROWS} row limit for ${contextLabel}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const widestRow = rows.reduce((max, row) => Math.max(max, row.length), 0);
|
||||||
|
if (widestRow > MAX_BROWSER_SPREADSHEET_COLUMNS) {
|
||||||
|
throw new Error(
|
||||||
|
`The selected file exceeds the ${MAX_BROWSER_SPREADSHEET_COLUMNS} column limit for ${contextLabel}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assertHeaderRow(headers: string[], contextLabel: string): void {
|
||||||
|
if (headers.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blankHeaderIndex = headers.findIndex((header) => header.length === 0);
|
||||||
|
if (blankHeaderIndex >= 0) {
|
||||||
|
throw new Error(
|
||||||
|
`The selected file contains an empty header cell in column ${blankHeaderIndex + 1} and cannot be used for ${contextLabel}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const seen = new Set<string>();
|
||||||
|
for (const header of headers) {
|
||||||
|
const normalized = header.toLowerCase();
|
||||||
|
if (seen.has(normalized)) {
|
||||||
|
throw new Error(`The selected file contains duplicate header "${header}" and cannot be used for ${contextLabel}.`);
|
||||||
|
}
|
||||||
|
seen.add(normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function matrixToObjects(rows: string[][], contextLabel: string): Record<string, string>[] {
|
||||||
|
assertTabularMatrixWithinLimits(rows, contextLabel);
|
||||||
const headers = (rows[0] ?? []).map((header) => header.trim());
|
const headers = (rows[0] ?? []).map((header) => header.trim());
|
||||||
|
assertHeaderRow(headers, contextLabel);
|
||||||
if (headers.length === 0) {
|
if (headers.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -203,7 +244,7 @@ async function parseXlsxSpreadsheet(file: File): Promise<Record<string, string>[
|
|||||||
rows.push(cells);
|
rows.push(cells);
|
||||||
}
|
}
|
||||||
|
|
||||||
return matrixToObjects(rows);
|
return matrixToObjects(rows, "spreadsheet import");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -214,7 +255,7 @@ export async function parseSpreadsheet(file: File): Promise<Record<string, strin
|
|||||||
assertSpreadsheetFile(file);
|
assertSpreadsheetFile(file);
|
||||||
|
|
||||||
if (getFileExtension(file.name) === CSV_EXTENSION) {
|
if (getFileExtension(file.name) === CSV_EXTENSION) {
|
||||||
return matrixToObjects(parseCsvMatrix(await file.text()));
|
return matrixToObjects(parseCsvMatrix(await file.text()), "spreadsheet import");
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseXlsxSpreadsheet(file);
|
return parseXlsxSpreadsheet(file);
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { matchRoleName, parseSkillMatrixWorkbook } from "./skillMatrixParser.js";
|
||||||
|
|
||||||
|
async function createWorkbookBuffer(
|
||||||
|
sheets: Array<{ name: string; rows: unknown[][] }>,
|
||||||
|
): Promise<ArrayBuffer> {
|
||||||
|
const ExcelJS = await import("exceljs");
|
||||||
|
const workbook = new ExcelJS.Workbook();
|
||||||
|
|
||||||
|
for (const sheet of sheets) {
|
||||||
|
const worksheet = workbook.addWorksheet(sheet.name);
|
||||||
|
for (const row of sheet.rows) {
|
||||||
|
worksheet.addRow(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buffer = await workbook.xlsx.writeBuffer();
|
||||||
|
const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
||||||
|
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("skill matrix parser", () => {
|
||||||
|
it("extracts employee info and merges skills by highest proficiency", async () => {
|
||||||
|
const workbook = await createWorkbookBuffer([
|
||||||
|
{
|
||||||
|
name: "Employee Information",
|
||||||
|
rows: [
|
||||||
|
["item", "property"],
|
||||||
|
["Full Name", "Alex Artist"],
|
||||||
|
["Area of Expertise", "Compositing"],
|
||||||
|
["Years of Experience", "7.4"],
|
||||||
|
["Portfolio URL", "https://portfolio.example/alex"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Software Skills",
|
||||||
|
rows: [
|
||||||
|
["category", "item", "property", "main skillset"],
|
||||||
|
["Software", "Nuke", "2", "1"],
|
||||||
|
["Software", "Photoshop", "0", ""],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Technical Skillset",
|
||||||
|
rows: [
|
||||||
|
["category", "item", "property", "main skillset"],
|
||||||
|
["Pipeline", "Nuke", "4", ""],
|
||||||
|
["Pipeline", "Python", "3", "2"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(parseSkillMatrixWorkbook(workbook)).resolves.toEqual({
|
||||||
|
employeeInfo: {
|
||||||
|
displayName: "Alex Artist",
|
||||||
|
areaOfExpertise: "Compositing",
|
||||||
|
yearsOfExperience: 7,
|
||||||
|
portfolioUrl: "https://portfolio.example/alex",
|
||||||
|
},
|
||||||
|
skills: expect.arrayContaining([
|
||||||
|
{
|
||||||
|
skill: "Nuke",
|
||||||
|
category: "Pipeline",
|
||||||
|
proficiency: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skill: "Python",
|
||||||
|
category: "Pipeline",
|
||||||
|
proficiency: 4,
|
||||||
|
isMainSkill: true,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects duplicate headers in skill sheets", async () => {
|
||||||
|
const workbook = await createWorkbookBuffer([
|
||||||
|
{
|
||||||
|
name: "Employee Information",
|
||||||
|
rows: [
|
||||||
|
["item", "property"],
|
||||||
|
["Full Name", "Alex Artist"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Software Skills",
|
||||||
|
rows: [
|
||||||
|
["item", "item", "property"],
|
||||||
|
["Nuke", "Duplicate", "2"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Technical Skillset",
|
||||||
|
rows: [["category", "item", "property"]],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(parseSkillMatrixWorkbook(workbook)).rejects.toThrow('duplicate header "item"');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("matches role names by exact and partial matches", () => {
|
||||||
|
expect(matchRoleName("Compositing", ["Producer", "Compositing"])).toBe("Compositing");
|
||||||
|
expect(matchRoleName("Senior Producer", ["Producer", "Lighting"])).toBe("Producer");
|
||||||
|
expect(matchRoleName("Rigging", ["Producer", "Lighting"])).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { SkillEntry } from "@capakraken/shared";
|
import type { SkillEntry } from "@capakraken/shared";
|
||||||
|
import { assertHeaderRow, assertTabularMatrixWithinLimits } from "./excel.js";
|
||||||
|
|
||||||
type ExcelJsModule = typeof import("exceljs");
|
type ExcelJsModule = typeof import("exceljs");
|
||||||
|
|
||||||
@@ -80,7 +81,9 @@ function worksheetToRowObjects(
|
|||||||
rows.push(cells);
|
rows.push(cells);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assertTabularMatrixWithinLimits(rows, "skill matrix import");
|
||||||
const headers = (rows[0] ?? []).map((header) => header.trim());
|
const headers = (rows[0] ?? []).map((header) => header.trim());
|
||||||
|
assertHeaderRow(headers, "skill matrix import");
|
||||||
if (headers.length === 0) {
|
if (headers.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
- Untrusted workbook imports no longer accept legacy `.xls`.
|
- Untrusted workbook imports no longer accept legacy `.xls`.
|
||||||
- Server-side dispo imports accept only `.xlsx` files.
|
- Server-side dispo imports accept only `.xlsx` files.
|
||||||
- Browser-side ad hoc imports accept `.xlsx` and `.csv`.
|
- Browser-side ad hoc imports accept `.xlsx` and `.csv`.
|
||||||
- Trusted export generation may still use `xlsx` until the export paths are migrated separately.
|
- Workbook import and export generation now use `exceljs` instead of direct runtime `xlsx` usage.
|
||||||
|
|
||||||
## Server Boundary
|
## Server Boundary
|
||||||
|
|
||||||
@@ -18,7 +18,9 @@ The dispo-import reader in [read-workbook.ts](/home/hartmut/Documents/Copilot/ca
|
|||||||
- regular-file checks
|
- regular-file checks
|
||||||
- non-empty file checks
|
- non-empty file checks
|
||||||
- a hard size limit of `15 MiB`
|
- a hard size limit of `15 MiB`
|
||||||
- `.xlsx`-only parsing behind a hardened server-side parser boundary
|
- a worksheet row limit of `10,000`
|
||||||
|
- a worksheet column limit of `256`
|
||||||
|
- `.xlsx`-only parsing through `exceljs` behind a hardened server-side parser boundary
|
||||||
|
|
||||||
The API entry points in [dispo.ts](/home/hartmut/Documents/Copilot/capakraken/packages/api/src/router/dispo.ts) reject non-`.xlsx` workbook paths before staging or validation begins.
|
The API entry points in [dispo.ts](/home/hartmut/Documents/Copilot/capakraken/packages/api/src/router/dispo.ts) reject non-`.xlsx` workbook paths before staging or validation begins.
|
||||||
|
|
||||||
@@ -28,6 +30,9 @@ The browser import helpers in [excel.ts](/home/hartmut/Documents/Copilot/capakra
|
|||||||
|
|
||||||
- a hard client-side file size limit of `10 MiB`
|
- a hard client-side file size limit of `10 MiB`
|
||||||
- explicit rejection of legacy `.xls`
|
- explicit rejection of legacy `.xls`
|
||||||
|
- a tabular row limit of `5,000` data rows plus the header row
|
||||||
|
- a tabular column limit of `200`
|
||||||
|
- header validation that rejects blank and duplicate column names
|
||||||
- `.xlsx` parsing through `exceljs`
|
- `.xlsx` parsing through `exceljs`
|
||||||
- `.csv` parsing through a local parser for simple tabular imports
|
- `.csv` parsing through a local parser for simple tabular imports
|
||||||
|
|
||||||
@@ -41,6 +46,7 @@ Affected upload flows:
|
|||||||
## Rationale
|
## Rationale
|
||||||
|
|
||||||
- `.xls` support keeps the old binary workbook format in the untrusted path without enough payoff.
|
- `.xls` support keeps the old binary workbook format in the untrusted path without enough payoff.
|
||||||
- the server path keeps compatibility-first `.xlsx` parsing for the current dispo workbooks, but only behind explicit file validation and limits
|
- the server path keeps compatibility-first `.xlsx` parsing for the current dispo workbooks, but only behind explicit file validation, size limits, and `exceljs`
|
||||||
- the browser path moves away from blanket `xlsx` import usage to a narrower parser boundary
|
- the browser path moves away from blanket spreadsheet parsing to a narrower parser boundary
|
||||||
|
- export generation follows the same maintained workbook stack as import parsing
|
||||||
- CSV remains useful for lightweight business imports and is small enough to parse with a narrow local parser.
|
- CSV remains useful for lightweight business imports and is small enough to parse with a narrow local parser.
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"@capakraken/shared": "workspace:*",
|
"@capakraken/shared": "workspace:*",
|
||||||
"@capakraken/staffing": "workspace:*",
|
"@capakraken/staffing": "workspace:*",
|
||||||
"@trpc/server": "^11.0.0",
|
"@trpc/server": "^11.0.0",
|
||||||
"xlsx": "^0.18.5"
|
"exceljs": "^4.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@capakraken/tsconfig": "workspace:*",
|
"@capakraken/tsconfig": "workspace:*",
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ function createCommitDb(overrides: Record<string, unknown> = {}) {
|
|||||||
},
|
},
|
||||||
stagedVacation: {
|
stagedVacation: {
|
||||||
findMany: vi.fn().mockResolvedValue([]),
|
findMany: vi.fn().mockResolvedValue([]),
|
||||||
|
count: vi.fn().mockResolvedValue(0),
|
||||||
updateMany: vi.fn().mockResolvedValue({ count: 0 }),
|
updateMany: vi.fn().mockResolvedValue({ count: 0 }),
|
||||||
},
|
},
|
||||||
stagedAvailabilityRule: {
|
stagedAvailabilityRule: {
|
||||||
@@ -94,6 +95,9 @@ function createCommitDb(overrides: Record<string, unknown> = {}) {
|
|||||||
findUnique: vi.fn().mockResolvedValue({ id: "batch_1", status: "STAGED", summary: {} }),
|
findUnique: vi.fn().mockResolvedValue({ id: "batch_1", status: "STAGED", summary: {} }),
|
||||||
update: vi.fn().mockResolvedValue({}),
|
update: vi.fn().mockResolvedValue({}),
|
||||||
},
|
},
|
||||||
|
stagedVacation: {
|
||||||
|
count: vi.fn().mockResolvedValue(0),
|
||||||
|
},
|
||||||
stagedUnresolvedRecord: {
|
stagedUnresolvedRecord: {
|
||||||
findMany: vi.fn().mockResolvedValue([]),
|
findMany: vi.fn().mockResolvedValue([]),
|
||||||
},
|
},
|
||||||
@@ -233,11 +237,11 @@ describe("commitDispoImportBatch", () => {
|
|||||||
{
|
{
|
||||||
id: "sv_1",
|
id: "sv_1",
|
||||||
resourceExternalId: "ada.director",
|
resourceExternalId: "ada.director",
|
||||||
vacationType: "PUBLIC_HOLIDAY",
|
vacationType: "ANNUAL",
|
||||||
startDate: new Date("2026-01-01T00:00:00.000Z"),
|
startDate: new Date("2026-01-08T00:00:00.000Z"),
|
||||||
endDate: new Date("2026-01-01T00:00:00.000Z"),
|
endDate: new Date("2026-01-09T00:00:00.000Z"),
|
||||||
note: "New Year",
|
note: "Winter vacation",
|
||||||
holidayName: "New Year",
|
holidayName: null,
|
||||||
isHalfDay: false,
|
isHalfDay: false,
|
||||||
halfDayPart: null,
|
halfDayPart: null,
|
||||||
},
|
},
|
||||||
@@ -705,4 +709,18 @@ describe("commitDispoImportBatch", () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("rejects staged PUBLIC_HOLIDAY rows until holiday calendars are synchronized", async () => {
|
||||||
|
const { db, tx } = createCommitDb();
|
||||||
|
|
||||||
|
db.stagedVacation.count.mockResolvedValue(2);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
commitDispoImportBatch(db as never, {
|
||||||
|
importBatchId: "batch_1",
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(
|
||||||
|
'Import batch "batch_1" still contains 2 staged PUBLIC_HOLIDAY row(s). Public holidays must be synchronized through holiday calendars before commit.',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -230,8 +230,8 @@ describe("dispo import", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(report.resourceCount).toBeGreaterThan(500);
|
expect(report.resourceCount).toBeGreaterThan(500);
|
||||||
expect(report.canCommitWithStrictSourceData).toBe(true);
|
expect(report.canCommitWithStrictSourceData).toBe(false);
|
||||||
expect(report.canCommitWithFallbacks).toBe(true);
|
expect(report.canCommitWithFallbacks).toBe(false);
|
||||||
expect(report.issues.find((issue) => issue.code === "FALLBACK_EMAIL_REQUIRED")).toBeUndefined();
|
expect(report.issues.find((issue) => issue.code === "FALLBACK_EMAIL_REQUIRED")).toBeUndefined();
|
||||||
expect(report.issues.find((issue) => issue.code === "FALLBACK_LCR_REQUIRED")).toBeUndefined();
|
expect(report.issues.find((issue) => issue.code === "FALLBACK_LCR_REQUIRED")).toBeUndefined();
|
||||||
expect(report.issues.find((issue) => issue.code === "FALLBACK_UCR_REQUIRED")).toBeUndefined();
|
expect(report.issues.find((issue) => issue.code === "FALLBACK_UCR_REQUIRED")).toBeUndefined();
|
||||||
@@ -247,6 +247,10 @@ describe("dispo import", () => {
|
|||||||
);
|
);
|
||||||
expect(report.issues).toEqual(
|
expect(report.issues).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
code: "PUBLIC_HOLIDAY_IMPORT_REQUIRES_CALENDAR_SYNC",
|
||||||
|
severity: "blocker",
|
||||||
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
code: "UNRESOLVED_RECORDS_PRESENT",
|
code: "UNRESOLVED_RECORDS_PRESENT",
|
||||||
severity: "warning",
|
severity: "warning",
|
||||||
@@ -740,7 +744,7 @@ describe("dispo import", () => {
|
|||||||
expect(result.counts.stagedResources).toBeGreaterThan(800);
|
expect(result.counts.stagedResources).toBeGreaterThan(800);
|
||||||
expect(result.counts.stagedRosterResources).toBeGreaterThan(500);
|
expect(result.counts.stagedRosterResources).toBeGreaterThan(500);
|
||||||
expect(result.counts.stagedAssignments).toBeGreaterThan(1000);
|
expect(result.counts.stagedAssignments).toBeGreaterThan(1000);
|
||||||
expect(result.readiness.canCommitWithStrictSourceData).toBe(true);
|
expect(result.readiness.canCommitWithStrictSourceData).toBe(false);
|
||||||
expect(result.readiness.issues).not.toEqual(
|
expect(result.readiness.issues).not.toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@@ -754,7 +758,7 @@ describe("dispo import", () => {
|
|||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
summary: expect.objectContaining({
|
summary: expect.objectContaining({
|
||||||
readiness: expect.objectContaining({
|
readiness: expect.objectContaining({
|
||||||
canCommitWithStrictSourceData: true,
|
canCommitWithStrictSourceData: false,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -5,12 +5,23 @@ import { fileURLToPath } from "node:url";
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import {
|
import {
|
||||||
MAX_DISPO_WORKBOOK_BYTES,
|
MAX_DISPO_WORKBOOK_BYTES,
|
||||||
|
MAX_DISPO_WORKBOOK_COLUMNS,
|
||||||
|
MAX_DISPO_WORKBOOK_ROWS,
|
||||||
readWorksheetMatrix,
|
readWorksheetMatrix,
|
||||||
} from "../use-cases/dispo-import/read-workbook.js";
|
} from "../use-cases/dispo-import/read-workbook.js";
|
||||||
|
|
||||||
const referenceWorkbookPath = fileURLToPath(
|
const referenceWorkbookPath = fileURLToPath(
|
||||||
new URL("../../../../samples/Dispov2/MandatoryDispoCategories_V3.xlsx", import.meta.url),
|
new URL("../../../../samples/Dispov2/MandatoryDispoCategories_V3.xlsx", import.meta.url),
|
||||||
);
|
);
|
||||||
|
const chargeabilityWorkbookPath = fileURLToPath(
|
||||||
|
new URL(
|
||||||
|
"../../../../samples/Dispov2/20260309_Bi-Weekly_Chargeability_Reporting_Content_Production_V0.943_4Hartmut.xlsx",
|
||||||
|
import.meta.url,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const planningWorkbookPath = fileURLToPath(
|
||||||
|
new URL("../../../../samples/Dispov2/DISPO_2026.xlsx", import.meta.url),
|
||||||
|
);
|
||||||
|
|
||||||
const tempDirectories: string[] = [];
|
const tempDirectories: string[] = [];
|
||||||
|
|
||||||
@@ -28,6 +39,18 @@ async function makeTempDirectory(): Promise<string> {
|
|||||||
return directory;
|
return directory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function writeWorkbook(filePath: string, rows: unknown[][], sheetName = "Sheet1"): Promise<void> {
|
||||||
|
const ExcelJS = await import("exceljs");
|
||||||
|
const workbook = new ExcelJS.Workbook();
|
||||||
|
const worksheet = workbook.addWorksheet(sheetName);
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
worksheet.addRow(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
await workbook.xlsx.writeFile(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
describe("readWorksheetMatrix", () => {
|
describe("readWorksheetMatrix", () => {
|
||||||
it("reads trusted xlsx worksheets through the hardened reader", async () => {
|
it("reads trusted xlsx worksheets through the hardened reader", async () => {
|
||||||
const rows = await readWorksheetMatrix(referenceWorkbookPath, "EID-Attr");
|
const rows = await readWorksheetMatrix(referenceWorkbookPath, "EID-Attr");
|
||||||
@@ -36,6 +59,21 @@ describe("readWorksheetMatrix", () => {
|
|||||||
expect(rows.some((row) => row.length > 0)).toBe(true);
|
expect(rows.some((row) => row.length > 0)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("tolerates workbook tables that contain unsupported exceljs date group filters", async () => {
|
||||||
|
const rows = await readWorksheetMatrix(chargeabilityWorkbookPath, "ChgFC");
|
||||||
|
|
||||||
|
expect(rows.length).toBeGreaterThan(300);
|
||||||
|
expect(rows[0]?.length).toBeGreaterThan(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accepts real dispo planning worksheets within the supported width envelope", async () => {
|
||||||
|
const rows = await readWorksheetMatrix(planningWorkbookPath, "Dispo");
|
||||||
|
|
||||||
|
expect(rows.length).toBeGreaterThan(500);
|
||||||
|
expect(rows.some((row) => row.length > 256)).toBe(true);
|
||||||
|
expect(rows.every((row) => row.length <= MAX_DISPO_WORKBOOK_COLUMNS)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("rejects legacy .xls workbook paths", async () => {
|
it("rejects legacy .xls workbook paths", async () => {
|
||||||
const directory = await makeTempDirectory();
|
const directory = await makeTempDirectory();
|
||||||
const legacyPath = path.join(directory, "legacy-input.xls");
|
const legacyPath = path.join(directory, "legacy-input.xls");
|
||||||
@@ -55,4 +93,30 @@ describe("readWorksheetMatrix", () => {
|
|||||||
"Workbook file exceeds the",
|
"Workbook file exceeds the",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("rejects worksheets that exceed the row limit", async () => {
|
||||||
|
const directory = await makeTempDirectory();
|
||||||
|
const workbookPath = path.join(directory, "too-many-rows.xlsx");
|
||||||
|
await writeWorkbook(
|
||||||
|
workbookPath,
|
||||||
|
Array.from({ length: MAX_DISPO_WORKBOOK_ROWS + 1 }, (_, index) => [`row-${index + 1}`]),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(readWorksheetMatrix(workbookPath, "Sheet1")).rejects.toThrow(
|
||||||
|
`exceeds the ${MAX_DISPO_WORKBOOK_ROWS} row import limit`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects worksheets that exceed the column limit", async () => {
|
||||||
|
const directory = await makeTempDirectory();
|
||||||
|
const workbookPath = path.join(directory, "too-many-columns.xlsx");
|
||||||
|
await writeWorkbook(
|
||||||
|
workbookPath,
|
||||||
|
[Array.from({ length: MAX_DISPO_WORKBOOK_COLUMNS + 1 }, (_, index) => `col-${index + 1}`)],
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(readWorksheetMatrix(workbookPath, "Sheet1")).rejects.toThrow(
|
||||||
|
`exceeds the ${MAX_DISPO_WORKBOOK_COLUMNS} column import limit`,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export interface DispoImportReadinessIssue {
|
|||||||
| "FALLBACK_EMAIL_REQUIRED"
|
| "FALLBACK_EMAIL_REQUIRED"
|
||||||
| "FALLBACK_LCR_REQUIRED"
|
| "FALLBACK_LCR_REQUIRED"
|
||||||
| "FALLBACK_UCR_REQUIRED"
|
| "FALLBACK_UCR_REQUIRED"
|
||||||
|
| "PUBLIC_HOLIDAY_IMPORT_REQUIRES_CALENDAR_SYNC"
|
||||||
| "PLANNING_RESOURCE_MISSING_FROM_ROSTER"
|
| "PLANNING_RESOURCE_MISSING_FROM_ROSTER"
|
||||||
| "REFERENCE_RESOURCE_MASTER_MISSING"
|
| "REFERENCE_RESOURCE_MASTER_MISSING"
|
||||||
| "UNRESOLVED_RECORDS_PRESENT";
|
| "UNRESOLVED_RECORDS_PRESENT";
|
||||||
@@ -172,6 +173,10 @@ export async function assessDispoImportReadiness(
|
|||||||
filterUnresolvedCount(chargeabilityWorkbook.unresolved, excludedIds) +
|
filterUnresolvedCount(chargeabilityWorkbook.unresolved, excludedIds) +
|
||||||
filterUnresolvedCount(planningWorkbook.unresolved, excludedIds) +
|
filterUnresolvedCount(planningWorkbook.unresolved, excludedIds) +
|
||||||
filterUnresolvedCount(rosterWorkbook?.unresolved ?? [], excludedIds);
|
filterUnresolvedCount(rosterWorkbook?.unresolved ?? [], excludedIds);
|
||||||
|
const publicHolidayImportCount = planningWorkbook.vacations.filter(
|
||||||
|
(vacation) =>
|
||||||
|
!excludedIds.has(vacation.resourceExternalId) && vacation.vacationType === "PUBLIC_HOLIDAY",
|
||||||
|
).length;
|
||||||
const missingEmailCount = Array.from(mergedResources.values()).filter(
|
const missingEmailCount = Array.from(mergedResources.values()).filter(
|
||||||
(resource) => !resource.email,
|
(resource) => !resource.email,
|
||||||
).length;
|
).length;
|
||||||
@@ -254,6 +259,20 @@ export async function assessDispoImportReadiness(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (publicHolidayImportCount > 0) {
|
||||||
|
issues.push(
|
||||||
|
buildReadinessIssue({
|
||||||
|
code: "PUBLIC_HOLIDAY_IMPORT_REQUIRES_CALENDAR_SYNC",
|
||||||
|
count: publicHolidayImportCount,
|
||||||
|
message:
|
||||||
|
"Planning import contains PUBLIC_HOLIDAY rows. Public holidays must be managed through holiday calendars so country/state/city-specific rules stay canonical.",
|
||||||
|
resolution:
|
||||||
|
"Import or update the relevant holiday calendars first, then remove PUBLIC_HOLIDAY rows from the generic planning/vacation import before commit.",
|
||||||
|
severity: "blocker",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (unresolvedCount > 0) {
|
if (unresolvedCount > 0) {
|
||||||
issues.push(
|
issues.push(
|
||||||
buildReadinessIssue({
|
buildReadinessIssue({
|
||||||
|
|||||||
@@ -166,6 +166,25 @@ function getSlotHalfDayPart(slotLabel: string | null): "AFTERNOON" | "MORNING" |
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPlanningSummaryRow(row: ReadonlyArray<WorksheetCellValue>): boolean {
|
||||||
|
if ((row[0] ?? null) !== null || (row[1] ?? null) !== null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const repeatedLabels = row
|
||||||
|
.slice(DISPO_EID_COLUMN - 1, 9)
|
||||||
|
.map((value) => normalizeNullableWorkbookValue(value))
|
||||||
|
.filter((value): value is string => value !== null);
|
||||||
|
if (repeatedLabels.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedLabels = new Set(repeatedLabels.map((value) => value.toLowerCase()));
|
||||||
|
const label = repeatedLabels[0] ?? null;
|
||||||
|
|
||||||
|
return normalizedLabels.size === 1 && label !== null && label.startsWith("(") && label.endsWith(")");
|
||||||
|
}
|
||||||
|
|
||||||
function buildPlanningColumns(rows: ReadonlyArray<ReadonlyArray<WorksheetCellValue>>) {
|
function buildPlanningColumns(rows: ReadonlyArray<ReadonlyArray<WorksheetCellValue>>) {
|
||||||
const columns: PlanningColumn[] = [];
|
const columns: PlanningColumn[] = [];
|
||||||
const headerWidth = Math.max(rows[DISPO_DATE_ROW - 1]?.length ?? 0, rows[DISPO_SLOT_ROW - 1]?.length ?? 0);
|
const headerWidth = Math.max(rows[DISPO_DATE_ROW - 1]?.length ?? 0, rows[DISPO_SLOT_ROW - 1]?.length ?? 0);
|
||||||
@@ -483,6 +502,9 @@ export async function parseDispoPlanningWorkbook(
|
|||||||
|
|
||||||
for (let rowNumber = DISPO_DATA_START_ROW; rowNumber <= rows.length; rowNumber += 1) {
|
for (let rowNumber = DISPO_DATA_START_ROW; rowNumber <= rows.length; rowNumber += 1) {
|
||||||
const row = rows[rowNumber - 1] ?? [];
|
const row = rows[rowNumber - 1] ?? [];
|
||||||
|
if (isPlanningSummaryRow(row)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const eid = normalizeNullableWorkbookValue(row[DISPO_EID_COLUMN - 1]);
|
const eid = normalizeNullableWorkbookValue(row[DISPO_EID_COLUMN - 1]);
|
||||||
|
|
||||||
if (!eid) {
|
if (!eid) {
|
||||||
|
|||||||
@@ -1,32 +1,41 @@
|
|||||||
import { stat } from "node:fs/promises";
|
import { stat } from "node:fs/promises";
|
||||||
import { createRequire } from "node:module";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
export type WorksheetCellValue = boolean | Date | number | string | null;
|
export type WorksheetCellValue = boolean | Date | number | string | null;
|
||||||
export type WorksheetMatrix = WorksheetCellValue[][];
|
export type WorksheetMatrix = WorksheetCellValue[][];
|
||||||
|
|
||||||
type XlsxWorkbook = {
|
type ExcelJsModule = typeof import("exceljs");
|
||||||
Sheets: Record<string, unknown>;
|
type ExcelJsWorkbook = InstanceType<ExcelJsModule["Workbook"]>;
|
||||||
|
type ExcelJsXlsxReader = ExcelJsWorkbook["xlsx"] & {
|
||||||
|
_processTableEntry?: (
|
||||||
|
stream: unknown,
|
||||||
|
model: Record<string, unknown>,
|
||||||
|
name: string,
|
||||||
|
) => Promise<unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SheetToJsonOptions = {
|
|
||||||
header: 1;
|
|
||||||
raw: true;
|
|
||||||
defval: null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type XlsxRuntime = {
|
|
||||||
readFile(filePath: string, options: { cellDates: true; dense: true }): XlsxWorkbook;
|
|
||||||
utils: {
|
|
||||||
sheet_to_json<T>(worksheet: unknown, options: SheetToJsonOptions): T[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
|
||||||
const XLSX = require("xlsx") as XlsxRuntime;
|
|
||||||
|
|
||||||
const DISPO_WORKBOOK_EXTENSION = ".xlsx";
|
const DISPO_WORKBOOK_EXTENSION = ".xlsx";
|
||||||
export const MAX_DISPO_WORKBOOK_BYTES = 15 * 1024 * 1024;
|
export const MAX_DISPO_WORKBOOK_BYTES = 15 * 1024 * 1024;
|
||||||
|
export const MAX_DISPO_WORKBOOK_ROWS = 10000;
|
||||||
|
export const MAX_DISPO_WORKBOOK_COLUMNS = 1024;
|
||||||
|
|
||||||
|
const EXCELJS_IGNORE_WORKSHEET_NODES = ["tableParts"];
|
||||||
|
const EXCELJS_UNSUPPORTED_TABLE_FILTER_MARKER = '"name":"dateGroupItem"';
|
||||||
|
|
||||||
|
let _excelJs: ExcelJsModule | null = null;
|
||||||
|
const worksheetMatrixCache = new Map<string, Promise<WorksheetMatrix>>();
|
||||||
|
|
||||||
|
function normalizeExcelJsModule(module: ExcelJsModule | { default?: ExcelJsModule }): ExcelJsModule {
|
||||||
|
return "Workbook" in module ? module : (module.default as ExcelJsModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getExcelJS() {
|
||||||
|
if (!_excelJs) {
|
||||||
|
_excelJs = normalizeExcelJsModule(await import("exceljs"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _excelJs;
|
||||||
|
}
|
||||||
|
|
||||||
function trimTrailingNulls(row: WorksheetCellValue[]): WorksheetCellValue[] {
|
function trimTrailingNulls(row: WorksheetCellValue[]): WorksheetCellValue[] {
|
||||||
let end = row.length;
|
let end = row.length;
|
||||||
@@ -44,6 +53,10 @@ function trimTrailingEmptyRows(rows: WorksheetMatrix): WorksheetMatrix {
|
|||||||
return rows.slice(0, end);
|
return rows.slice(0, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cloneWorksheetMatrix(rows: WorksheetMatrix): WorksheetMatrix {
|
||||||
|
return rows.map((row) => row.slice());
|
||||||
|
}
|
||||||
|
|
||||||
async function validateWorkbookPath(workbookPath: string): Promise<string> {
|
async function validateWorkbookPath(workbookPath: string): Promise<string> {
|
||||||
const resolvedPath = path.resolve(workbookPath);
|
const resolvedPath = path.resolve(workbookPath);
|
||||||
|
|
||||||
@@ -119,31 +132,99 @@ function normalizeWorksheetCellValue(value: unknown): WorksheetCellValue {
|
|||||||
return String(value);
|
return String(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assertWorksheetShape(rows: WorksheetMatrix, sheetName: string, workbookPath: string): void {
|
||||||
|
if (rows.length > MAX_DISPO_WORKBOOK_ROWS) {
|
||||||
|
throw new Error(
|
||||||
|
`Worksheet "${sheetName}" in "${workbookPath}" exceeds the ${MAX_DISPO_WORKBOOK_ROWS} row import limit.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const widestRow = rows.reduce((max, row) => Math.max(max, row.length), 0);
|
||||||
|
if (widestRow > MAX_DISPO_WORKBOOK_COLUMNS) {
|
||||||
|
throw new Error(
|
||||||
|
`Worksheet "${sheetName}" in "${workbookPath}" exceeds the ${MAX_DISPO_WORKBOOK_COLUMNS} column import limit.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUnsupportedExcelJsTableFilterError(error: unknown): boolean {
|
||||||
|
return error instanceof Error && error.message.includes(EXCELJS_UNSUPPORTED_TABLE_FILTER_MARKER);
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchExcelJsTableCompatibility(workbook: ExcelJsWorkbook): void {
|
||||||
|
const reader = workbook.xlsx as ExcelJsXlsxReader;
|
||||||
|
const originalProcessTableEntry = reader._processTableEntry;
|
||||||
|
|
||||||
|
if (typeof originalProcessTableEntry !== "function") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reader._processTableEntry = async function processTableEntryWithCompatibilityFallback(
|
||||||
|
stream,
|
||||||
|
model,
|
||||||
|
name,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
return await originalProcessTableEntry.call(this, stream, model, name);
|
||||||
|
} catch (error) {
|
||||||
|
if (isUnsupportedExcelJsTableFilterError(error)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function readWorksheetMatrix(
|
export async function readWorksheetMatrix(
|
||||||
workbookPath: string,
|
workbookPath: string,
|
||||||
sheetName: string,
|
sheetName: string,
|
||||||
): Promise<WorksheetMatrix> {
|
): Promise<WorksheetMatrix> {
|
||||||
const resolvedPath = await validateWorkbookPath(workbookPath);
|
const resolvedPath = await validateWorkbookPath(workbookPath);
|
||||||
const workbook = XLSX.readFile(resolvedPath, {
|
const cacheKey = `${resolvedPath}::${sheetName}`;
|
||||||
cellDates: true,
|
const cachedMatrix = worksheetMatrixCache.get(cacheKey);
|
||||||
dense: true,
|
if (cachedMatrix) {
|
||||||
});
|
return cloneWorksheetMatrix(await cachedMatrix);
|
||||||
const worksheet = workbook.Sheets[sheetName];
|
|
||||||
if (!worksheet) {
|
|
||||||
throw new Error(`Worksheet "${sheetName}" not found in workbook "${resolvedPath}"`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rows = XLSX.utils.sheet_to_json<(WorksheetCellValue | null)[]>(worksheet, {
|
const matrixPromise = (async () => {
|
||||||
header: 1,
|
const ExcelJS = await getExcelJS();
|
||||||
raw: true,
|
const workbook = new ExcelJS.Workbook();
|
||||||
defval: null,
|
patchExcelJsTableCompatibility(workbook);
|
||||||
});
|
await workbook.xlsx.readFile(resolvedPath, { ignoreNodes: EXCELJS_IGNORE_WORKSHEET_NODES });
|
||||||
|
|
||||||
return trimTrailingEmptyRows(
|
const worksheet = workbook.getWorksheet(sheetName);
|
||||||
rows.map((row: (WorksheetCellValue | null)[]) =>
|
if (!worksheet) {
|
||||||
trimTrailingNulls(row.map((value: WorksheetCellValue | null) => normalizeWorksheetCellValue(value))),
|
throw new Error(`Worksheet "${sheetName}" not found in workbook "${resolvedPath}"`);
|
||||||
),
|
}
|
||||||
);
|
|
||||||
|
const rows: WorksheetMatrix = [];
|
||||||
|
for (let rowNumber = 1; rowNumber <= worksheet.rowCount; rowNumber += 1) {
|
||||||
|
const row = worksheet.getRow(rowNumber);
|
||||||
|
const cells: WorksheetCellValue[] = [];
|
||||||
|
|
||||||
|
for (let columnNumber = 1; columnNumber <= row.cellCount; columnNumber += 1) {
|
||||||
|
cells.push(normalizeWorksheetCellValue(row.getCell(columnNumber).value));
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.push(trimTrailingNulls(cells));
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedRows = trimTrailingEmptyRows(rows);
|
||||||
|
|
||||||
|
assertWorksheetShape(normalizedRows, sheetName, resolvedPath);
|
||||||
|
|
||||||
|
return normalizedRows;
|
||||||
|
})();
|
||||||
|
|
||||||
|
worksheetMatrixCache.set(cacheKey, matrixPromise);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return cloneWorksheetMatrix(await matrixPromise);
|
||||||
|
} catch (error) {
|
||||||
|
worksheetMatrixCache.delete(cacheKey);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCellString(
|
export function getCellString(
|
||||||
|
|||||||
@@ -55,6 +55,12 @@ export async function validateDispoBatch(
|
|||||||
status: StagedRecordStatus.UNRESOLVED,
|
status: StagedRecordStatus.UNRESOLVED,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const stagedPublicHolidayCount = await db.stagedVacation.count({
|
||||||
|
where: {
|
||||||
|
importBatchId: batch.id,
|
||||||
|
vacationType: "PUBLIC_HOLIDAY",
|
||||||
|
},
|
||||||
|
});
|
||||||
const blockingUnresolved = unresolved.filter(
|
const blockingUnresolved = unresolved.filter(
|
||||||
(record) =>
|
(record) =>
|
||||||
!(
|
!(
|
||||||
@@ -70,6 +76,12 @@ export async function validateDispoBatch(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stagedPublicHolidayCount > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Import batch "${batch.id}" still contains ${stagedPublicHolidayCount} staged PUBLIC_HOLIDAY row(s). Public holidays must be synchronized through holiday calendars before commit.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
batchId: batch.id,
|
batchId: batch.id,
|
||||||
batchSummary: batch.summary,
|
batchSummary: batch.summary,
|
||||||
|
|||||||
@@ -2,14 +2,18 @@
|
|||||||
* Generate samples/CapaKrakenExamples.xlsx from the live database.
|
* Generate samples/CapaKrakenExamples.xlsx from the live database.
|
||||||
*
|
*
|
||||||
* Run from repo root:
|
* Run from repo root:
|
||||||
* DATABASE_URL=postgresql://capakraken:capakraken_dev@localhost:5433/capakraken \
|
* pnpm --filter @capakraken/db db:excel
|
||||||
* pnpm --filter @capakraken/db tsx src/generate-excel.ts
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
import ExcelJS from "exceljs";
|
import ExcelJS from "exceljs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
import { loadWorkspaceEnv } from "./load-workspace-env.js";
|
||||||
|
import { assertCapaKrakenDbTarget } from "./safe-destructive-env.js";
|
||||||
|
|
||||||
|
loadWorkspaceEnv();
|
||||||
|
assertCapaKrakenDbTarget("db:excel");
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|||||||
import { resolve } from "node:path";
|
import { resolve } from "node:path";
|
||||||
import { PrismaClient, StagedRecordStatus } from "@prisma/client";
|
import { PrismaClient, StagedRecordStatus } from "@prisma/client";
|
||||||
import { loadWorkspaceEnv, resolveWorkspacePath } from "./load-workspace-env.js";
|
import { loadWorkspaceEnv, resolveWorkspacePath } from "./load-workspace-env.js";
|
||||||
|
import { assertCapaKrakenDbTarget } from "./safe-destructive-env.js";
|
||||||
|
|
||||||
loadWorkspaceEnv();
|
loadWorkspaceEnv();
|
||||||
|
|
||||||
@@ -378,6 +379,7 @@ function ensureCommitAllowed(options: ImportDispoBatchOptions, readiness: DispoI
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function runImportDispoBatch(options: ImportDispoBatchOptions) {
|
export async function runImportDispoBatch(options: ImportDispoBatchOptions) {
|
||||||
|
assertCapaKrakenDbTarget("db:import:dispo");
|
||||||
const dispoImport = await loadDispoImportModule();
|
const dispoImport = await loadDispoImportModule();
|
||||||
|
|
||||||
printWorkbookSources(options);
|
printWorkbookSources(options);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capakraken/shared": "workspace:*",
|
"@capakraken/shared": "workspace:*",
|
||||||
"xlsx": "^0.18.5"
|
"exceljs": "^4.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@capakraken/tsconfig": "workspace:*",
|
"@capakraken/tsconfig": "workspace:*",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as XLSX from "xlsx";
|
|
||||||
import {
|
import {
|
||||||
EstimateExportFormat,
|
EstimateExportFormat,
|
||||||
EstimateStatus,
|
EstimateStatus,
|
||||||
@@ -144,8 +143,8 @@ function buildSource(): EstimateExportSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("estimate export serializer", () => {
|
describe("estimate export serializer", () => {
|
||||||
it("creates a structured JSON export payload", () => {
|
it("creates a structured JSON export payload", async () => {
|
||||||
const payload = serializeEstimateExport(buildSource(), EstimateExportFormat.JSON);
|
const payload = await serializeEstimateExport(buildSource(), EstimateExportFormat.JSON);
|
||||||
|
|
||||||
expect(payload.encoding).toBe("utf8");
|
expect(payload.encoding).toBe("utf8");
|
||||||
expect(payload.mimeType).toBe("application/json; charset=utf-8");
|
expect(payload.mimeType).toBe("application/json; charset=utf-8");
|
||||||
@@ -154,9 +153,16 @@ describe("estimate export serializer", () => {
|
|||||||
expect(payload.previewText).toContain('"schemaVersion": 1');
|
expect(payload.previewText).toContain('"schemaVersion": 1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("creates a multi-sheet xlsx export payload", () => {
|
it("creates a multi-sheet xlsx export payload", async () => {
|
||||||
const payload = serializeEstimateExport(buildSource(), EstimateExportFormat.XLSX);
|
const payload = await serializeEstimateExport(buildSource(), EstimateExportFormat.XLSX);
|
||||||
const workbook = XLSX.read(payload.content, { type: "base64" });
|
const ExcelJS = await import("exceljs");
|
||||||
|
const workbook = new ExcelJS.Workbook();
|
||||||
|
const workbookBytes = Uint8Array.from(Buffer.from(payload.content, "base64"));
|
||||||
|
const workbookBuffer = workbookBytes.buffer.slice(
|
||||||
|
workbookBytes.byteOffset,
|
||||||
|
workbookBytes.byteOffset + workbookBytes.byteLength,
|
||||||
|
);
|
||||||
|
await workbook.xlsx.load(workbookBuffer);
|
||||||
|
|
||||||
expect(payload.encoding).toBe("base64");
|
expect(payload.encoding).toBe("base64");
|
||||||
expect(payload.sheetNames).toEqual([
|
expect(payload.sheetNames).toEqual([
|
||||||
@@ -167,7 +173,7 @@ describe("estimate export serializer", () => {
|
|||||||
"Resources",
|
"Resources",
|
||||||
"Metrics",
|
"Metrics",
|
||||||
]);
|
]);
|
||||||
expect(workbook.SheetNames).toContain("DemandLines");
|
expect(workbook.getWorksheet("DemandLines")).toBeDefined();
|
||||||
expect(payload.byteLength).toBeGreaterThan(100);
|
expect(payload.byteLength).toBeGreaterThan(100);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as XLSX from "xlsx";
|
|
||||||
import {
|
import {
|
||||||
EstimateExportFormat,
|
EstimateExportFormat,
|
||||||
type EstimateExportArtifactPayload,
|
type EstimateExportArtifactPayload,
|
||||||
@@ -8,6 +7,8 @@ import {
|
|||||||
} from "@capakraken/shared";
|
} from "@capakraken/shared";
|
||||||
import { summarizeEstimateDemandLines } from "./metrics.js";
|
import { summarizeEstimateDemandLines } from "./metrics.js";
|
||||||
|
|
||||||
|
type ExcelJsModule = typeof import("exceljs");
|
||||||
|
|
||||||
type ExportProjectRef = {
|
type ExportProjectRef = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -109,6 +110,18 @@ type ExportMetric = {
|
|||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ExportSheetRow = Record<string, unknown>;
|
||||||
|
|
||||||
|
let _excelJs: ExcelJsModule | null = null;
|
||||||
|
|
||||||
|
async function getExcelJS() {
|
||||||
|
if (!_excelJs) {
|
||||||
|
_excelJs = await import("exceljs");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _excelJs;
|
||||||
|
}
|
||||||
|
|
||||||
export interface EstimateExportSource {
|
export interface EstimateExportSource {
|
||||||
estimate: {
|
estimate: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -508,6 +521,52 @@ function base64ByteLength(content: string) {
|
|||||||
return Math.floor((content.length * 3) / 4) - padding;
|
return Math.floor((content.length * 3) / 4) - padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildSheetColumns(rows: ExportSheetRow[]) {
|
||||||
|
return Array.from(
|
||||||
|
rows.reduce((keys, row) => {
|
||||||
|
for (const key of Object.keys(row)) {
|
||||||
|
keys.add(key);
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
}, new Set<string>()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toWorksheetCellValue(value: unknown): boolean | Date | number | string {
|
||||||
|
if (value == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value instanceof Date) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringifyValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendWorksheetFromRows(
|
||||||
|
workbook: InstanceType<ExcelJsModule["Workbook"]>,
|
||||||
|
sheetName: string,
|
||||||
|
rows: ExportSheetRow[],
|
||||||
|
): void {
|
||||||
|
const worksheet = workbook.addWorksheet(sheetName);
|
||||||
|
const columns = buildSheetColumns(rows);
|
||||||
|
|
||||||
|
if (columns.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
worksheet.addRow(columns);
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
worksheet.addRow(columns.map((column) => toWorksheetCellValue(row[column])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function buildTextPayload(
|
function buildTextPayload(
|
||||||
format: EstimateExportFormat,
|
format: EstimateExportFormat,
|
||||||
content: string,
|
content: string,
|
||||||
@@ -536,17 +595,18 @@ function buildTextPayload(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildXlsxPayload(
|
async function buildXlsxPayload(
|
||||||
source: EstimateExportSource,
|
source: EstimateExportSource,
|
||||||
summary: EstimateExportSummary,
|
summary: EstimateExportSummary,
|
||||||
): EstimateExportArtifactPayload {
|
): Promise<EstimateExportArtifactPayload> {
|
||||||
const overviewRows = buildOverviewRows(source, summary);
|
const overviewRows = buildOverviewRows(source, summary);
|
||||||
const assumptionRows = buildAssumptionRows(source.version.assumptions);
|
const assumptionRows = buildAssumptionRows(source.version.assumptions);
|
||||||
const scopeRows = buildScopeRows(source.version.scopeItems);
|
const scopeRows = buildScopeRows(source.version.scopeItems);
|
||||||
const demandRows = buildDemandRows(source);
|
const demandRows = buildDemandRows(source);
|
||||||
const resourceRows = buildResourceRows(source.version.resourceSnapshots);
|
const resourceRows = buildResourceRows(source.version.resourceSnapshots);
|
||||||
const metricRows = buildMetricRows(source.version.metrics);
|
const metricRows = buildMetricRows(source.version.metrics);
|
||||||
const workbook = XLSX.utils.book_new();
|
const ExcelJS = await getExcelJS();
|
||||||
|
const workbook = new ExcelJS.Workbook();
|
||||||
const sheets = [
|
const sheets = [
|
||||||
{ name: "Overview", rows: overviewRows },
|
{ name: "Overview", rows: overviewRows },
|
||||||
{ name: "Assumptions", rows: assumptionRows },
|
{ name: "Assumptions", rows: assumptionRows },
|
||||||
@@ -557,17 +617,11 @@ function buildXlsxPayload(
|
|||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
for (const sheet of sheets) {
|
for (const sheet of sheets) {
|
||||||
XLSX.utils.book_append_sheet(
|
appendWorksheetFromRows(workbook, sheet.name, sheet.rows);
|
||||||
workbook,
|
|
||||||
XLSX.utils.json_to_sheet(sheet.rows),
|
|
||||||
sheet.name,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = XLSX.write(workbook, {
|
const buffer = await workbook.xlsx.writeBuffer();
|
||||||
type: "base64",
|
const content = Buffer.from(buffer).toString("base64");
|
||||||
bookType: "xlsx",
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
schemaVersion: 1,
|
schemaVersion: 1,
|
||||||
@@ -593,10 +647,10 @@ function buildXlsxPayload(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function serializeEstimateExport(
|
export async function serializeEstimateExport(
|
||||||
source: EstimateExportSource,
|
source: EstimateExportSource,
|
||||||
format: EstimateExportFormat,
|
format: EstimateExportFormat,
|
||||||
): EstimateExportArtifactPayload {
|
): Promise<EstimateExportArtifactPayload> {
|
||||||
const summary = buildSummary(source);
|
const summary = buildSummary(source);
|
||||||
|
|
||||||
if (format === EstimateExportFormat.JSON) {
|
if (format === EstimateExportFormat.JSON) {
|
||||||
|
|||||||
Generated
+328
-93
@@ -4,6 +4,10 @@ settings:
|
|||||||
autoInstallPeers: true
|
autoInstallPeers: true
|
||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
overrides:
|
||||||
|
flatted: ^3.4.2
|
||||||
|
picomatch: ^4.0.4
|
||||||
|
|
||||||
importers:
|
importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
@@ -126,9 +130,6 @@ importers:
|
|||||||
three:
|
three:
|
||||||
specifier: ^0.183.2
|
specifier: ^0.183.2
|
||||||
version: 0.183.2
|
version: 0.183.2
|
||||||
xlsx:
|
|
||||||
specifier: ^0.18.5
|
|
||||||
version: 0.18.5
|
|
||||||
zod:
|
zod:
|
||||||
specifier: ^3.23.8
|
specifier: ^3.23.8
|
||||||
version: 3.25.76
|
version: 3.25.76
|
||||||
@@ -157,6 +158,9 @@ importers:
|
|||||||
'@types/three':
|
'@types/three':
|
||||||
specifier: ^0.183.1
|
specifier: ^0.183.1
|
||||||
version: 0.183.1
|
version: 0.183.1
|
||||||
|
'@vitest/coverage-v8':
|
||||||
|
specifier: ^2.1.9
|
||||||
|
version: 2.1.9(vitest@2.1.9(@types/node@22.19.13)(terser@5.46.1))
|
||||||
autoprefixer:
|
autoprefixer:
|
||||||
specifier: ^10.4.20
|
specifier: ^10.4.20
|
||||||
version: 10.4.27(postcss@8.5.8)
|
version: 10.4.27(postcss@8.5.8)
|
||||||
@@ -169,6 +173,9 @@ importers:
|
|||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.6.3
|
specifier: ^5.6.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
vitest:
|
||||||
|
specifier: ^2.1.9
|
||||||
|
version: 2.1.9(@types/node@22.19.13)(terser@5.46.1)
|
||||||
|
|
||||||
packages/api:
|
packages/api:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -245,9 +252,9 @@ importers:
|
|||||||
'@trpc/server':
|
'@trpc/server':
|
||||||
specifier: ^11.0.0
|
specifier: ^11.0.0
|
||||||
version: 11.11.0(typescript@5.9.3)
|
version: 11.11.0(typescript@5.9.3)
|
||||||
xlsx:
|
exceljs:
|
||||||
specifier: ^0.18.5
|
specifier: ^4.4.0
|
||||||
version: 0.18.5
|
version: 4.4.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@capakraken/tsconfig':
|
'@capakraken/tsconfig':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
@@ -298,9 +305,9 @@ importers:
|
|||||||
'@capakraken/shared':
|
'@capakraken/shared':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../shared
|
version: link:../shared
|
||||||
xlsx:
|
exceljs:
|
||||||
specifier: ^0.18.5
|
specifier: ^4.4.0
|
||||||
version: 0.18.5
|
version: 4.4.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@capakraken/tsconfig':
|
'@capakraken/tsconfig':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
@@ -407,6 +414,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
'@ampproject/remapping@2.3.0':
|
||||||
|
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
'@auth/core@0.41.0':
|
'@auth/core@0.41.0':
|
||||||
resolution: {integrity: sha512-Wd7mHPQ/8zy6Qj7f4T46vg3aoor8fskJm6g2Zyj064oQ3+p0xNZXAV60ww0hY+MbTesfu29kK14Zk5d5JTazXQ==}
|
resolution: {integrity: sha512-Wd7mHPQ/8zy6Qj7f4T46vg3aoor8fskJm6g2Zyj064oQ3+p0xNZXAV60ww0hY+MbTesfu29kK14Zk5d5JTazXQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -492,6 +503,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
|
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@bcoe/v8-coverage@0.2.3':
|
||||||
|
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
|
||||||
|
|
||||||
'@dimforge/rapier3d-compat@0.12.0':
|
'@dimforge/rapier3d-compat@0.12.0':
|
||||||
resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==}
|
resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==}
|
||||||
|
|
||||||
@@ -1025,6 +1039,14 @@ packages:
|
|||||||
'@ioredis/commands@1.5.1':
|
'@ioredis/commands@1.5.1':
|
||||||
resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==}
|
resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==}
|
||||||
|
|
||||||
|
'@isaacs/cliui@8.0.2':
|
||||||
|
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
'@istanbuljs/schema@0.1.3':
|
||||||
|
resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
'@jridgewell/gen-mapping@0.3.13':
|
'@jridgewell/gen-mapping@0.3.13':
|
||||||
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
||||||
|
|
||||||
@@ -1411,6 +1433,10 @@ packages:
|
|||||||
'@pinojs/redact@0.4.0':
|
'@pinojs/redact@0.4.0':
|
||||||
resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
|
resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
|
||||||
|
|
||||||
|
'@pkgjs/parseargs@0.11.0':
|
||||||
|
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
'@playwright/test@1.58.2':
|
'@playwright/test@1.58.2':
|
||||||
resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==}
|
resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -2003,6 +2029,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==}
|
resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
|
'@vitest/coverage-v8@2.1.9':
|
||||||
|
resolution: {integrity: sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@vitest/browser': 2.1.9
|
||||||
|
vitest: 2.1.9
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@vitest/browser':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@vitest/expect@2.1.9':
|
'@vitest/expect@2.1.9':
|
||||||
resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==}
|
resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==}
|
||||||
|
|
||||||
@@ -2114,10 +2149,6 @@ packages:
|
|||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
adler-32@1.3.1:
|
|
||||||
resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==}
|
|
||||||
engines: {node: '>=0.8'}
|
|
||||||
|
|
||||||
agent-base@6.0.2:
|
agent-base@6.0.2:
|
||||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||||
engines: {node: '>= 6.0.0'}
|
engines: {node: '>= 6.0.0'}
|
||||||
@@ -2141,10 +2172,22 @@ packages:
|
|||||||
ajv@8.18.0:
|
ajv@8.18.0:
|
||||||
resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
|
resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
|
||||||
|
|
||||||
|
ansi-regex@5.0.1:
|
||||||
|
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
ansi-regex@6.2.2:
|
||||||
|
resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
ansi-styles@4.3.0:
|
ansi-styles@4.3.0:
|
||||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
ansi-styles@6.2.3:
|
||||||
|
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
any-promise@1.3.0:
|
any-promise@1.3.0:
|
||||||
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
||||||
|
|
||||||
@@ -2328,10 +2371,6 @@ packages:
|
|||||||
caniuse-lite@1.0.30001776:
|
caniuse-lite@1.0.30001776:
|
||||||
resolution: {integrity: sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==}
|
resolution: {integrity: sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==}
|
||||||
|
|
||||||
cfb@1.2.2:
|
|
||||||
resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==}
|
|
||||||
engines: {node: '>=0.8'}
|
|
||||||
|
|
||||||
chai@5.3.3:
|
chai@5.3.3:
|
||||||
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
|
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -2373,10 +2412,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
|
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
codepage@1.15.0:
|
|
||||||
resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==}
|
|
||||||
engines: {node: '>=0.8'}
|
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||||
engines: {node: '>=7.0.0'}
|
engines: {node: '>=7.0.0'}
|
||||||
@@ -2593,12 +2628,21 @@ packages:
|
|||||||
duplexer2@0.1.4:
|
duplexer2@0.1.4:
|
||||||
resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==}
|
resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==}
|
||||||
|
|
||||||
|
eastasianwidth@0.2.0:
|
||||||
|
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||||
|
|
||||||
electron-to-chromium@1.5.307:
|
electron-to-chromium@1.5.307:
|
||||||
resolution: {integrity: sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==}
|
resolution: {integrity: sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==}
|
||||||
|
|
||||||
emoji-regex-xs@1.0.0:
|
emoji-regex-xs@1.0.0:
|
||||||
resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==}
|
resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==}
|
||||||
|
|
||||||
|
emoji-regex@8.0.0:
|
||||||
|
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||||
|
|
||||||
|
emoji-regex@9.2.2:
|
||||||
|
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
||||||
|
|
||||||
end-of-stream@1.4.5:
|
end-of-stream@1.4.5:
|
||||||
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
|
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
|
||||||
|
|
||||||
@@ -2806,7 +2850,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
picomatch: ^3 || ^4
|
picomatch: ^4.0.4
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
picomatch:
|
picomatch:
|
||||||
optional: true
|
optional: true
|
||||||
@@ -2830,8 +2874,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
|
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
flatted@3.3.4:
|
flatted@3.4.2:
|
||||||
resolution: {integrity: sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==}
|
resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
|
||||||
|
|
||||||
float-tooltip@1.7.5:
|
float-tooltip@1.7.5:
|
||||||
resolution: {integrity: sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==}
|
resolution: {integrity: sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==}
|
||||||
@@ -2844,13 +2888,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
foreground-child@3.3.1:
|
||||||
|
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
forwarded-parse@2.1.2:
|
forwarded-parse@2.1.2:
|
||||||
resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==}
|
resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==}
|
||||||
|
|
||||||
frac@1.1.2:
|
|
||||||
resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==}
|
|
||||||
engines: {node: '>=0.8'}
|
|
||||||
|
|
||||||
fraction.js@5.3.4:
|
fraction.js@5.3.4:
|
||||||
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
|
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
|
||||||
|
|
||||||
@@ -2933,6 +2977,11 @@ packages:
|
|||||||
glob-to-regexp@0.4.1:
|
glob-to-regexp@0.4.1:
|
||||||
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
|
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
|
||||||
|
|
||||||
|
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
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
glob@13.0.6:
|
glob@13.0.6:
|
||||||
resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==}
|
resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==}
|
||||||
engines: {node: 18 || 20 || >=22}
|
engines: {node: 18 || 20 || >=22}
|
||||||
@@ -2989,6 +3038,9 @@ packages:
|
|||||||
hsl-to-rgb-for-reals@1.1.1:
|
hsl-to-rgb-for-reals@1.1.1:
|
||||||
resolution: {integrity: sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==}
|
resolution: {integrity: sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==}
|
||||||
|
|
||||||
|
html-escaper@2.0.2:
|
||||||
|
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||||
|
|
||||||
https-proxy-agent@5.0.1:
|
https-proxy-agent@5.0.1:
|
||||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@@ -3097,6 +3149,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
|
resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
is-fullwidth-code-point@3.0.0:
|
||||||
|
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
is-generator-function@1.1.2:
|
is-generator-function@1.1.2:
|
||||||
resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
|
resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -3172,6 +3228,25 @@ packages:
|
|||||||
isexe@2.0.0:
|
isexe@2.0.0:
|
||||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||||
|
|
||||||
|
istanbul-lib-coverage@3.2.2:
|
||||||
|
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
istanbul-lib-report@3.0.1:
|
||||||
|
resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
istanbul-lib-source-maps@5.0.6:
|
||||||
|
resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
istanbul-reports@3.2.0:
|
||||||
|
resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
jackspeak@3.4.3:
|
||||||
|
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
||||||
|
|
||||||
jay-peg@1.1.1:
|
jay-peg@1.1.1:
|
||||||
resolution: {integrity: sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww==}
|
resolution: {integrity: sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww==}
|
||||||
|
|
||||||
@@ -3324,6 +3399,9 @@ packages:
|
|||||||
loupe@3.2.1:
|
loupe@3.2.1:
|
||||||
resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
|
resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
|
||||||
|
|
||||||
|
lru-cache@10.4.3:
|
||||||
|
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||||
|
|
||||||
lru-cache@11.2.7:
|
lru-cache@11.2.7:
|
||||||
resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==}
|
resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==}
|
||||||
engines: {node: 20 || >=22}
|
engines: {node: 20 || >=22}
|
||||||
@@ -3334,6 +3412,13 @@ packages:
|
|||||||
magic-string@0.30.21:
|
magic-string@0.30.21:
|
||||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||||
|
|
||||||
|
magicast@0.3.5:
|
||||||
|
resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==}
|
||||||
|
|
||||||
|
make-dir@4.0.0:
|
||||||
|
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
math-intrinsics@1.1.0:
|
math-intrinsics@1.1.0:
|
||||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -3374,6 +3459,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==}
|
resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
minimatch@9.0.9:
|
||||||
|
resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==}
|
||||||
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
|
|
||||||
minimist@1.2.8:
|
minimist@1.2.8:
|
||||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||||
|
|
||||||
@@ -3559,6 +3648,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
package-json-from-dist@1.0.1:
|
||||||
|
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||||
|
|
||||||
pako@0.2.9:
|
pako@0.2.9:
|
||||||
resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
|
resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
|
||||||
|
|
||||||
@@ -3587,6 +3679,10 @@ packages:
|
|||||||
path-parse@1.0.7:
|
path-parse@1.0.7:
|
||||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||||
|
|
||||||
|
path-scurry@1.11.1:
|
||||||
|
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
||||||
|
engines: {node: '>=16 || 14 >=14.18'}
|
||||||
|
|
||||||
path-scurry@2.0.2:
|
path-scurry@2.0.2:
|
||||||
resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==}
|
resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==}
|
||||||
engines: {node: 18 || 20 || >=22}
|
engines: {node: 18 || 20 || >=22}
|
||||||
@@ -3612,12 +3708,8 @@ packages:
|
|||||||
picocolors@1.1.1:
|
picocolors@1.1.1:
|
||||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
picomatch@2.3.1:
|
picomatch@4.0.4:
|
||||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
|
||||||
engines: {node: '>=8.6'}
|
|
||||||
|
|
||||||
picomatch@4.0.3:
|
|
||||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
pify@2.3.0:
|
pify@2.3.0:
|
||||||
@@ -4018,6 +4110,10 @@ packages:
|
|||||||
siginfo@2.0.0:
|
siginfo@2.0.0:
|
||||||
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||||
|
|
||||||
|
signal-exit@4.1.0:
|
||||||
|
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
simple-swizzle@0.2.4:
|
simple-swizzle@0.2.4:
|
||||||
resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==}
|
resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==}
|
||||||
|
|
||||||
@@ -4039,10 +4135,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||||
engines: {node: '>= 10.x'}
|
engines: {node: '>= 10.x'}
|
||||||
|
|
||||||
ssf@0.11.2:
|
|
||||||
resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==}
|
|
||||||
engines: {node: '>=0.8'}
|
|
||||||
|
|
||||||
stackback@0.0.2:
|
stackback@0.0.2:
|
||||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||||
|
|
||||||
@@ -4060,6 +4152,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
|
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
string-width@4.2.3:
|
||||||
|
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
string-width@5.1.2:
|
||||||
|
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
string.prototype.trim@1.2.10:
|
string.prototype.trim@1.2.10:
|
||||||
resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
|
resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -4078,6 +4178,14 @@ packages:
|
|||||||
string_decoder@1.3.0:
|
string_decoder@1.3.0:
|
||||||
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||||
|
|
||||||
|
strip-ansi@6.0.1:
|
||||||
|
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
strip-ansi@7.2.0:
|
||||||
|
resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
strip-bom@3.0.0:
|
strip-bom@3.0.0:
|
||||||
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
|
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -4156,6 +4264,10 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
test-exclude@7.0.2:
|
||||||
|
resolution: {integrity: sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
thenify-all@1.6.0:
|
thenify-all@1.6.0:
|
||||||
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
|
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
|
||||||
engines: {node: '>=0.8'}
|
engines: {node: '>=0.8'}
|
||||||
@@ -4469,26 +4581,21 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
wmf@1.0.2:
|
|
||||||
resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==}
|
|
||||||
engines: {node: '>=0.8'}
|
|
||||||
|
|
||||||
word-wrap@1.2.5:
|
word-wrap@1.2.5:
|
||||||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
word@0.3.0:
|
wrap-ansi@7.0.0:
|
||||||
resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
|
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||||
engines: {node: '>=0.8'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
wrap-ansi@8.1.0:
|
||||||
|
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
wrappy@1.0.2:
|
wrappy@1.0.2:
|
||||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||||
|
|
||||||
xlsx@0.18.5:
|
|
||||||
resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==}
|
|
||||||
engines: {node: '>=0.8'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
xmlchars@2.2.0:
|
xmlchars@2.2.0:
|
||||||
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
||||||
|
|
||||||
@@ -4525,6 +4632,11 @@ snapshots:
|
|||||||
|
|
||||||
'@alloc/quick-lru@5.2.0': {}
|
'@alloc/quick-lru@5.2.0': {}
|
||||||
|
|
||||||
|
'@ampproject/remapping@2.3.0':
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/gen-mapping': 0.3.13
|
||||||
|
'@jridgewell/trace-mapping': 0.3.31
|
||||||
|
|
||||||
'@auth/core@0.41.0':
|
'@auth/core@0.41.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@panva/hkdf': 1.2.1
|
'@panva/hkdf': 1.2.1
|
||||||
@@ -4635,6 +4747,8 @@ snapshots:
|
|||||||
'@babel/helper-string-parser': 7.27.1
|
'@babel/helper-string-parser': 7.27.1
|
||||||
'@babel/helper-validator-identifier': 7.28.5
|
'@babel/helper-validator-identifier': 7.28.5
|
||||||
|
|
||||||
|
'@bcoe/v8-coverage@0.2.3': {}
|
||||||
|
|
||||||
'@dimforge/rapier3d-compat@0.12.0': {}
|
'@dimforge/rapier3d-compat@0.12.0': {}
|
||||||
|
|
||||||
'@dnd-kit/accessibility@3.1.1(react@19.2.4)':
|
'@dnd-kit/accessibility@3.1.1(react@19.2.4)':
|
||||||
@@ -5010,6 +5124,17 @@ snapshots:
|
|||||||
|
|
||||||
'@ioredis/commands@1.5.1': {}
|
'@ioredis/commands@1.5.1': {}
|
||||||
|
|
||||||
|
'@isaacs/cliui@8.0.2':
|
||||||
|
dependencies:
|
||||||
|
string-width: 5.1.2
|
||||||
|
string-width-cjs: string-width@4.2.3
|
||||||
|
strip-ansi: 7.2.0
|
||||||
|
strip-ansi-cjs: strip-ansi@6.0.1
|
||||||
|
wrap-ansi: 8.1.0
|
||||||
|
wrap-ansi-cjs: wrap-ansi@7.0.0
|
||||||
|
|
||||||
|
'@istanbuljs/schema@0.1.3': {}
|
||||||
|
|
||||||
'@jridgewell/gen-mapping@0.3.13':
|
'@jridgewell/gen-mapping@0.3.13':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
@@ -5409,6 +5534,9 @@ snapshots:
|
|||||||
|
|
||||||
'@pinojs/redact@0.4.0': {}
|
'@pinojs/redact@0.4.0': {}
|
||||||
|
|
||||||
|
'@pkgjs/parseargs@0.11.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@playwright/test@1.58.2':
|
'@playwright/test@1.58.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
playwright: 1.58.2
|
playwright: 1.58.2
|
||||||
@@ -5563,10 +5691,10 @@ snapshots:
|
|||||||
'@rollup/pluginutils': 5.3.0(rollup@4.59.0)
|
'@rollup/pluginutils': 5.3.0(rollup@4.59.0)
|
||||||
commondir: 1.0.1
|
commondir: 1.0.1
|
||||||
estree-walker: 2.0.2
|
estree-walker: 2.0.2
|
||||||
fdir: 6.5.0(picomatch@4.0.3)
|
fdir: 6.5.0(picomatch@4.0.4)
|
||||||
is-reference: 1.2.1
|
is-reference: 1.2.1
|
||||||
magic-string: 0.30.21
|
magic-string: 0.30.21
|
||||||
picomatch: 4.0.3
|
picomatch: 4.0.4
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
rollup: 4.59.0
|
rollup: 4.59.0
|
||||||
|
|
||||||
@@ -5574,7 +5702,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.8
|
'@types/estree': 1.0.8
|
||||||
estree-walker: 2.0.2
|
estree-walker: 2.0.2
|
||||||
picomatch: 4.0.3
|
picomatch: 4.0.4
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
rollup: 4.59.0
|
rollup: 4.59.0
|
||||||
|
|
||||||
@@ -6101,6 +6229,24 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.56.1
|
'@typescript-eslint/types': 8.56.1
|
||||||
eslint-visitor-keys: 5.0.1
|
eslint-visitor-keys: 5.0.1
|
||||||
|
|
||||||
|
'@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@22.19.13)(terser@5.46.1))':
|
||||||
|
dependencies:
|
||||||
|
'@ampproject/remapping': 2.3.0
|
||||||
|
'@bcoe/v8-coverage': 0.2.3
|
||||||
|
debug: 4.4.3
|
||||||
|
istanbul-lib-coverage: 3.2.2
|
||||||
|
istanbul-lib-report: 3.0.1
|
||||||
|
istanbul-lib-source-maps: 5.0.6
|
||||||
|
istanbul-reports: 3.2.0
|
||||||
|
magic-string: 0.30.21
|
||||||
|
magicast: 0.3.5
|
||||||
|
std-env: 3.10.0
|
||||||
|
test-exclude: 7.0.2
|
||||||
|
tinyrainbow: 1.2.0
|
||||||
|
vitest: 2.1.9(@types/node@22.19.13)(terser@5.46.1)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@vitest/expect@2.1.9':
|
'@vitest/expect@2.1.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/spy': 2.1.9
|
'@vitest/spy': 2.1.9
|
||||||
@@ -6241,8 +6387,6 @@ snapshots:
|
|||||||
|
|
||||||
acorn@8.16.0: {}
|
acorn@8.16.0: {}
|
||||||
|
|
||||||
adler-32@1.3.1: {}
|
|
||||||
|
|
||||||
agent-base@6.0.2:
|
agent-base@6.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
@@ -6272,16 +6416,22 @@ snapshots:
|
|||||||
json-schema-traverse: 1.0.0
|
json-schema-traverse: 1.0.0
|
||||||
require-from-string: 2.0.2
|
require-from-string: 2.0.2
|
||||||
|
|
||||||
|
ansi-regex@5.0.1: {}
|
||||||
|
|
||||||
|
ansi-regex@6.2.2: {}
|
||||||
|
|
||||||
ansi-styles@4.3.0:
|
ansi-styles@4.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-convert: 2.0.1
|
color-convert: 2.0.1
|
||||||
|
|
||||||
|
ansi-styles@6.2.3: {}
|
||||||
|
|
||||||
any-promise@1.3.0: {}
|
any-promise@1.3.0: {}
|
||||||
|
|
||||||
anymatch@3.1.3:
|
anymatch@3.1.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
normalize-path: 3.0.0
|
normalize-path: 3.0.0
|
||||||
picomatch: 2.3.1
|
picomatch: 4.0.4
|
||||||
|
|
||||||
archiver-utils@2.1.0:
|
archiver-utils@2.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6496,11 +6646,6 @@ snapshots:
|
|||||||
|
|
||||||
caniuse-lite@1.0.30001776: {}
|
caniuse-lite@1.0.30001776: {}
|
||||||
|
|
||||||
cfb@1.2.2:
|
|
||||||
dependencies:
|
|
||||||
adler-32: 1.3.1
|
|
||||||
crc-32: 1.2.2
|
|
||||||
|
|
||||||
chai@5.3.3:
|
chai@5.3.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
assertion-error: 2.0.1
|
assertion-error: 2.0.1
|
||||||
@@ -6544,8 +6689,6 @@ snapshots:
|
|||||||
|
|
||||||
cluster-key-slot@1.1.2: {}
|
cluster-key-slot@1.1.2: {}
|
||||||
|
|
||||||
codepage@1.15.0: {}
|
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name: 1.1.4
|
color-name: 1.1.4
|
||||||
@@ -6737,10 +6880,16 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
readable-stream: 2.3.8
|
readable-stream: 2.3.8
|
||||||
|
|
||||||
|
eastasianwidth@0.2.0: {}
|
||||||
|
|
||||||
electron-to-chromium@1.5.307: {}
|
electron-to-chromium@1.5.307: {}
|
||||||
|
|
||||||
emoji-regex-xs@1.0.0: {}
|
emoji-regex-xs@1.0.0: {}
|
||||||
|
|
||||||
|
emoji-regex@8.0.0: {}
|
||||||
|
|
||||||
|
emoji-regex@9.2.2: {}
|
||||||
|
|
||||||
end-of-stream@1.4.5:
|
end-of-stream@1.4.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
once: 1.4.0
|
once: 1.4.0
|
||||||
@@ -7076,9 +7225,9 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
reusify: 1.1.0
|
reusify: 1.1.0
|
||||||
|
|
||||||
fdir@6.5.0(picomatch@4.0.3):
|
fdir@6.5.0(picomatch@4.0.4):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
picomatch: 4.0.3
|
picomatch: 4.0.4
|
||||||
|
|
||||||
fflate@0.8.2: {}
|
fflate@0.8.2: {}
|
||||||
|
|
||||||
@@ -7097,10 +7246,10 @@ snapshots:
|
|||||||
|
|
||||||
flat-cache@4.0.1:
|
flat-cache@4.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
flatted: 3.3.4
|
flatted: 3.4.2
|
||||||
keyv: 4.5.4
|
keyv: 4.5.4
|
||||||
|
|
||||||
flatted@3.3.4: {}
|
flatted@3.4.2: {}
|
||||||
|
|
||||||
float-tooltip@1.7.5:
|
float-tooltip@1.7.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -7124,9 +7273,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-callable: 1.2.7
|
is-callable: 1.2.7
|
||||||
|
|
||||||
forwarded-parse@2.1.2: {}
|
foreground-child@3.3.1:
|
||||||
|
dependencies:
|
||||||
|
cross-spawn: 7.0.6
|
||||||
|
signal-exit: 4.1.0
|
||||||
|
|
||||||
frac@1.1.2: {}
|
forwarded-parse@2.1.2: {}
|
||||||
|
|
||||||
fraction.js@5.3.4: {}
|
fraction.js@5.3.4: {}
|
||||||
|
|
||||||
@@ -7211,6 +7363,15 @@ snapshots:
|
|||||||
|
|
||||||
glob-to-regexp@0.4.1: {}
|
glob-to-regexp@0.4.1: {}
|
||||||
|
|
||||||
|
glob@10.5.0:
|
||||||
|
dependencies:
|
||||||
|
foreground-child: 3.3.1
|
||||||
|
jackspeak: 3.4.3
|
||||||
|
minimatch: 9.0.9
|
||||||
|
minipass: 7.1.3
|
||||||
|
package-json-from-dist: 1.0.1
|
||||||
|
path-scurry: 1.11.1
|
||||||
|
|
||||||
glob@13.0.6:
|
glob@13.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
minimatch: 10.2.4
|
minimatch: 10.2.4
|
||||||
@@ -7265,6 +7426,8 @@ snapshots:
|
|||||||
|
|
||||||
hsl-to-rgb-for-reals@1.1.1: {}
|
hsl-to-rgb-for-reals@1.1.1: {}
|
||||||
|
|
||||||
|
html-escaper@2.0.2: {}
|
||||||
|
|
||||||
https-proxy-agent@5.0.1:
|
https-proxy-agent@5.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 6.0.2
|
agent-base: 6.0.2
|
||||||
@@ -7388,6 +7551,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
|
|
||||||
|
is-fullwidth-code-point@3.0.0: {}
|
||||||
|
|
||||||
is-generator-function@1.1.2:
|
is-generator-function@1.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
@@ -7462,6 +7627,33 @@ snapshots:
|
|||||||
|
|
||||||
isexe@2.0.0: {}
|
isexe@2.0.0: {}
|
||||||
|
|
||||||
|
istanbul-lib-coverage@3.2.2: {}
|
||||||
|
|
||||||
|
istanbul-lib-report@3.0.1:
|
||||||
|
dependencies:
|
||||||
|
istanbul-lib-coverage: 3.2.2
|
||||||
|
make-dir: 4.0.0
|
||||||
|
supports-color: 7.2.0
|
||||||
|
|
||||||
|
istanbul-lib-source-maps@5.0.6:
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/trace-mapping': 0.3.31
|
||||||
|
debug: 4.4.3
|
||||||
|
istanbul-lib-coverage: 3.2.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
istanbul-reports@3.2.0:
|
||||||
|
dependencies:
|
||||||
|
html-escaper: 2.0.2
|
||||||
|
istanbul-lib-report: 3.0.1
|
||||||
|
|
||||||
|
jackspeak@3.4.3:
|
||||||
|
dependencies:
|
||||||
|
'@isaacs/cliui': 8.0.2
|
||||||
|
optionalDependencies:
|
||||||
|
'@pkgjs/parseargs': 0.11.0
|
||||||
|
|
||||||
jay-peg@1.1.1:
|
jay-peg@1.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
restructure: 3.0.2
|
restructure: 3.0.2
|
||||||
@@ -7585,6 +7777,8 @@ snapshots:
|
|||||||
|
|
||||||
loupe@3.2.1: {}
|
loupe@3.2.1: {}
|
||||||
|
|
||||||
|
lru-cache@10.4.3: {}
|
||||||
|
|
||||||
lru-cache@11.2.7: {}
|
lru-cache@11.2.7: {}
|
||||||
|
|
||||||
lru-cache@5.1.1:
|
lru-cache@5.1.1:
|
||||||
@@ -7595,6 +7789,16 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
magicast@0.3.5:
|
||||||
|
dependencies:
|
||||||
|
'@babel/parser': 7.29.2
|
||||||
|
'@babel/types': 7.29.0
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
make-dir@4.0.0:
|
||||||
|
dependencies:
|
||||||
|
semver: 7.7.4
|
||||||
|
|
||||||
math-intrinsics@1.1.0: {}
|
math-intrinsics@1.1.0: {}
|
||||||
|
|
||||||
media-engine@1.0.3: {}
|
media-engine@1.0.3: {}
|
||||||
@@ -7608,7 +7812,7 @@ snapshots:
|
|||||||
micromatch@4.0.8:
|
micromatch@4.0.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
braces: 3.0.3
|
braces: 3.0.3
|
||||||
picomatch: 2.3.1
|
picomatch: 4.0.4
|
||||||
|
|
||||||
mime-db@1.52.0: {}
|
mime-db@1.52.0: {}
|
||||||
|
|
||||||
@@ -7628,6 +7832,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 2.0.2
|
brace-expansion: 2.0.2
|
||||||
|
|
||||||
|
minimatch@9.0.9:
|
||||||
|
dependencies:
|
||||||
|
brace-expansion: 2.0.2
|
||||||
|
|
||||||
minimist@1.2.8: {}
|
minimist@1.2.8: {}
|
||||||
|
|
||||||
minipass@7.1.3: {}
|
minipass@7.1.3: {}
|
||||||
@@ -7795,6 +8003,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-limit: 3.1.0
|
p-limit: 3.1.0
|
||||||
|
|
||||||
|
package-json-from-dist@1.0.1: {}
|
||||||
|
|
||||||
pako@0.2.9: {}
|
pako@0.2.9: {}
|
||||||
|
|
||||||
pako@1.0.11: {}
|
pako@1.0.11: {}
|
||||||
@@ -7813,6 +8023,11 @@ snapshots:
|
|||||||
|
|
||||||
path-parse@1.0.7: {}
|
path-parse@1.0.7: {}
|
||||||
|
|
||||||
|
path-scurry@1.11.1:
|
||||||
|
dependencies:
|
||||||
|
lru-cache: 10.4.3
|
||||||
|
minipass: 7.1.3
|
||||||
|
|
||||||
path-scurry@2.0.2:
|
path-scurry@2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
lru-cache: 11.2.7
|
lru-cache: 11.2.7
|
||||||
@@ -7836,9 +8051,7 @@ snapshots:
|
|||||||
|
|
||||||
picocolors@1.1.1: {}
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
picomatch@2.3.1: {}
|
picomatch@4.0.4: {}
|
||||||
|
|
||||||
picomatch@4.0.3: {}
|
|
||||||
|
|
||||||
pify@2.3.0: {}
|
pify@2.3.0: {}
|
||||||
|
|
||||||
@@ -8053,7 +8266,7 @@ snapshots:
|
|||||||
|
|
||||||
readdirp@3.6.0:
|
readdirp@3.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
picomatch: 2.3.1
|
picomatch: 4.0.4
|
||||||
|
|
||||||
real-require@0.2.0: {}
|
real-require@0.2.0: {}
|
||||||
|
|
||||||
@@ -8311,6 +8524,8 @@ snapshots:
|
|||||||
|
|
||||||
siginfo@2.0.0: {}
|
siginfo@2.0.0: {}
|
||||||
|
|
||||||
|
signal-exit@4.1.0: {}
|
||||||
|
|
||||||
simple-swizzle@0.2.4:
|
simple-swizzle@0.2.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-arrayish: 0.3.4
|
is-arrayish: 0.3.4
|
||||||
@@ -8330,10 +8545,6 @@ snapshots:
|
|||||||
|
|
||||||
split2@4.2.0: {}
|
split2@4.2.0: {}
|
||||||
|
|
||||||
ssf@0.11.2:
|
|
||||||
dependencies:
|
|
||||||
frac: 1.1.2
|
|
||||||
|
|
||||||
stackback@0.0.2: {}
|
stackback@0.0.2: {}
|
||||||
|
|
||||||
stacktrace-parser@0.1.11:
|
stacktrace-parser@0.1.11:
|
||||||
@@ -8349,6 +8560,18 @@ snapshots:
|
|||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
internal-slot: 1.1.0
|
internal-slot: 1.1.0
|
||||||
|
|
||||||
|
string-width@4.2.3:
|
||||||
|
dependencies:
|
||||||
|
emoji-regex: 8.0.0
|
||||||
|
is-fullwidth-code-point: 3.0.0
|
||||||
|
strip-ansi: 6.0.1
|
||||||
|
|
||||||
|
string-width@5.1.2:
|
||||||
|
dependencies:
|
||||||
|
eastasianwidth: 0.2.0
|
||||||
|
emoji-regex: 9.2.2
|
||||||
|
strip-ansi: 7.2.0
|
||||||
|
|
||||||
string.prototype.trim@1.2.10:
|
string.prototype.trim@1.2.10:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.8
|
call-bind: 1.0.8
|
||||||
@@ -8380,6 +8603,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer: 5.2.1
|
safe-buffer: 5.2.1
|
||||||
|
|
||||||
|
strip-ansi@6.0.1:
|
||||||
|
dependencies:
|
||||||
|
ansi-regex: 5.0.1
|
||||||
|
|
||||||
|
strip-ansi@7.2.0:
|
||||||
|
dependencies:
|
||||||
|
ansi-regex: 6.2.2
|
||||||
|
|
||||||
strip-bom@3.0.0: {}
|
strip-bom@3.0.0: {}
|
||||||
|
|
||||||
strip-json-comments@3.1.1: {}
|
strip-json-comments@3.1.1: {}
|
||||||
@@ -8468,6 +8699,12 @@ snapshots:
|
|||||||
commander: 2.20.3
|
commander: 2.20.3
|
||||||
source-map-support: 0.5.21
|
source-map-support: 0.5.21
|
||||||
|
|
||||||
|
test-exclude@7.0.2:
|
||||||
|
dependencies:
|
||||||
|
'@istanbuljs/schema': 0.1.3
|
||||||
|
glob: 10.5.0
|
||||||
|
minimatch: 10.2.4
|
||||||
|
|
||||||
thenify-all@1.6.0:
|
thenify-all@1.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
thenify: 3.3.1
|
thenify: 3.3.1
|
||||||
@@ -8517,8 +8754,8 @@ snapshots:
|
|||||||
|
|
||||||
tinyglobby@0.2.15:
|
tinyglobby@0.2.15:
|
||||||
dependencies:
|
dependencies:
|
||||||
fdir: 6.5.0(picomatch@4.0.3)
|
fdir: 6.5.0(picomatch@4.0.4)
|
||||||
picomatch: 4.0.3
|
picomatch: 4.0.4
|
||||||
|
|
||||||
tinypool@1.1.1: {}
|
tinypool@1.1.1: {}
|
||||||
|
|
||||||
@@ -8860,24 +9097,22 @@ snapshots:
|
|||||||
siginfo: 2.0.0
|
siginfo: 2.0.0
|
||||||
stackback: 0.0.2
|
stackback: 0.0.2
|
||||||
|
|
||||||
wmf@1.0.2: {}
|
|
||||||
|
|
||||||
word-wrap@1.2.5: {}
|
word-wrap@1.2.5: {}
|
||||||
|
|
||||||
word@0.3.0: {}
|
wrap-ansi@7.0.0:
|
||||||
|
dependencies:
|
||||||
|
ansi-styles: 4.3.0
|
||||||
|
string-width: 4.2.3
|
||||||
|
strip-ansi: 6.0.1
|
||||||
|
|
||||||
|
wrap-ansi@8.1.0:
|
||||||
|
dependencies:
|
||||||
|
ansi-styles: 6.2.3
|
||||||
|
string-width: 5.1.2
|
||||||
|
strip-ansi: 7.2.0
|
||||||
|
|
||||||
wrappy@1.0.2: {}
|
wrappy@1.0.2: {}
|
||||||
|
|
||||||
xlsx@0.18.5:
|
|
||||||
dependencies:
|
|
||||||
adler-32: 1.3.1
|
|
||||||
cfb: 1.2.2
|
|
||||||
codepage: 1.15.0
|
|
||||||
crc-32: 1.2.2
|
|
||||||
ssf: 0.11.2
|
|
||||||
wmf: 1.0.2
|
|
||||||
word: 0.3.0
|
|
||||||
|
|
||||||
xmlchars@2.2.0: {}
|
xmlchars@2.2.0: {}
|
||||||
|
|
||||||
xtend@4.0.2: {}
|
xtend@4.0.2: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user