diff --git a/.changeset/modern-tabular-data-upload.md b/.changeset/modern-tabular-data-upload.md
new file mode 100644
index 00000000..07ff7634
--- /dev/null
+++ b/.changeset/modern-tabular-data-upload.md
@@ -0,0 +1,5 @@
+---
+"@usace-watermanagement/groundwork-water": minor
+---
+
+Add a React and Groundwork-based CWMS tabular data upload form with workbook validation, existing-data comparison, batch submission, deletion, and generated templates.
diff --git a/docs/src/bundles/route-bundle.js b/docs/src/bundles/route-bundle.js
index 4877fa6d..ca8358e9 100644
--- a/docs/src/bundles/route-bundle.js
+++ b/docs/src/bundles/route-bundle.js
@@ -40,6 +40,7 @@ import FormsDocs from "../pages/docs/forms";
import CWMSFormDocs from "../pages/docs/forms/cwms-form";
import CWMSInputDocs from "../pages/docs/forms/cwms-input";
import CWMSFileUploadDocs from "../pages/docs/forms/cwms-file-upload";
+import CWMSDataUploadDocs from "../pages/docs/forms/cwms-data-upload";
import CWMSTextareaDocs from "../pages/docs/forms/cwms-textarea";
import CWMSCheckboxesDocs from "../pages/docs/forms/cwms-checkboxes";
import CWMSRadioGroupDocs from "../pages/docs/forms/cwms-radio-group";
@@ -87,6 +88,7 @@ export default createRouteBundle(
"/docs/forms/cwms-form": CWMSFormDocs,
"/docs/forms/cwms-input": CWMSInputDocs,
"/docs/forms/cwms-file-upload": CWMSFileUploadDocs,
+ "/docs/forms/cwms-data-upload": CWMSDataUploadDocs,
"/docs/forms/cwms-textarea": CWMSTextareaDocs,
"/docs/forms/cwms-checkboxes": CWMSCheckboxesDocs,
"/docs/forms/cwms-radio-group": CWMSRadioGroupDocs,
diff --git a/docs/src/nav-links.js b/docs/src/nav-links.js
index 4576d989..6c66802c 100644
--- a/docs/src/nav-links.js
+++ b/docs/src/nav-links.js
@@ -206,6 +206,11 @@ export default [
text: "CWMS File Upload",
href: `${BASE_URL}#/docs/forms/cwms-file-upload`,
},
+ {
+ id: "cwms-data-upload",
+ text: "CWMS Data Upload",
+ href: `${BASE_URL}#/docs/forms/cwms-data-upload`,
+ },
{
id: "cwms-input-table",
text: "CWMS Input Table",
diff --git a/docs/src/pages/docs/forms/cwms-data-upload.jsx b/docs/src/pages/docs/forms/cwms-data-upload.jsx
new file mode 100644
index 00000000..56ad5f02
--- /dev/null
+++ b/docs/src/pages/docs/forms/cwms-data-upload.jsx
@@ -0,0 +1,246 @@
+import { Badge, Code, Text, UsaceBox } from "@usace/groundwork";
+import {
+ CWMSDataUpload,
+ CWMSForm,
+ CWMS_DATA_UPLOAD_HEADERS,
+} from "@usace-watermanagement/groundwork-water";
+import { Code as CodeBlock } from "../../components/code";
+import PropsTable from "../../components/props-table";
+import Divider from "../../components/divider";
+import DocsPage from "../_docs-wrapper";
+
+const DEMO_ROWS = [
+ CWMS_DATA_UPLOAD_HEADERS,
+ [
+ "MVS",
+ "DEMO.Stage.Inst.1Hour.0.TEST",
+ "2026-01-01 00:00",
+ "10.25",
+ "0",
+ "",
+ "Observed",
+ ],
+ ["MVS", "DEMO.Stage.Inst.1Hour.0.TEST", "2026-01-01 01:00", "10.5", "0", "", ""],
+ [
+ "MVS",
+ "DEMO.Stage.Inst.1Hour.0.TEST",
+ "2026-01-01 02:00",
+ "10.75",
+ "3",
+ "2026-01-01 02:05",
+ "Questionable",
+ ],
+];
+
+const componentProps = [
+ {
+ name: "office",
+ type: "string",
+ default: "workbook OFFICE",
+ desc: "Expected CWMS office. When supplied, every workbook row must match it.",
+ },
+ {
+ name: "cdaUrl",
+ type: "string",
+ default: "CdaUrlProvider or cwmsjs default",
+ desc: "CWMS Data API base URL used to compare, submit, and delete values.",
+ },
+ {
+ name: "unit",
+ type: "string",
+ default: "ft",
+ desc: "Unit used to retrieve and submit numeric time-series values.",
+ },
+ {
+ name: "timezone",
+ type: "string",
+ default: "America/Chicago",
+ desc: "IANA timezone used to interpret workbook DATE TIME values.",
+ },
+ {
+ name: "storeRule",
+ type: "string",
+ default: "REPLACE_ALL",
+ desc: "CDA store rule used by CWMSForm for numeric batch submission.",
+ },
+ {
+ name: "defaultFilter",
+ type: "all | existing | new",
+ default: "all",
+ desc: "Initial row filter. The visible rows are also the rows submitted or deleted.",
+ },
+ {
+ name: "maxRows",
+ type: "number",
+ default: "200000",
+ desc: "Maximum number of workbook data rows accepted.",
+ },
+ {
+ name: "maxIntradayYears",
+ type: "number",
+ default: "10",
+ desc: "Maximum span for minute and hourly time-series workbooks.",
+ },
+ {
+ name: "maxPreviewRows",
+ type: "number",
+ default: "250",
+ desc: "Maximum number of selected rows rendered in the preview table.",
+ },
+ {
+ name: "showPlot",
+ type: "boolean",
+ default: "true",
+ desc: "Show a CWMSPlot comparison of workbook and existing values.",
+ },
+ {
+ name: "showDeleteButton",
+ type: "boolean",
+ default: "true",
+ desc: "Show the two-step authenticated deletion control for the selected range.",
+ },
+ {
+ name: "loadExistingData",
+ type: "boolean",
+ default: "true",
+ desc: "Retrieve matching CDA values for classification and comparison.",
+ },
+ {
+ name: "initialData",
+ type: "array | parsed model",
+ default: "undefined",
+ desc: "Preload worksheet rows or a parsed model. Useful for tests and controlled examples.",
+ },
+ {
+ name: "onChange",
+ type: "function",
+ default: "undefined",
+ desc: "Called with the parsed workbook model, or null when cleared or invalid.",
+ },
+];
+
+function CWMSDataUploadDocs() {
+ return (
+
+
+ CWMSDataUpload converts a standard Excel workbook into validated
+ numeric and text time-series batches. It uses Groundwork components and
+ Groundwork Water's form, authentication, CDA query, plotting, and toast
+ infrastructure rather than a standalone page or copied vendor scripts.
+
+
+
+ React
+ Groundwork
+ Tailwind CSS
+ TanStack Query
+ cwmsjs
+
+
+
+
+ This documentation example uses controlled in-memory rows. Existing-data reads,
+ submission buttons, and deletion are disabled, so it exercises the complete
+ presentation and form-registration path without changing CDA.
+
+
+
+
+
+
+
+ The component reads the first worksheet, preferring a sheet named{" "}
+ Sheet1. These seven headers must appear in order:
+
+
+ {CWMS_DATA_UPLOAD_HEADERS.map((header) => (
+
+ {header}
+
+ ))}
+
+
+ All rows must use one office and one TSID. Dates use{" "}
+ YYYY-MM-DD HH:mm and are interpreted in the configured IANA
+ timezone. Each row requires a numeric value, a text value, or both. Duplicate
+ local timestamps, malformed values, invalid quality codes, and oversized ranges
+ are rejected before submission.
+
+
+
+
+ {`import {
+ AuthProvider,
+ CdaUrlProvider,
+ CWMSDataUpload,
+ CWMSForm,
+} from "@usace-watermanagement/groundwork-water";
+
+const cdaUrl = "https://cwms-data.usace.army.mil/cwms-data";
+
+
+
+ console.log(result)}
+ >
+
+
+
+ `}
+
+
+
+
+ Numeric rows are grouped into one CDA time-series payload per TSID.
+ Text rows are grouped into CWMS regular text time-series payloads.
+ The current all/existing/new filter controls the submitted rows.
+ Authentication comes from AuthProvider; no login polling is required.
+ Successful writes invalidate matching TanStack Query caches.
+ Deletion requires a second explicit confirmation click.
+
+
+
+
+
+ Use useCwmsDataUpload when an application needs its own layout. The
+ hook retrieves existing values, classifies parsed rows, applies the selected
+ filter, refreshes the comparison query, and provides the authenticated range
+ deletion mutation.
+
+
+ {`const {
+ classifiedRows,
+ filteredRows,
+ existingData,
+ isLoadingExisting,
+ refreshExisting,
+ deleteRows,
+} = useCwmsDataUpload({
+ model: parsedWorkbook,
+ filter: "new",
+ unit: "ft",
+});`}
+
+
+
+
+
+ );
+}
+
+export { CWMSDataUploadDocs };
+export default CWMSDataUploadDocs;
diff --git a/docs/src/pages/docs/forms/index.jsx b/docs/src/pages/docs/forms/index.jsx
index 10dd1b76..70415ccf 100644
--- a/docs/src/pages/docs/forms/index.jsx
+++ b/docs/src/pages/docs/forms/index.jsx
@@ -58,6 +58,15 @@ function FormsDocs() {
{" "}
- Drag-and-drop blob upload input to CWMS Data API (CDA)
+
+
+ CWMSDataUpload
+ {" "}
+ - Validated Excel upload, CDA comparison, plotting, and batch submission
+
{
plugins: [react(), tailwindcss()],
base: base,
resolve: {
+ // The development alias loads library source from the parent package. Dedupe
+ // shared React peers back to the docs app so linked/file installs do not create
+ // a second hooks runtime.
+ dedupe: [
+ "react",
+ "react-dom",
+ "react/jsx-runtime",
+ "@tanstack/react-query",
+ "@usace/groundwork",
+ "react-toastify",
+ ],
alias: isDevelopment
? [
// During development, alias to the source files for easier debugging
diff --git a/lib/components/data/forms/CWMSForm.jsx b/lib/components/data/forms/CWMSForm.jsx
index 1b9e560f..b873b219 100644
--- a/lib/components/data/forms/CWMSForm.jsx
+++ b/lib/components/data/forms/CWMSForm.jsx
@@ -286,7 +286,9 @@ export function CWMSForm({
}
// Submit using TanStack Query mutation
- const submitInputs = formData.filter((input) => input.tsid || input.blobId);
+ const submitInputs = formData.filter(
+ (input) => input.tsid || input.blobId || input.kind === "timeseries-batch",
+ );
if (submitInputs.length > 0) {
mutation.mutate(submitInputs);
diff --git a/lib/components/data/forms/helpers/__tests__/dataUpload.test.js b/lib/components/data/forms/helpers/__tests__/dataUpload.test.js
new file mode 100644
index 00000000..124e72f0
--- /dev/null
+++ b/lib/components/data/forms/helpers/__tests__/dataUpload.test.js
@@ -0,0 +1,118 @@
+import { describe, expect, it } from "vitest";
+import ExcelJS from "exceljs";
+import {
+ CWMS_DATA_UPLOAD_HEADERS,
+ buildCwmsDataUploadPayloads,
+ classifyCwmsDataUploadRows,
+ createCwmsDataUploadTemplate,
+ filterCwmsDataUploadRows,
+ parseCwmsDataUploadRows,
+} from "../dataUpload";
+
+const validRows = [
+ CWMS_DATA_UPLOAD_HEADERS,
+ [
+ "MVS",
+ "TEST.Stage.Inst.1Hour.0.TEST",
+ "2026-01-01 00:00",
+ "10.25",
+ "0",
+ "",
+ "first",
+ ],
+ [
+ "MVS",
+ "TEST.Stage.Inst.1Hour.0.TEST",
+ "2026-01-01 01:00",
+ "10.5",
+ "3",
+ "2026-01-01 01:05",
+ "",
+ ],
+];
+
+describe("CWMS tabular data helpers", () => {
+ it("parses and normalizes a valid workbook", () => {
+ const model = parseCwmsDataUploadRows(validRows, { office: "MVS" });
+
+ expect(model.office).toBe("MVS");
+ expect(model.tsid).toBe("TEST.Stage.Inst.1Hour.0.TEST");
+ expect(model.rows).toHaveLength(2);
+ expect(model.rows[0]).toMatchObject({
+ value: 10.25,
+ qualityCode: 0,
+ timestamp: "2026-01-01T06:00:00.000Z",
+ });
+ expect(model.rows[1].dataEntryDate).toBeInstanceOf(Date);
+ });
+
+ it("reports structural and row validation errors together", () => {
+ const rows = [
+ CWMS_DATA_UPLOAD_HEADERS,
+ ["MVS", "A.Stage.Inst.1Hour.0.TEST", "not-a-date", "bad", "-1", "", ""],
+ ["SWT", "B.Stage.Inst.1Hour.0.TEST", "2026-01-01 01:00", "1", "0", "", ""],
+ ];
+
+ expect(() => parseCwmsDataUploadRows(rows, { office: "MVS" })).toThrowError(
+ expect.objectContaining({
+ issues: expect.arrayContaining([
+ expect.stringContaining("DATE TIME must use"),
+ expect.stringContaining("VALUE bad is not numeric"),
+ expect.stringContaining("QUALITY CODE"),
+ expect.stringContaining("does not match configured office"),
+ expect.stringContaining("All rows must use one OFFICE"),
+ expect.stringContaining("All rows must use one TSID"),
+ ]),
+ }),
+ );
+ });
+
+ it("builds grouped numeric and text CDA payloads", () => {
+ const model = parseCwmsDataUploadRows(validRows);
+ const payloads = buildCwmsDataUploadPayloads(model.rows, {
+ unit: "ft",
+ valueUrlBase: "https://example.test/cwms-data/",
+ });
+
+ expect(payloads.numeric).toHaveLength(1);
+ expect(payloads.numeric[0]).toMatchObject({
+ name: model.tsid,
+ officeId: "MVS",
+ units: "ft",
+ });
+ expect(payloads.numeric[0].values).toEqual([
+ [model.rows[0].epoch, 10.25, 0],
+ [model.rows[1].epoch, 10.5, 3],
+ ]);
+ expect(payloads.text[0].regularTextValues[0]).toMatchObject({
+ textValue: "first",
+ qualityCode: 0,
+ valueUrl: "https://example.test/cwms-data/timeseries/text/ignored",
+ });
+ });
+
+ it("classifies and filters values already present in CDA", () => {
+ const model = parseCwmsDataUploadRows(validRows);
+ const classified = classifyCwmsDataUploadRows(model.rows, [
+ [model.rows[0].epoch, 10.25, 0],
+ ]);
+
+ expect(classified.map((row) => row.status)).toEqual(["existing", "new"]);
+ expect(filterCwmsDataUploadRows(classified, "existing")).toHaveLength(1);
+ expect(filterCwmsDataUploadRows(classified, "new")).toHaveLength(1);
+ });
+
+ it("generates the reusable workbook template", async () => {
+ const buffer = await createCwmsDataUploadTemplate({
+ office: "MVS",
+ tsid: "TEST.Stage.Inst.1Hour.0.TEST",
+ });
+ const workbook = new ExcelJS.Workbook();
+ await workbook.xlsx.load(buffer);
+ const worksheet = workbook.getWorksheet("Sheet1");
+
+ expect(worksheet.getRow(1).values.slice(1)).toEqual(CWMS_DATA_UPLOAD_HEADERS);
+ expect(worksheet.getRow(2).getCell(1).value).toBe("MVS");
+ expect(worksheet.getRow(2).getCell(2).value).toBe("TEST.Stage.Inst.1Hour.0.TEST");
+ });
+});
diff --git a/lib/components/data/forms/helpers/dataUpload.js b/lib/components/data/forms/helpers/dataUpload.js
new file mode 100644
index 00000000..e2e70514
--- /dev/null
+++ b/lib/components/data/forms/helpers/dataUpload.js
@@ -0,0 +1,360 @@
+import dayjs from "dayjs";
+import customParseFormat from "dayjs/plugin/customParseFormat.js";
+import timezonePlugin from "dayjs/plugin/timezone.js";
+import utc from "dayjs/plugin/utc.js";
+
+dayjs.extend(customParseFormat);
+dayjs.extend(utc);
+dayjs.extend(timezonePlugin);
+
+const DATE_TIME_FORMAT = "YYYY-MM-DD HH:mm";
+const DEFAULT_TIMEZONE = "America/Chicago";
+const CWMS_DATA_UPLOAD_HEADERS = [
+ "OFFICE",
+ "TSID",
+ "DATE TIME",
+ "VALUE",
+ "QUALITY CODE",
+ "DATA ENTRY DATE",
+ "TEXT VALUE",
+];
+const INTRADAY_INTERVALS = new Set([
+ "1Minute",
+ "5Minutes",
+ "10Minutes",
+ "15Minutes",
+ "30Minutes",
+ "1Hour",
+]);
+
+class CwmsDataUploadValidationError extends Error {
+ constructor(issues) {
+ const normalizedIssues = Array.isArray(issues) ? issues : [String(issues)];
+ super(normalizedIssues[0] || "The workbook is not a valid CWMS data upload.");
+ this.name = "CwmsDataUploadValidationError";
+ this.issues = normalizedIssues;
+ }
+}
+
+const cellText = (value) => {
+ if (value === null || value === undefined) return "";
+ if (value instanceof Date) return dayjs(value).format(DATE_TIME_FORMAT);
+ if (typeof value === "object") {
+ if (value.text !== undefined) return String(value.text).trim();
+ if (value.result !== undefined) return cellText(value.result);
+ if (Array.isArray(value.richText)) {
+ return value.richText
+ .map((part) => part.text || "")
+ .join("")
+ .trim();
+ }
+ }
+ return String(value).trim();
+};
+
+const parseLocalTimestamp = (value, timezone, rowNumber, label = "DATE TIME") => {
+ const text = cellText(value);
+ if (!/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/.test(text)) {
+ throw new CwmsDataUploadValidationError(
+ `Row ${rowNumber}: ${label} must use ${DATE_TIME_FORMAT}.`,
+ );
+ }
+
+ const parsed = dayjs.tz(text, DATE_TIME_FORMAT, timezone);
+ if (!parsed.isValid() || parsed.format(DATE_TIME_FORMAT) !== text) {
+ throw new CwmsDataUploadValidationError(
+ `Row ${rowNumber}: ${label} is not a valid time in ${timezone}.`,
+ );
+ }
+ return parsed;
+};
+
+const parseDataEntryDate = (value, timezone, rowNumber) => {
+ const text = cellText(value);
+ if (!text || text.toUpperCase() === "NA") return undefined;
+
+ if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/.test(text)) {
+ return parseLocalTimestamp(text, timezone, rowNumber, "DATA ENTRY DATE").toDate();
+ }
+
+ const parsed = dayjs(text);
+ if (!parsed.isValid()) {
+ throw new CwmsDataUploadValidationError(
+ `Row ${rowNumber}: DATA ENTRY DATE must be blank, NA, an ISO timestamp, or ${DATE_TIME_FORMAT}.`,
+ );
+ }
+ return parsed.toDate();
+};
+
+const validateHeaders = (headerRow) => {
+ const issues = [];
+ CWMS_DATA_UPLOAD_HEADERS.forEach((header, index) => {
+ const actual = cellText(headerRow?.[index]);
+ if (actual !== header) {
+ issues.push(
+ `Column ${index + 1} must be ${header}; found ${actual || "a blank header"}.`,
+ );
+ }
+ });
+ if (issues.length) throw new CwmsDataUploadValidationError(issues);
+};
+
+function parseCwmsDataUploadRows(
+ worksheetRows,
+ {
+ timezone = DEFAULT_TIMEZONE,
+ maxRows = 200000,
+ maxIntradayYears = 10,
+ office: expectedOffice,
+ } = {},
+) {
+ if (!Array.isArray(worksheetRows) || worksheetRows.length < 2) {
+ throw new CwmsDataUploadValidationError(
+ "The workbook must contain a header and at least one data row.",
+ );
+ }
+ if (worksheetRows.length - 1 > maxRows) {
+ throw new CwmsDataUploadValidationError(
+ `The workbook contains ${worksheetRows.length - 1} data rows; the limit is ${maxRows}.`,
+ );
+ }
+
+ validateHeaders(worksheetRows[0]);
+
+ const rows = [];
+ const issues = [];
+ const seenLocalTimes = new Set();
+ const offices = new Set();
+ const tsids = new Set();
+
+ worksheetRows.slice(1).forEach((sourceRow, index) => {
+ const rowNumber = index + 2;
+ const values = Array.from({ length: CWMS_DATA_UPLOAD_HEADERS.length }, (_, col) =>
+ cellText(sourceRow?.[col]),
+ );
+ if (values.every((value) => value === "")) return;
+
+ const [office, tsid, dateTime, rawValue, rawQuality, dataEntryDate, textValue] =
+ values;
+
+ if (!office) issues.push(`Row ${rowNumber}: OFFICE is required.`);
+ if (!tsid) issues.push(`Row ${rowNumber}: TSID is required.`);
+ if (expectedOffice && office && office !== expectedOffice) {
+ issues.push(
+ `Row ${rowNumber}: OFFICE ${office} does not match configured office ${expectedOffice}.`,
+ );
+ }
+ if (!rawValue && !textValue) {
+ issues.push(`Row ${rowNumber}: provide VALUE, TEXT VALUE, or both.`);
+ }
+
+ let parsedTime;
+ try {
+ parsedTime = parseLocalTimestamp(dateTime, timezone, rowNumber);
+ } catch (error) {
+ issues.push(...(error.issues || [error.message]));
+ }
+
+ const duplicateKey = `${office}|${tsid}|${dateTime}`;
+ if (seenLocalTimes.has(duplicateKey)) {
+ issues.push(`Row ${rowNumber}: duplicate DATE TIME ${dateTime} for ${tsid}.`);
+ }
+ seenLocalTimes.add(duplicateKey);
+
+ const value = rawValue === "" ? null : Number(rawValue);
+ if (rawValue !== "" && !Number.isFinite(value)) {
+ issues.push(`Row ${rowNumber}: VALUE ${rawValue} is not numeric.`);
+ }
+
+ const qualityCode = rawQuality === "" ? 0 : Number(rawQuality);
+ if (!Number.isSafeInteger(qualityCode) || qualityCode < 0) {
+ issues.push(`Row ${rowNumber}: QUALITY CODE must be a non-negative integer.`);
+ }
+
+ let parsedEntryDate;
+ try {
+ parsedEntryDate = parseDataEntryDate(dataEntryDate, timezone, rowNumber);
+ } catch (error) {
+ issues.push(...(error.issues || [error.message]));
+ }
+
+ if (office) offices.add(office);
+ if (tsid) tsids.add(tsid);
+ if (office && tsid && parsedTime?.isValid()) {
+ rows.push({
+ rowNumber,
+ office,
+ tsid,
+ dateTime,
+ timestamp: parsedTime.toISOString(),
+ epoch: parsedTime.valueOf(),
+ value: Number.isFinite(value) ? value : null,
+ qualityCode: Number.isSafeInteger(qualityCode) ? qualityCode : 0,
+ dataEntryDate: parsedEntryDate,
+ textValue,
+ });
+ }
+ });
+
+ if (offices.size > 1) {
+ issues.push(`All rows must use one OFFICE; found ${[...offices].join(", ")}.`);
+ }
+ if (tsids.size > 1) {
+ issues.push(`All rows must use one TSID; found ${[...tsids].join(", ")}.`);
+ }
+ if (!rows.length && !issues.length) issues.push("The workbook has no data rows.");
+
+ if (rows.length) {
+ const tsid = rows[0].tsid;
+ const interval = tsid.split(".")[3];
+ const spanYears = (rows.at(-1).epoch - rows[0].epoch) / 31557600000;
+ if (INTRADAY_INTERVALS.has(interval) && spanYears > maxIntradayYears) {
+ issues.push(
+ `${interval} uploads may span at most ${maxIntradayYears} years; this workbook spans ${spanYears.toFixed(2)} years.`,
+ );
+ }
+ }
+
+ if (issues.length) throw new CwmsDataUploadValidationError(issues);
+
+ const sortedRows = [...rows].sort((a, b) => a.epoch - b.epoch);
+ return {
+ rows: sortedRows,
+ office: sortedRows[0].office,
+ tsid: sortedRows[0].tsid,
+ begin: sortedRows[0].timestamp,
+ end: sortedRows.at(-1).timestamp,
+ timezone,
+ };
+}
+
+async function getExcelModule() {
+ const excel = await import("exceljs");
+ return excel.default || excel;
+}
+
+async function readCwmsDataUploadFile(file, options) {
+ const ExcelJS = await getExcelModule();
+ const workbook = new ExcelJS.Workbook();
+ const source =
+ typeof file?.arrayBuffer === "function" ? await file.arrayBuffer() : file;
+ await workbook.xlsx.load(source);
+ const worksheet = workbook.getWorksheet("Sheet1") || workbook.worksheets[0];
+ if (!worksheet) {
+ throw new CwmsDataUploadValidationError(
+ "The workbook does not contain a worksheet.",
+ );
+ }
+
+ const rows = [];
+ worksheet.eachRow({ includeEmpty: true }, (row) => {
+ rows.push(
+ Array.from({ length: CWMS_DATA_UPLOAD_HEADERS.length }, (_, index) =>
+ cellText(row.getCell(index + 1).value),
+ ),
+ );
+ });
+ return parseCwmsDataUploadRows(rows, options);
+}
+
+async function createCwmsDataUploadTemplate({ office = "", tsid = "" } = {}) {
+ const ExcelJS = await getExcelModule();
+ const workbook = new ExcelJS.Workbook();
+ const worksheet = workbook.addWorksheet("Sheet1");
+ worksheet.addRow(CWMS_DATA_UPLOAD_HEADERS);
+ worksheet.addRow([office, tsid, "2026-01-01 00:00", "", "0", "", ""]);
+ worksheet.getRow(1).font = { bold: true };
+ worksheet.columns = [12, 46, 20, 14, 16, 22, 30].map((width) => ({ width }));
+ return workbook.xlsx.writeBuffer();
+}
+
+function buildCwmsDataUploadPayloads(
+ rows,
+ { unit = "ft", textFilename = "cwms-data-upload.txt", valueUrlBase } = {},
+) {
+ const numericGroups = new Map();
+ const textGroups = new Map();
+
+ rows.forEach((row) => {
+ const key = `${row.office}|${row.tsid}`;
+ if (row.value !== null && row.value !== undefined) {
+ if (!numericGroups.has(key)) {
+ numericGroups.set(key, {
+ name: row.tsid,
+ officeId: row.office,
+ units: unit,
+ values: [],
+ });
+ }
+ numericGroups.get(key).values.push([row.epoch, row.value, row.qualityCode || 0]);
+ }
+
+ if (row.textValue) {
+ if (!textGroups.has(key)) {
+ textGroups.set(key, {
+ name: row.tsid,
+ officeId: row.office,
+ intervalOffset: 0,
+ timeZone: "UTC",
+ dateVersionType: "MAX_AGGREGATE",
+ regularTextValues: [],
+ });
+ }
+ textGroups.get(key).regularTextValues.push({
+ dateTime: new Date(row.epoch),
+ dataEntryDate: row.dataEntryDate || new Date(),
+ textValue: row.textValue,
+ filename: textFilename,
+ mediaType: "text/plain",
+ qualityCode: row.qualityCode || 0,
+ destFlag: 0,
+ valueUrl: valueUrlBase
+ ? `${valueUrlBase.replace(/\/$/, "")}/timeseries/text/ignored`
+ : undefined,
+ });
+ }
+ });
+
+ return {
+ numeric: [...numericGroups.values()],
+ text: [...textGroups.values()],
+ };
+}
+
+function classifyCwmsDataUploadRows(rows, existingValues = []) {
+ const existing = new Set(
+ existingValues
+ .filter((item) => item?.[0] !== undefined && Number.isFinite(Number(item?.[1])))
+ .map((item) => {
+ const time = Number.isFinite(Number(item[0]))
+ ? Number(item[0])
+ : new Date(item[0]).getTime();
+ return `${time}|${Number(item[1]).toFixed(2)}`;
+ }),
+ );
+
+ return rows.map((row) => ({
+ ...row,
+ status:
+ row.value !== null && existing.has(`${row.epoch}|${Number(row.value).toFixed(2)}`)
+ ? "existing"
+ : "new",
+ }));
+}
+
+function filterCwmsDataUploadRows(rows, filter = "all") {
+ if (filter === "existing") return rows.filter((row) => row.status === "existing");
+ if (filter === "new") return rows.filter((row) => row.status !== "existing");
+ return rows;
+}
+
+export {
+ CWMS_DATA_UPLOAD_HEADERS,
+ CwmsDataUploadValidationError,
+ buildCwmsDataUploadPayloads,
+ classifyCwmsDataUploadRows,
+ createCwmsDataUploadTemplate,
+ filterCwmsDataUploadRows,
+ parseCwmsDataUploadRows,
+ readCwmsDataUploadFile,
+};
diff --git a/lib/components/data/forms/hooks/useCwmsFormSubmit.js b/lib/components/data/forms/hooks/useCwmsFormSubmit.js
index c42331c8..1f1f73c7 100644
--- a/lib/components/data/forms/hooks/useCwmsFormSubmit.js
+++ b/lib/components/data/forms/hooks/useCwmsFormSubmit.js
@@ -1,6 +1,7 @@
import React from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { TimeSeriesApi, TextTimeSeriesApi, BlobApi, Configuration } from "cwmsjs";
+import { buildCwmsDataUploadPayloads } from "../helpers/dataUpload";
/**
* Custom hook for submitting CWMS form data using TanStack Query
@@ -28,9 +29,9 @@ export function useCwmsFormSubmit({
const isSubmittingRef = React.useRef(false);
// Create configuration for CWMS APIs
- const createApiConfig = () => {
+ const createApiConfig = (inputCdaUrl = cdaUrl) => {
const configParams = {
- basePath: cdaUrl,
+ basePath: inputCdaUrl,
headers: {
accept: "application/json;version=2",
},
@@ -72,9 +73,66 @@ export function useCwmsFormSubmit({
const results = [];
const errors = [];
- // Process each input with TSID or blob
+ // Process each input with TSID, blob, or a parsed tabular upload.
for (const input of formInputs) {
- if (input.kind === "blob" || input.blobId) {
+ if (input.kind === "timeseries-batch") {
+ const batchConfig = input.cdaUrl ? createApiConfig(input.cdaUrl) : config;
+ const batchTsApi = new TimeSeriesApi(batchConfig);
+ const batchTextApi = new TextTimeSeriesApi(batchConfig);
+ const payloads = buildCwmsDataUploadPayloads(input.rows || [], {
+ unit: input.unit,
+ valueUrlBase: input.cdaUrl || cdaUrl,
+ });
+
+ for (const timeSeries of payloads.numeric) {
+ try {
+ await batchTsApi.postTimeSeries({
+ timeSeries,
+ timezone: "UTC",
+ createAsLrts: false,
+ storeRule: input.storeRule || storeRule,
+ overrideProtection: input.overrideProtection ?? true,
+ });
+ results.push({
+ tsid: timeSeries.name,
+ type: "numeric-batch",
+ status: "success",
+ valueCount: timeSeries.values.length,
+ });
+ } catch (error) {
+ errors.push({
+ tsid: timeSeries.name,
+ error: error.message || "Unknown error",
+ details: error,
+ response: error.response?.data,
+ status: error.response?.status,
+ });
+ }
+ }
+
+ for (const textTimeSeries of payloads.text) {
+ try {
+ await batchTextApi.postTimeSeriesText({
+ textTimeSeries,
+ replaceAll: true,
+ });
+ results.push({
+ tsid: textTimeSeries.name,
+ type: "text-batch",
+ status: "success",
+ valueCount: textTimeSeries.regularTextValues.length,
+ });
+ } catch (error) {
+ errors.push({
+ tsid: textTimeSeries.name,
+ error: error.message || "Unknown error",
+ details: error,
+ response: error.response?.data,
+ status: error.response?.status,
+ });
+ }
+ }
+ } else if (input.kind === "blob" || input.blobId) {
try {
const rawValue = input.value ?? input.values?.[0];
@@ -210,9 +268,17 @@ export function useCwmsFormSubmit({
onSuccess: (data, variables) => {
// Invalidate only the specific TSIDs that were submitted
if (variables) {
- const submittedTsids = variables
- .filter((input) => input.tsid)
- .map((input) => input.tsid);
+ const submittedTsids = [
+ ...new Set(
+ variables.flatMap((input) =>
+ input.kind === "timeseries-batch"
+ ? (input.rows || []).map((row) => row.tsid).filter(Boolean)
+ : input.tsid
+ ? [input.tsid]
+ : [],
+ ),
+ ),
+ ];
const submittedBlobIds = variables
.filter((input) => input.blobId)
.map((input) => input.blobId);
@@ -222,7 +288,7 @@ export function useCwmsFormSubmit({
queryClient.invalidateQueries({
predicate: (query) =>
query.queryKey[0] === "cda" &&
- query.queryKey[1] === "timeseries" &&
+ ["timeseries", "data-upload"].includes(query.queryKey[1]) &&
query.queryKey.includes(tsid),
});
});
diff --git a/lib/components/data/forms/inputs/CWMSDataUpload.jsx b/lib/components/data/forms/inputs/CWMSDataUpload.jsx
new file mode 100644
index 00000000..9c02facd
--- /dev/null
+++ b/lib/components/data/forms/inputs/CWMSDataUpload.jsx
@@ -0,0 +1,459 @@
+import React, { useContext, useEffect, useId, useMemo, useRef, useState } from "react";
+import {
+ Badge,
+ Button,
+ Dropdown,
+ Field,
+ Label,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableRow,
+ UsaceBox,
+ gwMerge,
+} from "@usace/groundwork";
+import { FormContext } from "../CWMSForm";
+import CWMSPlot from "../../plots/CWMSPlot";
+import useCwmsDataUpload from "../../hooks/useCwmsDataUpload";
+import {
+ createCwmsDataUploadTemplate,
+ parseCwmsDataUploadRows,
+ readCwmsDataUploadFile,
+} from "../helpers/dataUpload";
+
+const FILTER_OPTIONS = [
+ { value: "all", label: "All data points" },
+ { value: "existing", label: "Existing data points only" },
+ { value: "new", label: "New data points only" },
+];
+
+function toInitialModel(initialData, options) {
+ if (!initialData) return null;
+ if (Array.isArray(initialData)) return parseCwmsDataUploadRows(initialData, options);
+ if (Array.isArray(initialData.rows)) return initialData;
+ return null;
+}
+
+function CWMSDataUpload({
+ name = "cwmsDataUpload",
+ label = "CWMS tabular data upload",
+ helperText = "Upload an .xlsx workbook using the CWMS tabular data template.",
+ cdaUrl,
+ office,
+ unit = "ft",
+ timezone = "America/Chicago",
+ storeRule = "REPLACE_ALL",
+ overrideProtection = true,
+ maxRows = 200000,
+ maxIntradayYears = 10,
+ maxPreviewRows = 250,
+ defaultFilter = "all",
+ required = true,
+ disabled = false,
+ showPlot = true,
+ showDeleteButton = true,
+ loadExistingData = true,
+ initialData,
+ className = "",
+ onChange,
+ onDeleteSuccess,
+ onDeleteError,
+}) {
+ const formContext = useContext(FormContext);
+ const registerInput = formContext?.registerInput;
+ const inputId = useId();
+ const fileInputRef = useRef(null);
+ const options = useMemo(
+ () => ({ office, timezone, maxRows, maxIntradayYears }),
+ [maxIntradayYears, maxRows, office, timezone],
+ );
+ const [model, setModel] = useState(() => {
+ try {
+ return toInitialModel(initialData, options);
+ } catch {
+ return null;
+ }
+ });
+ const [fileName, setFileName] = useState("");
+ const [filter, setFilter] = useState(defaultFilter);
+ const [isReading, setIsReading] = useState(false);
+ const [isDragging, setIsDragging] = useState(false);
+ const [issues, setIssues] = useState([]);
+ const [isInvalid, setIsInvalid] = useState(false);
+ const [validationMessage, setValidationMessage] = useState("");
+ const [confirmDelete, setConfirmDelete] = useState(false);
+
+ const {
+ classifiedRows,
+ filteredRows,
+ existingData,
+ isLoadingExisting,
+ existingError,
+ refreshExisting,
+ deleteRows,
+ isDeleting,
+ deleteError,
+ } = useCwmsDataUpload({
+ model,
+ filter,
+ cdaUrl,
+ unit,
+ loadExistingData,
+ onDeleteSuccess,
+ onDeleteError,
+ });
+
+ const reset = () => {
+ setModel(null);
+ setFileName("");
+ setFilter(defaultFilter);
+ setIssues([]);
+ setIsInvalid(false);
+ setValidationMessage("");
+ setConfirmDelete(false);
+ if (fileInputRef.current) fileInputRef.current.value = "";
+ onChange?.(null);
+ };
+
+ const validate = () => {
+ if (issues.length) return issues[0];
+ if (required && !model?.rows?.length) return `${label} is required.`;
+ if (model && !filteredRows.length) return "The selected filter contains no rows.";
+ return null;
+ };
+
+ useEffect(() => {
+ if (!registerInput || disabled) return undefined;
+ return registerInput({
+ kind: "timeseries-batch",
+ name,
+ label,
+ required,
+ getValues: () => [model?.rows?.length ? String(model.rows.length) : ""],
+ getSubmissionData: () => ({
+ kind: "timeseries-batch",
+ name,
+ rows: filteredRows,
+ unit,
+ storeRule,
+ overrideProtection,
+ cdaUrl,
+ }),
+ validate,
+ reset,
+ setInvalid: setIsInvalid,
+ setValidationMessage,
+ });
+ }, [
+ cdaUrl,
+ disabled,
+ filteredRows,
+ issues,
+ label,
+ model,
+ name,
+ overrideProtection,
+ registerInput,
+ required,
+ storeRule,
+ unit,
+ ]);
+
+ const loadFile = async (file) => {
+ if (!file || disabled) return;
+ setIsReading(true);
+ setIssues([]);
+ setValidationMessage("");
+ setIsInvalid(false);
+ setConfirmDelete(false);
+ try {
+ const nextModel = await readCwmsDataUploadFile(file, options);
+ setModel(nextModel);
+ setFileName(file.name);
+ onChange?.(nextModel);
+ } catch (error) {
+ const nextIssues = error?.issues || [
+ error?.message || "Unable to read workbook.",
+ ];
+ setModel(null);
+ setFileName(file.name);
+ setIssues(nextIssues);
+ setIsInvalid(true);
+ setValidationMessage(nextIssues[0]);
+ onChange?.(null);
+ } finally {
+ setIsReading(false);
+ }
+ };
+
+ const downloadTemplate = async () => {
+ const buffer = await createCwmsDataUploadTemplate({ office });
+ const blob = new Blob([buffer], {
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement("a");
+ link.href = url;
+ link.download = "cwms-data-upload-template.xlsx";
+ document.body.appendChild(link);
+ link.click();
+ link.remove();
+ URL.revokeObjectURL(url);
+ };
+
+ const handleDelete = async () => {
+ if (!confirmDelete) {
+ setConfirmDelete(true);
+ return;
+ }
+ await deleteRows(filteredRows);
+ setConfirmDelete(false);
+ };
+
+ const previewRows = filteredRows.slice(0, maxPreviewRows);
+ const uploadPlotRows = filteredRows.filter((row) => row.value !== null);
+ const existingCount = classifiedRows.filter(
+ (row) => row.status === "existing",
+ ).length;
+ const newCount = classifiedRows.length - existingCount;
+ const plotData =
+ existingData ||
+ (model
+ ? { name: model.tsid, officeId: model.office, units: unit, values: [] }
+ : null);
+
+ return (
+
+
+ {label}
+ loadFile(event.target.files?.[0])}
+ />
+ {
+ event.preventDefault();
+ if (!disabled) setIsDragging(true);
+ }}
+ onDragLeave={() => setIsDragging(false)}
+ onDrop={(event) => {
+ event.preventDefault();
+ setIsDragging(false);
+ loadFile(event.dataTransfer?.files?.[0]);
+ }}
+ aria-invalid={isInvalid}
+ >
+
+
+ {isReading
+ ? "Reading workbook..."
+ : fileName || "Drag an .xlsx workbook here"}
+
+
{helperText}
+
+
+ fileInputRef.current?.click()}
+ >
+ Choose workbook
+
+
+ Download template
+
+ {model ? (
+
+ Clear
+
+ ) : null}
+
+
+
+
+ {issues.length ? (
+
+
Workbook validation failed
+
+ {issues.map((issue) => (
+ {issue}
+ ))}
+
+
+ ) : null}
+ {validationMessage && !issues.length ? (
+
+ {validationMessage}
+
+ ) : null}
+
+ {model ? (
+ <>
+
+
+ {model.office}
+
+ {model.tsid}
+
+ {classifiedRows.length.toLocaleString()} rows
+ {newCount.toLocaleString()} new
+ {existingCount.toLocaleString()} existing
+
+
+ {model.begin} through {model.end} · {unit} · {timezone}
+
+
+
+
+
+ Rows used by form actions
+ {
+ setFilter(event.target.value);
+ setConfirmDelete(false);
+ }}
+ options={FILTER_OPTIONS.map((option) => (
+
+ {option.label}
+
+ ))}
+ />
+
+ refreshExisting()}
+ >
+ {isLoadingExisting ? "Refreshing..." : "Refresh existing data"}
+
+ {showDeleteButton ? (
+
+ {isDeleting
+ ? "Deleting..."
+ : confirmDelete
+ ? `Confirm delete ${filteredRows.length.toLocaleString()} rows`
+ : "Delete selected range"}
+
+ ) : null}
+ {confirmDelete ? (
+ setConfirmDelete(false)}
+ >
+ Cancel
+
+ ) : null}
+
+
+ {existingError || deleteError ? (
+
+ {(existingError || deleteError)?.message || "The CDA request failed."}
+
+ ) : null}
+
+ {showPlot && uploadPlotRows.length ? (
+
new Date(row.epoch)),
+ y: uploadPlotRows.map((row) => row.value),
+ text: uploadPlotRows.map((row) => row.textValue),
+ name: "Workbook data",
+ mode: "markers",
+ type: "scatter",
+ },
+ ]}
+ layoutOptions={{
+ height: 420,
+ title: { text: "Workbook and existing time-series values" },
+ }}
+ />
+ ) : null}
+
+
+
+
+
+ Status
+ Office
+ TSID
+ Date time ({timezone})
+ Value
+ Quality
+ Text value
+
+
+
+ {previewRows.map((row) => (
+
+
+
+ {row.status}
+
+
+ {row.office}
+ {row.tsid}
+ {row.dateTime}
+ {row.value ?? ""}
+ {row.qualityCode}
+ {row.textValue}
+
+ ))}
+
+
+ {filteredRows.length > maxPreviewRows ? (
+
+ Showing the first {maxPreviewRows.toLocaleString()} of{" "}
+ {filteredRows.length.toLocaleString()} selected rows.
+
+ ) : null}
+
+ >
+ ) : null}
+
+ );
+}
+
+export default CWMSDataUpload;
+export { CWMSDataUpload };
diff --git a/lib/components/data/forms/inputs/__tests__/CWMSDataUpload.test.jsx b/lib/components/data/forms/inputs/__tests__/CWMSDataUpload.test.jsx
new file mode 100644
index 00000000..a574f074
--- /dev/null
+++ b/lib/components/data/forms/inputs/__tests__/CWMSDataUpload.test.jsx
@@ -0,0 +1,88 @@
+import React from "react";
+import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { CWMSForm } from "../../CWMSForm";
+import CWMSDataUpload from "../CWMSDataUpload";
+import { CWMS_DATA_UPLOAD_HEADERS } from "../../helpers/dataUpload";
+
+vi.mock("cwmsjs", async (importOriginal) => {
+ const actual = await importOriginal();
+ return {
+ ...actual,
+ TimeSeriesApi: class {
+ getTimeSeries = vi.fn();
+ postTimeSeries = vi.fn().mockResolvedValue(undefined);
+ deleteTimeSeriesWithTimeSeries = vi.fn().mockResolvedValue(undefined);
+ },
+ TextTimeSeriesApi: class {
+ postTimeSeriesText = vi.fn().mockResolvedValue(undefined);
+ deleteTimeSeriesTextWithName = vi.fn().mockResolvedValue(undefined);
+ },
+ };
+});
+
+const initialData = [
+ CWMS_DATA_UPLOAD_HEADERS,
+ ["MVS", "TEST.Stage.Inst.1Hour.0.TEST", "2026-01-01 00:00", "10.25", "0", "", "note"],
+];
+
+const renderUpload = (onSubmit = vi.fn()) => {
+ const queryClient = new QueryClient({
+ defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
+ });
+ render(
+
+
+
+
+ ,
+ );
+ return onSubmit;
+};
+
+describe("CWMSDataUpload", () => {
+ afterEach(() => vi.clearAllMocks());
+
+ it("renders parsed rows with Groundwork form controls", () => {
+ renderUpload();
+
+ expect(screen.getByText("Upload summary")).toBeTruthy();
+ expect(screen.getAllByText("TEST.Stage.Inst.1Hour.0.TEST").length).toBeGreaterThan(
+ 0,
+ );
+ expect(screen.getByText("10.25")).toBeTruthy();
+ expect(screen.getByRole("button", { name: "Download template" }).disabled).toBe(
+ false,
+ );
+ });
+
+ it("registers a batch payload with CWMSForm", async () => {
+ const onSubmit = renderUpload();
+ fireEvent.click(screen.getByRole("button", { name: "Submit" }));
+
+ await waitFor(() => expect(onSubmit).toHaveBeenCalled());
+ expect(onSubmit.mock.calls[0][0][0]).toMatchObject({
+ kind: "timeseries-batch",
+ unit: "ft",
+ rows: [
+ expect.objectContaining({
+ office: "MVS",
+ tsid: "TEST.Stage.Inst.1Hour.0.TEST",
+ value: 10.25,
+ }),
+ ],
+ });
+ });
+});
diff --git a/lib/components/data/hooks/useCwmsDataUpload.js b/lib/components/data/hooks/useCwmsDataUpload.js
new file mode 100644
index 00000000..1bf59a24
--- /dev/null
+++ b/lib/components/data/hooks/useCwmsDataUpload.js
@@ -0,0 +1,163 @@
+import { useContext, useMemo } from "react";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { Configuration, TextTimeSeriesApi, TimeSeriesApi } from "cwmsjs";
+import { AuthContext } from "../utilities/auth/AuthContext";
+import useCdaUrl from "../utilities/useCdaUrl";
+import {
+ classifyCwmsDataUploadRows,
+ filterCwmsDataUploadRows,
+} from "../forms/helpers/dataUpload";
+
+function useCwmsDataUpload({
+ model,
+ filter = "all",
+ cdaUrl,
+ unit = "ft",
+ loadExistingData = true,
+ onDeleteSuccess,
+ onDeleteError,
+} = {}) {
+ const providedCdaUrl = useCdaUrl();
+ const resolvedCdaUrl = cdaUrl ?? providedCdaUrl;
+ const auth = useContext(AuthContext);
+ const queryClient = useQueryClient();
+
+ const config = useMemo(() => {
+ const headers = { accept: "application/json;version=2" };
+ if (auth?.token) headers.Authorization = `Bearer ${auth.token}`;
+ return new Configuration({
+ basePath: resolvedCdaUrl,
+ headers,
+ credentials: "include",
+ });
+ }, [auth?.token, resolvedCdaUrl]);
+
+ const timeSeriesApi = useMemo(() => new TimeSeriesApi(config), [config]);
+ const textTimeSeriesApi = useMemo(() => new TextTimeSeriesApi(config), [config]);
+ const queryKey = [
+ "cda",
+ "data-upload",
+ model?.office,
+ model?.tsid,
+ model?.begin,
+ model?.end,
+ unit,
+ ];
+
+ const existingQuery = useQuery({
+ queryKey,
+ enabled: Boolean(
+ loadExistingData && model?.office && model?.tsid && model?.begin && model?.end,
+ ),
+ queryFn: async () => {
+ try {
+ return await timeSeriesApi.getTimeSeries({
+ name: model.tsid,
+ office: model.office,
+ begin: model.begin,
+ end: model.end,
+ unit,
+ timezone: "UTC",
+ pageSize: 1000000,
+ });
+ } catch (error) {
+ if (error?.response?.status === 404) {
+ return {
+ name: model.tsid,
+ officeId: model.office,
+ units: unit,
+ values: [],
+ };
+ }
+ throw error;
+ }
+ },
+ });
+
+ const classifiedRows = useMemo(
+ () =>
+ classifyCwmsDataUploadRows(model?.rows || [], existingQuery.data?.values || []),
+ [existingQuery.data?.values, model?.rows],
+ );
+ const filteredRows = useMemo(
+ () => filterCwmsDataUploadRows(classifiedRows, filter),
+ [classifiedRows, filter],
+ );
+
+ const deleteMutation = useMutation({
+ mutationFn: async (rows) => {
+ if (!rows?.length) throw new Error("Select at least one row to delete.");
+
+ const groups = new Map();
+ rows.forEach((row) => {
+ const key = `${row.office}|${row.tsid}`;
+ const current = groups.get(key) || {
+ office: row.office,
+ tsid: row.tsid,
+ begin: row.timestamp,
+ end: row.timestamp,
+ hasNumeric: false,
+ hasText: false,
+ };
+ if (row.epoch < new Date(current.begin).getTime())
+ current.begin = row.timestamp;
+ if (row.epoch > new Date(current.end).getTime()) current.end = row.timestamp;
+ current.hasNumeric ||= row.value !== null && row.value !== undefined;
+ current.hasText ||= Boolean(row.textValue);
+ groups.set(key, current);
+ });
+
+ const operations = [];
+ groups.forEach((group) => {
+ if (group.hasNumeric) {
+ operations.push(
+ timeSeriesApi.deleteTimeSeriesWithTimeSeries({
+ timeseries: group.tsid,
+ office: group.office,
+ begin: group.begin,
+ end: group.end,
+ timezone: "UTC",
+ startTimeInclusive: true,
+ endTimeInclusive: true,
+ overrideProtection: true,
+ }),
+ );
+ }
+ if (group.hasText) {
+ operations.push(
+ textTimeSeriesApi.deleteTimeSeriesTextWithName({
+ name: group.tsid,
+ office: group.office,
+ textMask: "*",
+ begin: group.begin,
+ end: group.end,
+ timezone: "UTC",
+ }),
+ );
+ }
+ });
+ await Promise.all(operations);
+ return { deletedRows: rows.length, operations: operations.length };
+ },
+ onSuccess: (result) => {
+ queryClient.invalidateQueries({ queryKey });
+ onDeleteSuccess?.(result);
+ },
+ onError: onDeleteError,
+ });
+
+ return {
+ classifiedRows,
+ filteredRows,
+ existingData: existingQuery.data,
+ isLoadingExisting: existingQuery.isLoading || existingQuery.isFetching,
+ existingError: existingQuery.error,
+ refreshExisting: existingQuery.refetch,
+ deleteRows: deleteMutation.mutateAsync,
+ isDeleting: deleteMutation.isPending,
+ deleteError: deleteMutation.error,
+ };
+}
+
+export { useCwmsDataUpload };
+export default useCwmsDataUpload;
diff --git a/lib/index.jsx b/lib/index.jsx
index 1ceb760e..c89ac1f2 100644
--- a/lib/index.jsx
+++ b/lib/index.jsx
@@ -19,6 +19,7 @@ import DataStatus from "./components/data/summary/DataStatus";
// Import input components
import CWMSInput from "./components/data/forms/inputs/CWMSInput";
import CWMSFileUpload from "./components/data/forms/inputs/CWMSFileUpload";
+import CWMSDataUpload from "./components/data/forms/inputs/CWMSDataUpload";
import CWMSTextarea from "./components/data/forms/inputs/CWMSTextarea";
import CWMSCheckboxes from "./components/data/forms/inputs/CWMSCheckboxes";
import CWMSRadioGroup from "./components/data/forms/inputs/CWMSRadioGroup";
@@ -55,6 +56,7 @@ import useCdaOffices from "./components/data/hooks/useCdaOffices";
import useNwpsGauge from "./components/data/hooks/useNwpsGauge";
import useNwpsGaugeData from "./components/data/hooks/useNwpsGaugeData";
import useDataStatusFile from "./components/data/hooks/useDataStatusFile";
+import useCwmsDataUpload from "./components/data/hooks/useCwmsDataUpload";
import {
fetchCdaLevelTimeSeries,
fetchCdaLevelValues,
@@ -66,6 +68,16 @@ import useCdaBlobs from "./components/data/hooks/useCdaBlobs";
// Utility Hooks
import useDebounce from "./components/data/utilities/useDebounce";
+import {
+ CWMS_DATA_UPLOAD_HEADERS,
+ CwmsDataUploadValidationError,
+ buildCwmsDataUploadPayloads,
+ classifyCwmsDataUploadRows,
+ createCwmsDataUploadTemplate,
+ filterCwmsDataUploadRows,
+ parseCwmsDataUploadRows,
+ readCwmsDataUploadFile,
+} from "./components/data/forms/helpers/dataUpload";
// Utility Functions
import {
PRECISION_BY_UNIT,
@@ -112,9 +124,18 @@ export {
useCdaTimeSeriesGroup,
useCdaOffices,
useDataStatusFile,
+ useCwmsDataUpload,
fetchCdaLevelTimeSeries,
fetchCdaLevelValues,
useDebounce,
+ CWMS_DATA_UPLOAD_HEADERS,
+ CwmsDataUploadValidationError,
+ buildCwmsDataUploadPayloads,
+ classifyCwmsDataUploadRows,
+ createCwmsDataUploadTemplate,
+ filterCwmsDataUploadRows,
+ parseCwmsDataUploadRows,
+ readCwmsDataUploadFile,
useNwpsGauge,
useNwpsGaugeData,
AuthProvider,
@@ -127,6 +148,7 @@ export {
// Input components
CWMSInput,
CWMSFileUpload,
+ CWMSDataUpload,
CWMSTextarea,
CWMSCheckboxes,
CWMSRadioGroup,
diff --git a/package-lock.json b/package-lock.json
index fcc8539b..aea417e5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,18 +1,19 @@
{
"name": "@usace-watermanagement/groundwork-water",
- "version": "3.11.0",
+ "version": "4.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@usace-watermanagement/groundwork-water",
- "version": "3.11.0",
+ "version": "4.0.0",
"license": "MIT",
"dependencies": {
"@tanstack/react-virtual": "^3.14.6",
"cwmsjs": "^2.3.0-2024.12.10",
"dayjs": "^1.11.11",
"deepmerge": "^4.3.1",
+ "exceljs": "^4.4.0",
"oidc-client-ts": "^3.5.0",
"ol": "^10.0.0",
"plotly.js-basic-dist": "^2.33.0",
@@ -866,9 +867,9 @@
}
},
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
- "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
+ "version": "1.1.18",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
+ "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -927,6 +928,47 @@
}
}
},
+ "node_modules/@fast-csv/format": {
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz",
+ "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "^14.0.1",
+ "lodash.escaperegexp": "^4.1.2",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isequal": "^4.5.0",
+ "lodash.isfunction": "^3.0.9",
+ "lodash.isnil": "^4.0.0"
+ }
+ },
+ "node_modules/@fast-csv/format/node_modules/@types/node": {
+ "version": "14.18.63",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz",
+ "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==",
+ "license": "MIT"
+ },
+ "node_modules/@fast-csv/parse": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz",
+ "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "^14.0.1",
+ "lodash.escaperegexp": "^4.1.2",
+ "lodash.groupby": "^4.6.0",
+ "lodash.isfunction": "^3.0.9",
+ "lodash.isnil": "^4.0.0",
+ "lodash.isundefined": "^3.0.1",
+ "lodash.uniq": "^4.5.0"
+ }
+ },
+ "node_modules/@fast-csv/parse/node_modules/@types/node": {
+ "version": "14.18.63",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz",
+ "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==",
+ "license": "MIT"
+ },
"node_modules/@floating-ui/core": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
@@ -1023,9 +1065,9 @@
}
},
"node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
- "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
+ "version": "1.1.18",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
+ "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1562,9 +1604,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1582,9 +1621,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1602,9 +1638,6 @@
"ppc64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1622,9 +1655,6 @@
"s390x"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1642,9 +1672,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1662,9 +1689,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1899,6 +1923,18 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/node": {
+ "version": "26.2.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz",
+ "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "undici-types": "~8.3.0"
+ }
+ },
"node_modules/@types/ol": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@types/ol/-/ol-7.0.0.tgz",
@@ -2780,6 +2816,81 @@
"node": ">= 8"
}
},
+ "node_modules/archiver": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz",
+ "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==",
+ "license": "MIT",
+ "dependencies": {
+ "archiver-utils": "^2.1.0",
+ "async": "^3.2.4",
+ "buffer-crc32": "^0.2.1",
+ "readable-stream": "^3.6.0",
+ "readdir-glob": "^1.1.2",
+ "tar-stream": "^2.2.0",
+ "zip-stream": "^4.1.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/archiver-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz",
+ "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
+ "license": "MIT",
+ "dependencies": {
+ "glob": "^7.1.4",
+ "graceful-fs": "^4.2.0",
+ "lazystream": "^1.0.0",
+ "lodash.defaults": "^4.2.0",
+ "lodash.difference": "^4.5.0",
+ "lodash.flatten": "^4.4.0",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.union": "^4.6.0",
+ "normalize-path": "^3.0.0",
+ "readable-stream": "^2.0.0"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/archiver-utils/node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "license": "MIT"
+ },
+ "node_modules/archiver-utils/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "license": "MIT",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/archiver-utils/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "license": "MIT"
+ },
+ "node_modules/archiver-utils/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
"node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
@@ -2966,7 +3077,6 @@
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
- "dev": true,
"license": "MIT"
},
"node_modules/async-function": {
@@ -3036,7 +3146,26 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
@@ -3075,6 +3204,28 @@
"require-from-string": "^2.0.2"
}
},
+ "node_modules/big-integer": {
+ "version": "1.6.52",
+ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
+ "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
+ "license": "Unlicense",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/binary": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz",
+ "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==",
+ "license": "MIT",
+ "dependencies": {
+ "buffers": "~0.1.1",
+ "chainsaw": "~0.1.0"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -3087,10 +3238,27 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/bluebird": {
+ "version": "3.4.7",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
+ "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==",
+ "license": "MIT"
+ },
"node_modules/brace-expansion": {
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
- "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
+ "version": "5.0.9",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
+ "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
@@ -3154,6 +3322,56 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/buffer-indexof-polyfill": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz",
+ "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/buffers": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
+ "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==",
+ "engines": {
+ "node": ">=0.2.0"
+ }
+ },
"node_modules/call-bind": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -3254,6 +3472,18 @@
"node": ">=18"
}
},
+ "node_modules/chainsaw": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
+ "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==",
+ "license": "MIT/X11",
+ "dependencies": {
+ "traverse": ">=0.3.0 <0.4"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -3398,11 +3628,25 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/compress-commons": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz",
+ "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-crc32": "^0.2.13",
+ "crc32-stream": "^4.0.2",
+ "normalize-path": "^3.0.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true,
"license": "MIT"
},
"node_modules/convert-source-map": {
@@ -3412,6 +3656,37 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+ "license": "MIT"
+ },
+ "node_modules/crc-32": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
+ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+ "license": "Apache-2.0",
+ "bin": {
+ "crc32": "bin/crc32.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/crc32-stream": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz",
+ "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==",
+ "license": "MIT",
+ "dependencies": {
+ "crc-32": "^1.2.0",
+ "readable-stream": "^3.4.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -3707,6 +3982,51 @@
"node": ">= 0.4"
}
},
+ "node_modules/duplexer2": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
+ "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "node_modules/duplexer2/node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "license": "MIT"
+ },
+ "node_modules/duplexer2/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "license": "MIT",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/duplexer2/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "license": "MIT"
+ },
+ "node_modules/duplexer2/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
"node_modules/earcut": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz",
@@ -3739,6 +4059,15 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
+ "node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
"node_modules/enquirer": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz",
@@ -4123,9 +4452,9 @@
}
},
"node_modules/eslint-plugin-react/node_modules/brace-expansion": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
- "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
+ "version": "1.1.18",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
+ "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4200,9 +4529,9 @@
}
},
"node_modules/eslint/node_modules/brace-expansion": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
- "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
+ "version": "1.1.18",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
+ "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4393,6 +4722,38 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/exceljs": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz",
+ "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==",
+ "license": "MIT",
+ "dependencies": {
+ "archiver": "^5.0.0",
+ "dayjs": "^1.8.34",
+ "fast-csv": "^4.3.1",
+ "jszip": "^3.10.1",
+ "readable-stream": "^3.6.0",
+ "saxes": "^5.0.1",
+ "tmp": "^0.2.0",
+ "unzipper": "^0.10.11",
+ "uuid": "^8.3.0"
+ },
+ "engines": {
+ "node": ">=8.3.0"
+ }
+ },
+ "node_modules/exceljs/node_modules/saxes": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
+ "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==",
+ "license": "ISC",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/expect-type": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
@@ -4410,6 +4771,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-csv": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz",
+ "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==",
+ "license": "MIT",
+ "dependencies": {
+ "@fast-csv/format": "4.3.5",
+ "@fast-csv/parse": "4.3.6"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -4627,6 +5001,12 @@
"url": "https://github.com/sponsors/rawify"
}
},
+ "node_modules/fs-constants": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+ "license": "MIT"
+ },
"node_modules/fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
@@ -4646,7 +5026,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
- "dev": true,
"license": "ISC"
},
"node_modules/fsevents": {
@@ -4663,6 +5042,35 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/fstream": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
+ "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
+ "deprecated": "This package is no longer supported.",
+ "license": "ISC",
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "inherits": "~2.0.0",
+ "mkdirp": ">=0.5 0",
+ "rimraf": "2"
+ },
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/fstream/node_modules/rimraf": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -4878,7 +5286,6 @@
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
- "dev": true,
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
@@ -4908,10 +5315,9 @@
}
},
"node_modules/glob/node_modules/brace-expansion": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
- "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
- "dev": true,
+ "version": "1.1.18",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
+ "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
@@ -4922,7 +5328,6 @@
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.4.tgz",
"integrity": "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==",
- "dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
@@ -5012,7 +5417,6 @@
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true,
"license": "ISC"
},
"node_modules/graphemer": {
@@ -5188,6 +5592,26 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
"node_modules/ignore": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
@@ -5198,6 +5622,12 @@
"node": ">= 4"
}
},
+ "node_modules/immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
+ "license": "MIT"
+ },
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -5240,7 +5670,6 @@
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
- "dev": true,
"license": "ISC",
"dependencies": {
"once": "^1.3.0",
@@ -5251,7 +5680,6 @@
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true,
"license": "ISC"
},
"node_modules/internal-nav-helper": {
@@ -5940,6 +6368,60 @@
"node": ">=4.0"
}
},
+ "node_modules/jszip": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
+ "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
+ "license": "(MIT OR GPL-3.0-or-later)",
+ "dependencies": {
+ "lie": "~3.3.0",
+ "pako": "~1.0.2",
+ "readable-stream": "~2.3.6",
+ "setimmediate": "^1.0.5"
+ }
+ },
+ "node_modules/jszip/node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "license": "MIT"
+ },
+ "node_modules/jszip/node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "license": "(MIT AND Zlib)"
+ },
+ "node_modules/jszip/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "license": "MIT",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/jszip/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "license": "MIT"
+ },
+ "node_modules/jszip/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
"node_modules/jwt-decode": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
@@ -5959,6 +6441,54 @@
"json-buffer": "3.0.1"
}
},
+ "node_modules/lazystream": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
+ "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
+ "license": "MIT",
+ "dependencies": {
+ "readable-stream": "^2.0.5"
+ },
+ "engines": {
+ "node": ">= 0.6.3"
+ }
+ },
+ "node_modules/lazystream/node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "license": "MIT"
+ },
+ "node_modules/lazystream/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "license": "MIT",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/lazystream/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "license": "MIT"
+ },
+ "node_modules/lazystream/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
"node_modules/lerc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz",
@@ -5979,6 +6509,15 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/lie": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "immediate": "~3.0.5"
+ }
+ },
"node_modules/lightningcss": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
@@ -6122,9 +6661,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -6146,9 +6682,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -6170,9 +6703,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -6194,9 +6724,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -6305,6 +6832,12 @@
"node": ">=20"
}
},
+ "node_modules/listenercount": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz",
+ "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==",
+ "license": "ISC"
+ },
"node_modules/listr2": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz",
@@ -6336,6 +6869,73 @@
"node": ">=8"
}
},
+ "node_modules/lodash.defaults": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
+ "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.difference": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
+ "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.escaperegexp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
+ "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.flatten": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
+ "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.groupby": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz",
+ "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isequal": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
+ "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isfunction": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz",
+ "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isnil": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz",
+ "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isundefined": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz",
+ "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==",
+ "license": "MIT"
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -6350,6 +6950,18 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/lodash.union": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
+ "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.uniq": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
+ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
+ "license": "MIT"
+ },
"node_modules/log-update": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
@@ -6545,6 +7157,15 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
@@ -6554,6 +7175,18 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
"node_modules/mri": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@@ -6812,7 +7445,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
"license": "ISC",
"dependencies": {
"wrappy": "1"
@@ -7017,7 +7649,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -7408,6 +8039,12 @@
"license": "MIT",
"peer": true
},
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+ "license": "MIT"
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -7614,6 +8251,50 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/readdir-glob": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz",
+ "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "minimatch": "^5.1.0"
+ }
+ },
+ "node_modules/readdir-glob/node_modules/brace-expansion": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
+ "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/readdir-glob/node_modules/minimatch": {
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
+ "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -7879,6 +8560,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/safe-push-apply": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
@@ -8006,6 +8707,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -8217,6 +8924,15 @@
"node": ">= 0.4"
}
},
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
"node_modules/string-argv": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
@@ -8613,6 +9329,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/tar-stream": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "bl": "^4.0.3",
+ "end-of-stream": "^1.4.1",
+ "fs-constants": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/term-size": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",
@@ -8749,6 +9481,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/tmp": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz",
+ "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -8787,6 +9528,15 @@
"node": ">=20"
}
},
+ "node_modules/traverse": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
+ "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==",
+ "license": "MIT/X11",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/trim-repeated": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
@@ -8983,6 +9733,15 @@
"node": ">=20.18.1"
}
},
+ "node_modules/undici-types": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
+ "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true
+ },
"node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
@@ -9002,6 +9761,60 @@
"node": ">=18"
}
},
+ "node_modules/unzipper": {
+ "version": "0.10.14",
+ "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz",
+ "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==",
+ "license": "MIT",
+ "dependencies": {
+ "big-integer": "^1.6.17",
+ "binary": "~0.3.0",
+ "bluebird": "~3.4.1",
+ "buffer-indexof-polyfill": "~1.0.0",
+ "duplexer2": "~0.1.4",
+ "fstream": "^1.0.12",
+ "graceful-fs": "^4.2.2",
+ "listenercount": "~1.0.1",
+ "readable-stream": "~2.3.6",
+ "setimmediate": "~1.0.4"
+ }
+ },
+ "node_modules/unzipper/node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "license": "MIT"
+ },
+ "node_modules/unzipper/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "license": "MIT",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/unzipper/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "license": "MIT"
+ },
+ "node_modules/unzipper/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
"node_modules/update-browserslist-db": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
@@ -9059,6 +9872,16 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
+ "node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).",
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"node_modules/vite": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz",
@@ -9568,7 +10391,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "dev": true,
"license": "ISC"
},
"node_modules/xml-name-validator": {
@@ -9591,7 +10413,6 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
- "dev": true,
"license": "MIT"
},
"node_modules/yallist": {
@@ -9640,6 +10461,41 @@
"numcodecs": "^0.3.2"
}
},
+ "node_modules/zip-stream": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz",
+ "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==",
+ "license": "MIT",
+ "dependencies": {
+ "archiver-utils": "^3.0.4",
+ "compress-commons": "^4.1.2",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/zip-stream/node_modules/archiver-utils": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz",
+ "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==",
+ "license": "MIT",
+ "dependencies": {
+ "glob": "^7.2.3",
+ "graceful-fs": "^4.2.0",
+ "lazystream": "^1.0.0",
+ "lodash.defaults": "^4.2.0",
+ "lodash.difference": "^4.5.0",
+ "lodash.flatten": "^4.4.0",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.union": "^4.6.0",
+ "normalize-path": "^3.0.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/zod": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
diff --git a/package.json b/package.json
index 5fb63e49..23018d73 100644
--- a/package.json
+++ b/package.json
@@ -111,6 +111,7 @@
"cwmsjs": "^2.3.0-2024.12.10",
"dayjs": "^1.11.11",
"deepmerge": "^4.3.1",
+ "exceljs": "^4.4.0",
"oidc-client-ts": "^3.5.0",
"ol": "^10.0.0",
"plotly.js-basic-dist": "^2.33.0",