From 2a4d109ee6a7622041592db9d8c76c8214e63bcd Mon Sep 17 00:00:00 2001 From: Pouzor Date: Wed, 17 Jun 2026 11:55:55 +0200 Subject: [PATCH 1/4] fix: persist manual node sizes and add width/height inputs Nested vm/lxc/docker leaf nodes lost their size on reload: the deserialize restore branch excluded those types entirely, so a resized child snapped back to content-fit. Gate the restore on container_mode instead of node type. Also prefer explicit width/height over the DOM-measured value when serializing, so a manual resize persists its exact target rather than drifting to the fractional content-fit size. Add a Size section to the detail panel with W/H number inputs (setNodeSize store action, clamped to the resizer minimums). Inputs resync live when the node is resized by corner drag, without clobbering active keystrokes. ha-relevant: yes --- .../src/components/panels/DetailPanel.tsx | 82 ++++++++++++++++++- .../panels/__tests__/DetailPanel.test.tsx | 66 +++++++++++++++ .../src/stores/__tests__/canvasStore.test.ts | 25 ++++++ frontend/src/stores/canvasStore.ts | 17 ++++ .../utils/__tests__/canvasSerializer.test.ts | 34 +++++++- frontend/src/utils/canvasSerializer.ts | 23 ++++-- 6 files changed, 237 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/panels/DetailPanel.tsx b/frontend/src/components/panels/DetailPanel.tsx index 6508675..7b5f748 100644 --- a/frontend/src/components/panels/DetailPanel.tsx +++ b/frontend/src/components/panels/DetailPanel.tsx @@ -21,7 +21,7 @@ type PropForm = { key: string; value: string; icon: string | null; visible: bool const EMPTY_PROP: PropForm = { key: '', value: '', icon: null, visible: true } export function DetailPanel({ onEdit }: DetailPanelProps) { - const { nodes, selectedNodeId, selectedNodeIds, setSelectedNode, deleteNode, updateNode, snapshotHistory, createGroup, ungroup, removeFromGroup } = useCanvasStore() + const { nodes, selectedNodeId, selectedNodeIds, setSelectedNode, deleteNode, updateNode, snapshotHistory, createGroup, ungroup, removeFromGroup, setNodeSize } = useCanvasStore() const serviceStatuses = useCanvasStore((s) => s.serviceStatuses) const [addingForNode, setAddingForNode] = useState(null) @@ -255,6 +255,13 @@ export function DetailPanel({ onEdit }: DetailPanelProps) { {data.last_seen && } + {/* Size section — manual width/height entry for pixel-exact sizing */} + { snapshotHistory(); setNodeSize(node.id, size) }} + /> + {/* Properties section */}
@@ -345,6 +352,79 @@ export function DetailPanel({ onEdit }: DetailPanelProps) { ) } +// --- Size fields (manual width/height entry) --- + +const round = (v: number | undefined): string => (v != null ? String(Math.round(v)) : '') + +function SizeFields({ node, onCommit }: { node: Node; onCommit: (size: { width?: number; height?: number }) => void }) { + // Live size from the explicit dimension, falling back to the DOM-measured + // size so the field shows the node's current footprint even before resize. + const liveWidth = round(node.width ?? node.measured?.width) + const liveHeight = round(node.height ?? node.measured?.height) + const [width, setWidth] = useState(liveWidth) + const [height, setHeight] = useState(liveHeight) + + // Resync the fields when the node is resized by dragging its corner handle + // (which mutates node.width/height in the store). Adjusting state during + // render — the React-blessed alternative to an effect — keeps it in step + // without cascading renders. The field the user is actively editing is left + // alone so we don't clobber their keystrokes mid-type. + const [editing, setEditing] = useState<'width' | 'height' | null>(null) + const [prev, setPrev] = useState({ w: liveWidth, h: liveHeight }) + if (prev.w !== liveWidth || prev.h !== liveHeight) { + setPrev({ w: liveWidth, h: liveHeight }) + if (editing !== 'width') setWidth(liveWidth) + if (editing !== 'height') setHeight(liveHeight) + } + + const commit = (raw: string, axis: 'width' | 'height') => { + const n = Number(raw) + if (!raw.trim() || Number.isNaN(n) || n <= 0) { + // Reset the field to the live value on invalid input — no mutation. + if (axis === 'width') setWidth(round(node.width ?? node.measured?.width)) + else setHeight(round(node.height ?? node.measured?.height)) + return + } + onCommit({ [axis]: n }) + } + + return ( +
+ Size +
+ + +
+
+ ) +} + // --- Multi-select panel --- interface MultiSelectPanelProps { diff --git a/frontend/src/components/panels/__tests__/DetailPanel.test.tsx b/frontend/src/components/panels/__tests__/DetailPanel.test.tsx index c610eab..e81e1d5 100644 --- a/frontend/src/components/panels/__tests__/DetailPanel.test.tsx +++ b/frontend/src/components/panels/__tests__/DetailPanel.test.tsx @@ -36,6 +36,7 @@ function setupStore(nodeData: Partial = {}, serviceStatuses: Record { expect(row?.textContent).not.toMatch(/Invalid Date/) }) }) + + describe('Size section', () => { + function setupSized(node: Partial>, setNodeSize = vi.fn()) { + const state = { + nodes: [{ ...makeNode({}), ...node }], + selectedNodeId: 'n1', + selectedNodeIds: [], + setSelectedNode: vi.fn(), + deleteNode: vi.fn(), + updateNode: vi.fn(), + snapshotHistory: vi.fn(), + createGroup: vi.fn(), + ungroup: vi.fn(), + setNodeSize, + serviceStatuses: {}, + } + vi.mocked(canvasStore.useCanvasStore).mockImplementation( + ((sel?: (s: typeof state) => unknown) => (sel ? sel(state) : state)) as unknown as typeof canvasStore.useCanvasStore, + ) + return setNodeSize + } + + it('shows the current width/height, preferring explicit over measured', () => { + setupSized({ width: 220, height: 130, measured: { width: 999, height: 999 } }) + render() + expect((screen.getByLabelText('Width') as HTMLInputElement).value).toBe('220') + expect((screen.getByLabelText('Height') as HTMLInputElement).value).toBe('130') + }) + + it('falls back to the measured size before a resize', () => { + setupSized({ measured: { width: 187, height: 73 } }) + render() + expect((screen.getByLabelText('Width') as HTMLInputElement).value).toBe('187') + expect((screen.getByLabelText('Height') as HTMLInputElement).value).toBe('73') + }) + + it('commits a manual width on blur', () => { + const setNodeSize = setupSized({ width: 200, height: 100 }) + render() + const w = screen.getByLabelText('Width') + fireEvent.change(w, { target: { value: '260' } }) + fireEvent.blur(w) + expect(setNodeSize).toHaveBeenCalledWith('n1', { width: 260 }) + }) + + it('reverts invalid input without committing', () => { + const setNodeSize = setupSized({ width: 200, height: 100 }) + render() + const w = screen.getByLabelText('Width') as HTMLInputElement + fireEvent.change(w, { target: { value: 'abc' } }) + fireEvent.blur(w) + expect(setNodeSize).not.toHaveBeenCalled() + expect(w.value).toBe('200') + }) + + it('resyncs the field when the node is resized by corner drag', () => { + setupSized({ width: 200, height: 100 }) + const { rerender } = render() + expect((screen.getByLabelText('Width') as HTMLInputElement).value).toBe('200') + // Simulate a corner-drag resize updating the store dimension. + setupSized({ width: 340, height: 100 }) + rerender() + expect((screen.getByLabelText('Width') as HTMLInputElement).value).toBe('340') + }) + }) }) diff --git a/frontend/src/stores/__tests__/canvasStore.test.ts b/frontend/src/stores/__tests__/canvasStore.test.ts index bda3b00..3f39c0a 100644 --- a/frontend/src/stores/__tests__/canvasStore.test.ts +++ b/frontend/src/stores/__tests__/canvasStore.test.ts @@ -1263,4 +1263,29 @@ describe('canvasStore — custom style apply', () => { expect(e.data?.custom_color).toBe('#aabbcc') expect(useCanvasStore.getState().hasUnsavedChanges).toBe(true) }) + + it('setNodeSize sets explicit width/height and marks unsaved', () => { + useCanvasStore.setState({ nodes: [makeNode('n1')], hasUnsavedChanges: false }) + useCanvasStore.getState().setNodeSize('n1', { width: 220, height: 130 }) + const n = useCanvasStore.getState().nodes.find((x) => x.id === 'n1')! + expect(n.width).toBe(220) + expect(n.height).toBe(130) + expect(useCanvasStore.getState().hasUnsavedChanges).toBe(true) + }) + + it('setNodeSize clamps below the minimum box', () => { + useCanvasStore.setState({ nodes: [makeNode('n1')] }) + useCanvasStore.getState().setNodeSize('n1', { width: 10, height: 10 }) + const n = useCanvasStore.getState().nodes.find((x) => x.id === 'n1')! + expect(n.width).toBe(140) + expect(n.height).toBe(50) + }) + + it('setNodeSize updates only the provided axis', () => { + useCanvasStore.setState({ nodes: [{ ...makeNode('n1'), width: 200, height: 100 }] }) + useCanvasStore.getState().setNodeSize('n1', { width: 300 }) + const n = useCanvasStore.getState().nodes.find((x) => x.id === 'n1')! + expect(n.width).toBe(300) + expect(n.height).toBe(100) + }) }) diff --git a/frontend/src/stores/canvasStore.ts b/frontend/src/stores/canvasStore.ts index c3ecec6..baad428 100644 --- a/frontend/src/stores/canvasStore.ts +++ b/frontend/src/stores/canvasStore.ts @@ -61,6 +61,7 @@ interface CanvasState { deleteEdge: (id: string) => void setProxmoxContainerMode: (proxmoxId: string, enabled: boolean) => void setNodeZIndex: (id: string, zIndex: number) => void + setNodeSize: (id: string, size: { width?: number; height?: number }) => void editingGroupRectId: string | null setEditingGroupRectId: (id: string | null) => void editingTextId: string | null @@ -466,6 +467,22 @@ export const useCanvasStore = create((set) => ({ hasUnsavedChanges: true, })), + // Manual width/height entry. Lets the user type an exact size instead of + // dragging the resize handle (which lands on fractional content-fit pixels). + // A clamp matches the NodeResizer minimums so the box can't collapse. + setNodeSize: (id, size) => + set((state) => ({ + nodes: state.nodes.map((n) => { + if (n.id !== id) return n + return { + ...n, + ...(size.width != null ? { width: Math.max(140, size.width) } : {}), + ...(size.height != null ? { height: Math.max(50, size.height) } : {}), + } + }), + hasUnsavedChanges: true, + })), + setEditingGroupRectId: (id) => set({ editingGroupRectId: id }), setEditingTextId: (id) => set({ editingTextId: id }), diff --git a/frontend/src/utils/__tests__/canvasSerializer.test.ts b/frontend/src/utils/__tests__/canvasSerializer.test.ts index a38f729..f609463 100644 --- a/frontend/src/utils/__tests__/canvasSerializer.test.ts +++ b/frontend/src/utils/__tests__/canvasSerializer.test.ts @@ -102,6 +102,20 @@ describe('serializeNode — regular node', () => { expect(result.height).toBeNull() }) + it('prefers explicit width/height over the measured (content-fit) value', () => { + const node: Node = { ...makeRfNode({ width: 200, height: 100 }), measured: { width: 187, height: 73 } } + const result = serializeNode(node) + expect(result.width).toBe(200) + expect(result.height).toBe(100) + }) + + it('falls back to measured when no explicit dimension is set', () => { + const node: Node = { ...makeRfNode(), measured: { width: 187, height: 73 } } + const result = serializeNode(node) + expect(result.width).toBe(187) + expect(result.height).toBe(73) + }) + it('serializes hardware fields', () => { const node = makeRfNode({ data: { @@ -162,13 +176,14 @@ describe('serializeNode — groupRect', () => { expect((result.custom_colors as Record).height).toBe(250) }) - it('falls back to measured dimensions over explicit width/height', () => { + it('prefers explicit width/height over measured, falling back to measured per-axis', () => { const node: Node = { ...makeRfNode({ type: 'groupRect', data: { label: 'Z', type: 'groupRect', status: 'unknown', services: [] }, width: 400 }), measured: { width: 420, height: 260 }, } const result = serializeNode(node) - expect((result.custom_colors as Record).width).toBe(420) + // Explicit width wins; height has no explicit value so it uses measured. + expect((result.custom_colors as Record).width).toBe(400) expect((result.custom_colors as Record).height).toBe(260) }) @@ -323,6 +338,21 @@ describe('deserializeApiNode — regular node', () => { expect(result.width).toBeUndefined() expect(result.height).toBeUndefined() }) + + // Regression: issue #205 — a leaf vm/lxc nested in a container would lose its + // saved size on reload because the restore branch excluded those types. + it.each(['vm', 'lxc', 'docker_host'] as const)( + 'restores saved width/height for a leaf %s node (container_mode false)', + (type) => { + const map = new Map([['px1', true]]) + const result = deserializeApiNode( + makeApiNode({ type, container_mode: false, parent_id: 'px1', width: 240, height: 90 }), + map, + ) + expect(result.width).toBe(240) + expect(result.height).toBe(90) + }, + ) }) // ── deserializeApiNode — groupRect ──────────────────────────────────────────── diff --git a/frontend/src/utils/canvasSerializer.ts b/frontend/src/utils/canvasSerializer.ts index 9e8488e..f3fc36c 100644 --- a/frontend/src/utils/canvasSerializer.ts +++ b/frontend/src/utils/canvasSerializer.ts @@ -75,8 +75,8 @@ export function serializeNode(n: Node): Record { pos_y: n.position.y, custom_colors: { ...n.data.custom_colors, - width: n.measured?.width ?? n.width ?? 360, - height: n.measured?.height ?? n.height ?? 240, + width: n.width ?? n.measured?.width ?? 360, + height: n.height ?? n.measured?.height ?? 240, // Stash collapse state inside custom_colors so the API/YAML blob does // not need a new column. Hoisted back to `data.collapsed` on load. collapsed: n.data.collapsed ?? false, @@ -112,8 +112,11 @@ export function serializeNode(n: Node): Record { disk_gb: n.data.disk_gb ?? null, show_hardware: n.data.show_hardware ?? false, properties: n.data.properties ?? [], - width: n.measured?.width ?? n.width ?? null, - height: n.measured?.height ?? n.height ?? null, + // Prefer the explicit (resized) dimension over the DOM-measured one so a + // manual resize persists its exact target instead of drifting to the + // fractional content-fit value. + width: n.width ?? n.measured?.width ?? null, + height: n.height ?? n.measured?.height ?? null, bottom_handles: clampBottomHandles(n.data.bottom_handles ?? 1), show_port_numbers: n.data.show_port_numbers ?? false, pos_x: n.position.x, @@ -179,11 +182,17 @@ export function deserializeApiNode( collapsed: Boolean(n.custom_colors?.collapsed), } as unknown as NodeData, ...(n.parent_id && parentIsContainer ? { parentId: n.parent_id, extent: 'parent' as const } : {}), + // Container hosts (Proxmox/VM/LXC/docker in container_mode) get a default + // box if none was saved. Every other node — including LEAF vm/lxc/docker + // nodes nested inside a container — restores its own saved width/height. + // Gating on container_mode (not type) is what keeps a resized nested node + // from snapping back to content-fit on reload. ...(['proxmox', 'vm', 'lxc', 'docker_host'].includes(normalizedType) && n.container_mode !== false ? { width: n.width ?? 300, height: n.height ?? 200 } - : {}), - ...(n.width && !['proxmox', 'vm', 'lxc', 'docker_host'].includes(normalizedType) ? { width: n.width } : {}), - ...(n.height && !['proxmox', 'vm', 'lxc', 'docker_host'].includes(normalizedType) ? { height: n.height } : {}), + : { + ...(n.width ? { width: n.width } : {}), + ...(n.height ? { height: n.height } : {}), + }), } } From 6fba0cdec44453400c037f56bf5b60f9fdae16af Mon Sep 17 00:00:00 2001 From: Pouzor Date: Wed, 17 Jun 2026 14:23:49 +0200 Subject: [PATCH 2/4] fix: don't trap new child nodes under a non-container parent Adding an LXC/VM with a parent_id pointed at a Proxmox node that is NOT in container_mode set extent:'parent' anyway, confining the node to the parent's ~140px bounding box with no way to drag it out. Nesting is now gated on the parent's container_mode in both places: App.handleAddNode no longer sets parentId/extent itself (it seeds an absolute position and lets addNode decide), and addNode strips any stray parentId/extent when it isn't nesting. New children of a real container still land at the container's top-left. ha-relevant: yes --- frontend/src/App.tsx | 14 ++++++++++---- .../src/stores/__tests__/canvasStore.test.ts | 17 +++++++++++++++++ frontend/src/stores/canvasStore.ts | 4 +++- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d18b3b1..069ecb6 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -245,9 +245,16 @@ export default function App() { const id = generateUUID() const isContainerNode = data.container_mode === true const parentNode = data.parent_id ? nodes.find((n) => n.id === data.parent_id) : null - // Children position is relative to parent; place near top-left with padding - const position = parentNode - ? { x: 20, y: 50 } + // Only nest when the parent is an actual container. For a non-container + // parent the LXC/VM stays a free node (linked by a virtual edge) — setting + // extent:'parent' on a non-container would trap it inside the parent's tiny + // bounding box with no way to drag it out (issue #205 follow-up). + const nestInParent = !!parentNode?.data.container_mode + // Seed an ABSOLUTE position near the container's top-left; addNode converts + // it to container-relative. addNode is the single authority for parentId / + // extent, so we don't set them here. + const position = nestInParent && parentNode + ? { x: parentNode.position.x + 20, y: parentNode.position.y + 50 } : { x: 300, y: 300 } const newNode: Node = { @@ -255,7 +262,6 @@ export default function App() { type: data.type ?? 'generic', position, data: { status: 'unknown', services: [], ...data } as NodeData, - ...(data.parent_id ? { parentId: data.parent_id, extent: 'parent' as const } : {}), ...(isContainerNode ? { width: 300, height: 200 } : {}), } addNode(newNode) diff --git a/frontend/src/stores/__tests__/canvasStore.test.ts b/frontend/src/stores/__tests__/canvasStore.test.ts index 3f39c0a..9c69b99 100644 --- a/frontend/src/stores/__tests__/canvasStore.test.ts +++ b/frontend/src/stores/__tests__/canvasStore.test.ts @@ -150,6 +150,23 @@ describe('canvasStore', () => { expect(nested?.extent).toBe('parent') }) + it('addNode strips parentId/extent when the parent is not a container', () => { + // Regression: a stray extent:'parent' on a non-container parent traps the + // node in the parent's tiny box with no way to drag it out (issue #205). + const parent = { ...makeNode('p1', { container_mode: false }), position: { x: 100, y: 100 } } + useCanvasStore.getState().addNode(parent) + const trapped: Node = { + ...makeNode('c1', { parent_id: 'p1' }), + position: { x: 150, y: 180 }, + parentId: 'p1', + extent: 'parent', + } + useCanvasStore.getState().addNode(trapped) + const child = useCanvasStore.getState().nodes.find((n) => n.id === 'c1') + expect(child?.parentId).toBeUndefined() + expect(child?.extent).toBeUndefined() + }) + it('docker_container nests under docker_host with container_mode on', () => { const host = { ...makeNode('dh1', { type: 'docker_host', container_mode: true }), position: { x: 100, y: 100 } } const container = { ...makeNode('dc1', { type: 'docker_container' }), position: { x: 160, y: 180 } } diff --git a/frontend/src/stores/canvasStore.ts b/frontend/src/stores/canvasStore.ts index baad428..b1ba24e 100644 --- a/frontend/src/stores/canvasStore.ts +++ b/frontend/src/stores/canvasStore.ts @@ -289,7 +289,9 @@ export const useCanvasStore = create((set) => ({ y: Math.max(10, node.position.y - parent.position.y), }, } - : node + // Not nesting: strip any parentId/extent a caller may have set so a + // non-container parent can't trap the node in its bounding box. + : { ...node, parentId: undefined, extent: undefined } // Parents must come before children in the array (React Flow requirement) const withoutNew = state.nodes.filter((n) => n.id !== node.id) if (enriched.parentId) { From 0796c96fc178b546abf227ec789d2b4d5fcd60ad Mon Sep 17 00:00:00 2001 From: Pouzor Date: Wed, 17 Jun 2026 16:49:25 +0200 Subject: [PATCH 3/4] chore: resolve high-severity npm audit advisories The dependency-audit CI step (npm audit --audit-level=high) was failing on pre-existing transitive advisories. Force patched versions via overrides and bump vite to a patched 7.x: - esbuild ^0.28.1 (GHSA-g7r4-m6w7-qqqr, high) - form-data ^4.0.6 (GHSA-hmw2-7cc7-3qxx, high) - vite ^7.3.5 (GHSA-v6wh-96g9-6wx3 / GHSA-fx2h-pf6j-xcff, high) Audit now passes at --audit-level=high (only a low @babel/core and moderate js-yaml remain, both below the gate and build/dev-time only; js-yaml is a direct dep so it can't be overridden). Build + full test suite (1130) green on the bumped toolchain. ha-relevant: no --- frontend/package-lock.json | 238 ++++++++++++++++++------------------- frontend/package.json | 6 +- 2 files changed, 123 insertions(+), 121 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 03f930a..31e884e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -52,7 +52,7 @@ "tailwindcss": "^4.2.1", "typescript": "~5.9.3", "typescript-eslint": "^8.48.0", - "vite": "^7.3.1", + "vite": "^7.3.5", "vitest": "^4.0.18" } }, @@ -964,9 +964,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", "cpu": [ "ppc64" ], @@ -981,9 +981,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", "cpu": [ "arm" ], @@ -998,9 +998,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", "cpu": [ "arm64" ], @@ -1015,9 +1015,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", "cpu": [ "x64" ], @@ -1032,9 +1032,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", - "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], @@ -1049,9 +1049,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", "cpu": [ "x64" ], @@ -1066,9 +1066,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", "cpu": [ "arm64" ], @@ -1083,9 +1083,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", "cpu": [ "x64" ], @@ -1100,9 +1100,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", "cpu": [ "arm" ], @@ -1117,9 +1117,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", "cpu": [ "arm64" ], @@ -1134,9 +1134,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", "cpu": [ "ia32" ], @@ -1151,9 +1151,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", "cpu": [ "loong64" ], @@ -1168,9 +1168,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", "cpu": [ "mips64el" ], @@ -1185,9 +1185,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", "cpu": [ "ppc64" ], @@ -1202,9 +1202,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", "cpu": [ "riscv64" ], @@ -1219,9 +1219,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", "cpu": [ "s390x" ], @@ -1236,9 +1236,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", "cpu": [ "x64" ], @@ -1253,9 +1253,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", - "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", "cpu": [ "arm64" ], @@ -1270,9 +1270,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", "cpu": [ "x64" ], @@ -1287,9 +1287,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", "cpu": [ "arm64" ], @@ -1304,9 +1304,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", "cpu": [ "x64" ], @@ -1321,9 +1321,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", "cpu": [ "arm64" ], @@ -1338,9 +1338,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", "cpu": [ "x64" ], @@ -1355,9 +1355,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", "cpu": [ "arm64" ], @@ -1372,9 +1372,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", "cpu": [ "ia32" ], @@ -1389,9 +1389,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", "cpu": [ "x64" ], @@ -5216,9 +5216,9 @@ } }, "node_modules/esbuild": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", - "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -5229,32 +5229,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" } }, "node_modules/escalade": { @@ -5877,16 +5877,16 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -6161,9 +6161,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -9267,9 +9267,9 @@ } }, "node_modules/vite": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", - "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz", + "integrity": "sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==", "dev": true, "license": "MIT", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index beef11a..c12fc3f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -38,7 +38,9 @@ "zustand": "^5.0.11" }, "overrides": { - "hono": "^4.12.21" + "hono": "^4.12.21", + "esbuild": "^0.28.1", + "form-data": "^4.0.6" }, "devDependencies": { "@eslint/js": "^9.39.1", @@ -61,7 +63,7 @@ "tailwindcss": "^4.2.1", "typescript": "~5.9.3", "typescript-eslint": "^8.48.0", - "vite": "^7.3.1", + "vite": "^7.3.5", "vitest": "^4.0.18" } } From 6e5bda58607ce08aa9384ca182efa030a92667a6 Mon Sep 17 00:00:00 2001 From: Pouzor Date: Wed, 17 Jun 2026 17:59:32 +0200 Subject: [PATCH 4/4] chore: bump python-multipart to 0.0.31 for CVE fixes pip-audit (dependency-audit CI step) flagged python-multipart 0.0.27 for CVE-2026-53538/53539/53540 (fixed in 0.0.30/0.0.31). Bump to 0.0.31. pip-audit clean and backend test suite green. ha-relevant: no --- backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 744c957..01720fc 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -8,7 +8,7 @@ pydantic==2.9.2 pydantic-settings==2.5.2 python-jose[cryptography]==3.5.0 bcrypt==4.2.1 -python-multipart==0.0.27 +python-multipart==0.0.31 apscheduler==3.10.4 python-nmap==0.7.1 pyyaml==6.0.2