From 054c6626b040b9752af79cfd940287211a4dc8ec Mon Sep 17 00:00:00 2001 From: Lucas Roa Date: Wed, 10 Jun 2026 09:30:43 -0300 Subject: [PATCH 01/32] test(sync): cover the modified-time merge path for shows/projects/stage (#3365) --- src/electron/cloud/syncLedger.test.ts | 29 +++++++++++++++++++++++++-- src/electron/cloud/syncLedger.ts | 16 +++++++-------- src/electron/cloud/syncManager.ts | 9 +++------ 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/electron/cloud/syncLedger.test.ts b/src/electron/cloud/syncLedger.test.ts index 1f7f34d0a..364c58877 100644 --- a/src/electron/cloud/syncLedger.test.ts +++ b/src/electron/cloud/syncLedger.test.ts @@ -52,12 +52,37 @@ describe("SyncLedger — per-item merge (#3335)", () => { expect(b.resolveCloudEntry(ID, KEY, /* localExists */ false).action).toBe("skip") }) + }) - it("an item that exists on both sides and is not deleted is kept (upload) for the caller to tie-break by date", () => { + describe("entries with a per-item modified time (shows / projects / stage — newest wins)", () => { + // these stores carry a per-item "modified"; checkCloudEntry computes cloudIsNewer and passes it here + it("downloads when the cloud copy is newer", () => { const changes = makeChanges(["A", "B"]) const b = new SyncLedger({ changes, deviceId: "B" }) - expect(b.resolveCloudEntry(ID, KEY, /* localExists */ true).action).toBe("upload") + expect(b.resolveCloudEntry("SHOWS_CONTENT", "show1", /* localExists */ true, /* cloudIsNewer */ true).action).toBe("download") + }) + + it("keeps the local copy (upload) when it is not older than the cloud", () => { + const changes = makeChanges(["A", "B"]) + const b = new SyncLedger({ changes, deviceId: "B" }) + + expect(b.resolveCloudEntry("PROJECTS", "proj1", /* localExists */ true, /* cloudIsNewer */ false).action).toBe("upload") + }) + + it("a tombstoned item is deleted regardless of the date (the ledger wins over newest)", () => { + const changes = makeChanges(["A", "B"], { deleted: { STAGE_stage1: ["A"] } }) + const b = new SyncLedger({ changes, deviceId: "B" }) + + // even with a newer cloud copy, a deleted item is removed, not downloaded + expect(b.resolveCloudEntry("STAGE", "stage1", /* localExists */ true, /* cloudIsNewer */ true).action).toBe("delete") + }) + + it("a brand-new device downloads the newer cloud copy and ignores tombstones", () => { + const changes = makeChanges(["A", "B"], { deleted: { SHOWS_CONTENT_show1: ["A"] } }) + const fresh = new SyncLedger({ changes, deviceId: "C", isNewDevice: true }) + + expect(fresh.resolveCloudEntry("SHOWS_CONTENT", "show1", /* localExists */ true, /* cloudIsNewer */ true).action).toBe("download") }) }) diff --git a/src/electron/cloud/syncLedger.ts b/src/electron/cloud/syncLedger.ts index 14c373b3f..f1ba86a33 100644 --- a/src/electron/cloud/syncLedger.ts +++ b/src/electron/cloud/syncLedger.ts @@ -108,11 +108,12 @@ export class SyncLedger { // ----- merge decisions (item-collections with no per-item "modified") ----- - // Item present in the cloud snapshot; decide create/skip/delete/keep given whether it exists locally. - // Single source of truth for the ledger decision: syncManager.checkCloudEntry delegates here. - // An "upload" result means "keep local for now" — for entries that carry a per-item "modified", - // the caller then breaks the tie by date (download if the cloud copy is newer). - resolveCloudEntry(storeId: string, key: string, localExists: boolean): { action: EntryAction } { + // Item present in the cloud snapshot; decide create/skip/delete/download/upload given whether it + // exists locally. Single source of truth for the cloud-side decision: syncManager.checkCloudEntry + // delegates here. When the item exists on both sides and the ledger doesn't force delete, the + // newest copy wins: entries that carry a per-item "modified" pass `cloudIsNewer` (shows, projects, + // stage, …); item-collections have no per-item "modified" and omit it → keep local (upload). + resolveCloudEntry(storeId: string, key: string, localExists: boolean, cloudIsNewer = false): { action: EntryAction } { // exists only in cloud if (!localExists) { if (this.isNewDevice) return { action: "create" } @@ -142,9 +143,8 @@ export class SyncLedger { } if (this.isCreated(storeId, key)) this.markAsCreated(storeId, key) - // exists on both sides and not deleted → keep local; the caller may override to "download" - // by modified time (collections have no per-item "modified", so they stay as upload) - return { action: "upload" } + // exists on both sides and not deleted → newest wins (collections pass cloudIsNewer=false → keep local) + return { action: cloudIsNewer ? "download" : "upload" } } // Item present only locally (not in the cloud snapshot). diff --git a/src/electron/cloud/syncManager.ts b/src/electron/cloud/syncManager.ts index 8dcefe23b..ea5c310f8 100644 --- a/src/electron/cloud/syncManager.ts +++ b/src/electron/cloud/syncManager.ts @@ -560,16 +560,13 @@ async function checkCloudEntry(id: ChangeId, key: string, cloudData: any, getLoc // exists only in cloud → the ledger decides (create / skip) if (!localValue) return ledger.resolveCloudEntry(id, key, false) - // exists both locally and in cloud → the ledger decides delete/keep; "upload" means keep local, - // so break the tie by modified time (newest wins) - const decision = ledger.resolveCloudEntry(id, key, true) - if (decision.action !== "upload") return decision - + // exists both locally and in cloud → resolve the tie by modified time (newest wins) and let the + // ledger apply it — it still forces delete/skip first if the item is marked deleted/created. let localModTime = getModifiedDate(localValue) if (cloudData !== null && !localModTime) localModTime = setModifiedDate() const cloudIsNewer = isCloudNewer ? await isCloudNewer() : cloudModTime > localModTime - return { action: cloudIsNewer ? "download" : "upload" } + return ledger.resolveCloudEntry(id, key, true, cloudIsNewer) function getModifiedDate(data: any): number { if (!data) return 0 From c1830d35b0d7c86fd5506172aa6029e2c38d9638 Mon Sep 17 00:00:00 2001 From: Kristoffer Date: Thu, 11 Jun 2026 10:06:09 +0200 Subject: [PATCH 02/32] Fixed Next on media finished sometimes triggering twice --- .../output/layers/BackgroundMedia.svelte | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/frontend/components/output/layers/BackgroundMedia.svelte b/src/frontend/components/output/layers/BackgroundMedia.svelte index b876bec0c..99f241487 100644 --- a/src/frontend/components/output/layers/BackgroundMedia.svelte +++ b/src/frontend/components/output/layers/BackgroundMedia.svelte @@ -142,17 +142,26 @@ destroy(OUTPUT, listenerId) } + $: isVideo = videoExtensions.includes(getExtension(id)) + // call end just before (to make room for transition) - this also triggers video ended on loop - $: if (videoData.duration && videoTime >= videoData.duration - (duration / 1000 + 0.1) && !mediaStyle.softLoop) videoEnded() + $: if (isVideo && videoData.duration && videoTime >= videoData.duration - (duration / 1000 + 0.1) && !mediaStyle.softLoop) { + videoEnded() + } let endedCalled = false + $: if (id) endedCalled = false + function videoEnded() { if (fadingOut || mirror || endedCalled) return - endedCalled = true - setTimeout(() => (endedCalled = false), duration || 1000) send(OUTPUT, ["MAIN_VIDEO_ENDED"], { id: outputId, loop: videoData.loop, duration }) + + // Only reset if looping, otherwise keep endedCalled true for the remainder of this media's life + if (videoData.loop) { + setTimeout(() => (endedCalled = false), Math.max(duration, 2000)) + } } // FADE OUT AUDIO From 9940dea65a1f214ca96080c7345a3a7f4f4276cf Mon Sep 17 00:00:00 2001 From: Kristoffer Date: Thu, 11 Jun 2026 10:32:10 +0200 Subject: [PATCH 03/32] Fixed errors --- src/electron/utils/shows.ts | 9 ++- src/frontend/components/actions/apiHelper.ts | 19 ++++- src/frontend/components/context/menuClick.ts | 36 +++++---- .../components/drawer/info/ShowInfo.svelte | 4 +- .../edit/editbox/EditboxLines.svelte | 7 +- .../edit/editors/SlideEditor.svelte | 17 +++-- .../components/edit/scripts/itemClipboard.ts | 12 ++- src/frontend/components/helpers/output.ts | 2 +- .../components/helpers/showActions.ts | 21 +++--- .../components/main/popups/NextTimer.svelte | 31 +++++--- src/frontend/components/show/Slides.svelte | 17 +++-- src/frontend/components/show/getTextEditor.ts | 2 +- src/frontend/converters/openlp.ts | 26 +++++-- src/server/stage/helpers/textStyle.ts | 75 ++++++++++++------- 14 files changed, 178 insertions(+), 100 deletions(-) diff --git a/src/electron/utils/shows.ts b/src/electron/utils/shows.ts index 8d67006d6..5ab43f0e5 100644 --- a/src/electron/utils/shows.ts +++ b/src/electron/utils/shows.ts @@ -49,10 +49,13 @@ function showHasLayoutContent(show: Show) { export function getShowTextContent(show: Show) { let textContent = "" Object.values(show.slides || {}).forEach((slide) => { + if (!Array.isArray(slide.items)) return slide.items.forEach((item) => { - item.lines?.forEach((line) => { - line.text?.forEach((text) => { - textContent += text.value + if (!Array.isArray(item.lines)) return + item.lines.forEach((line) => { + if (!Array.isArray(line.text)) return + line.text.forEach((text) => { + textContent += text.value || "" }) }) }) diff --git a/src/frontend/components/actions/apiHelper.ts b/src/frontend/components/actions/apiHelper.ts index e6c622ce2..2d7f13165 100644 --- a/src/frontend/components/actions/apiHelper.ts +++ b/src/frontend/components/actions/apiHelper.ts @@ -555,11 +555,22 @@ export function setNextSlideTimer(time: number) { const layoutId = _show().get("settings.activeLayout") showsCache.update((a) => { - let ref = goToStartRefs[0] - if (!ref) return a + const ref = goToStartRefs[0] + const show = a[showId] + if (!ref || !show) return a - if (ref.type === "parent") delete a[showId].layouts[layoutId]?.slides?.[ref.index]?.end - else delete a[showId].layouts[layoutId]?.slides?.[ref.parent?.index ?? -1]?.children?.[ref.id]?.end + const layout = show.layouts?.[layoutId] + if (!layout) return a + + if (ref.type === "parent") { + const slide = layout.slides?.[ref.index] + if (slide) delete slide.end + } else { + const parentIndex = ref.parent?.index ?? -1 + const parentSlide = layout.slides?.[parentIndex] + const child = parentSlide?.children?.[ref.id] + if (child) delete child.end + } return a }) } diff --git a/src/frontend/components/context/menuClick.ts b/src/frontend/components/context/menuClick.ts index 3ba1e8075..30636f8f2 100644 --- a/src/frontend/components/context/menuClick.ts +++ b/src/frontend/components/context/menuClick.ts @@ -371,21 +371,24 @@ const clickActions = { // WIP history showsCache.update((a) => { - if (!a[showId]) return a + if (!a[showId]?.slides) return a const newId = uid() const newSlide = clone(a[showId].slides[groupId]) + if (!newSlide) return a // delete newSlide.id // should not be there // group children let newChildren: string[] = [] let newChildIds = new Map() - const children = newSlide.children || [] + const children = Array.isArray(newSlide.children) ? newSlide.children : [] children.forEach((childId) => { const newChildId = uid() - a[showId].slides[newChildId] = clone(a[showId].slides[childId]) - newChildren.push(newChildId) - newChildIds.set(childId, newChildId) + if (a[showId].slides[childId]) { + a[showId].slides[newChildId] = clone(a[showId].slides[childId]) + newChildren.push(newChildId) + newChildIds.set(childId, newChildId) + } }) if (newChildren.length) newSlide.children = newChildren @@ -393,16 +396,19 @@ const clickActions = { // layout const activeLayout = a[showId].settings?.activeLayout - a[showId].layouts[activeLayout].slides[groupIndex].id = newId - // children data - if (a[showId].layouts[activeLayout].slides[groupIndex].children) { - Object.keys(a[showId].layouts[activeLayout].slides[groupIndex].children).forEach((childId) => { - const newChildId = newChildIds.get(childId) - if (newChildId) { - a[showId].layouts[activeLayout].slides[groupIndex].children![newChildId] = clone(a[showId].layouts[activeLayout].slides[groupIndex].children![childId]) - delete a[showId].layouts[activeLayout].slides[groupIndex].children![childId] - } - }) + const layoutSlide = a[showId].layouts?.[activeLayout || ""]?.slides?.[groupIndex] + if (layoutSlide) { + layoutSlide.id = newId + // children data + if (layoutSlide.children) { + Object.keys(layoutSlide.children).forEach((childId) => { + const newChildId = newChildIds.get(childId) + if (newChildId) { + layoutSlide.children![newChildId] = clone(layoutSlide.children![childId]) + delete layoutSlide.children![childId] + } + }) + } } return a diff --git a/src/frontend/components/drawer/info/ShowInfo.svelte b/src/frontend/components/drawer/info/ShowInfo.svelte index 0238e8e9b..67e1998c8 100644 --- a/src/frontend/components/drawer/info/ShowInfo.svelte +++ b/src/frontend/components/drawer/info/ShowInfo.svelte @@ -49,10 +49,12 @@ function getWords() { words = 0 + if (!Array.isArray(allLines)) return allLines.forEach((lines) => { if (!Array.isArray(lines)) return lines.forEach((line) => { - line?.text?.forEach((text) => (words += text.value?.split(" ").length)) + if (!Array.isArray(line?.text)) return + line.text.forEach((text) => (words += text.value?.split(" ").length || 0)) }) }) } diff --git a/src/frontend/components/edit/editbox/EditboxLines.svelte b/src/frontend/components/edit/editbox/EditboxLines.svelte index bd7e0baf5..49f7ea887 100644 --- a/src/frontend/components/edit/editbox/EditboxLines.svelte +++ b/src/frontend/components/edit/editbox/EditboxLines.svelte @@ -198,7 +198,7 @@ const texts = (Array.isArray(firstLine?.text) ? firstLine.text : []).filter((a) => !a.customType).map((a) => a.value) showsCache.update((a) => { const showId = ref.showId || $activeShow?.id || "" - const slide = a[showId]?.slides[ref.id] + const slide = a[showId]?.slides?.[ref.id] if (!slide?.customDynamicValues?.scripture_text) return a texts.forEach((t, i) => { @@ -334,8 +334,9 @@ if (text.sourceDynamicKey?.includes("scripture_text")) { const key = text.sourceDynamicKey.split(":")[0] const index = text.sourceDynamicKey.split(":")[1] || "0" - if (!a[ref.showId!].slides[ref.id].customDynamicValues![key]?.[index]?.[1]) return - a[ref.showId!].slides[ref.id].customDynamicValues![key][index][1] = text.value + const storage = a[ref.showId!]?.slides?.[ref.id]?.customDynamicValues + if (!storage?.[key]?.[index]) return + storage[key][index][1] = text.value } }) }) diff --git a/src/frontend/components/edit/editors/SlideEditor.svelte b/src/frontend/components/edit/editors/SlideEditor.svelte index 6d964b861..a5b9072a9 100644 --- a/src/frontend/components/edit/editors/SlideEditor.svelte +++ b/src/frontend/components/edit/editors/SlideEditor.svelte @@ -122,7 +122,11 @@ // updateStyles() // }, CHANGE_POS_TIME) - let items = currentShow?.slides?.[ref[$activeEdit.slide || 0]?.id]?.items || [] + const slideIndex = $activeEdit.slide ?? 0 + const slideId = ref[slideIndex]?.id + if (!slideId) return + + let items = currentShow?.slides?.[slideId]?.items || [] let values: string[] = [] active.forEach((id) => { let item = items[id] @@ -137,14 +141,11 @@ } }) - let slideId = ref[$activeEdit.slide || 0]?.id - if (!slideId) return - let activeItems = [...active] let historyShow = $activeShow // focus mode - if (!historyShow && currentShowId) historyShow = { type: "show", id: currentShowId, index: $activeEdit.slide || 0 } + if (!historyShow && currentShowId) historyShow = { type: "show", id: currentShowId, index: slideIndex } else if (!historyShow) return history({ @@ -255,7 +256,7 @@ // timeout to prevent number 2 from getting typed if changing with shortcuts setTimeout(() => { // set focus to textbox if only one - if (Slide?.items.length === 1 && !$activeEdit.items.length) { + if (Slide?.items?.length === 1 && !$activeEdit.items.length) { activeEdit.update((a) => ({ ...(a || {}), items: [0] })) const elem = document.querySelector(".editItem")?.querySelector(".edit") if (elem && !$special.slideTimelineActive) { @@ -328,9 +329,9 @@ function edit(e: any) { const slideId = ref[$activeEdit.slide!]?.id - if (Slide.notes === e.detail || !slideId) return + if (!Slide || Slide.notes === e.detail || !slideId) return - _show($activeShow!.id).slides([slideId]).set({ key: "notes", value: e.detail }) + _show(currentShowId).slides([slideId]).set({ key: "notes", value: e.detail }) } diff --git a/src/frontend/components/edit/scripts/itemClipboard.ts b/src/frontend/components/edit/scripts/itemClipboard.ts index 264793121..fef15cdb8 100644 --- a/src/frontend/components/edit/scripts/itemClipboard.ts +++ b/src/frontend/components/edit/scripts/itemClipboard.ts @@ -150,12 +150,16 @@ export async function setBoxStyle(styles: StyleClipboard[], slides: any, type: I // location: { page: "edit", show: get(activeShow)!, slide: slide.id, items }, // }) showsCache.update((a) => { - ;(a[get(activeShow)!.id]?.slides[slide.id || ""]?.items || []) + const showId = get(activeShow)?.id + if (!showId || !a[showId]?.slides) return a + ;(a[showId].slides[slide.id || ""]?.items || []) .filter((_, i) => items.includes(i)) .forEach((item) => { - item.lines?.forEach((line) => { - line.align = style.linesAlign! - }) + if (Array.isArray(item.lines)) { + item.lines.forEach((line) => { + line.align = style.linesAlign! + }) + } }) return a }) diff --git a/src/frontend/components/helpers/output.ts b/src/frontend/components/helpers/output.ts index d9613a093..b5c5c4775 100644 --- a/src/frontend/components/helpers/output.ts +++ b/src/frontend/components/helpers/output.ts @@ -1317,7 +1317,7 @@ function replaceScriptureValues(items: Item[], templateItems: Item[], customDyna // remove empty values items.forEach((item) => { item.lines?.forEach((line) => { - line.text = line.text?.filter((text) => text.value) || [] + line.text = Array.isArray(line.text) ? line.text.filter((text) => text.value) : [] }) }) } diff --git a/src/frontend/components/helpers/showActions.ts b/src/frontend/components/helpers/showActions.ts index 5be4e8c1e..acc672dfc 100644 --- a/src/frontend/components/helpers/showActions.ts +++ b/src/frontend/components/helpers/showActions.ts @@ -108,8 +108,9 @@ export function swichProjectItem(pos: number, id: string) { export function getItemWithMostLines(slide: Slide | { items: Item[] }) { let amount = 0 - slide.items?.forEach((item) => { - const lines: number = item?.lines?.filter((line) => line.text?.filter((text) => text.value !== undefined)?.length)?.length || 0 + if (!Array.isArray(slide.items)) return 0 + slide.items.forEach((item) => { + const lines: number = (Array.isArray(item?.lines) ? item.lines.filter((line) => Array.isArray(line.text) && line.text.filter((text) => text.value !== undefined).length > 0) : [])?.length || 0 if (lines > amount) amount = lines }) return amount @@ -165,9 +166,11 @@ function shouldTriggerBefore(action: any) { return action?.triggers?.find((trigger) => triggerActionsBeforeOutput[trigger]?.(action.actionValues?.[trigger])) } export function checkActionTrigger(layoutData: SlideData, slideIndex = 0) { - layoutData?.actions?.slideActions?.forEach((a) => { - if (shouldTriggerBefore(a)) runAction(a, { slideIndex }) - }) + if (Array.isArray(layoutData?.actions?.slideActions)) { + layoutData.actions.slideActions.forEach((a) => { + if (shouldTriggerBefore(a)) runAction(a, { slideIndex }) + }) + } } export async function playPdf(data: OutSlide | null, next: boolean, loop = false) { @@ -358,7 +361,7 @@ export function updateOut(showId: string, index: number, layout: LayoutRef[], ex } // audio - if (data.audio) { + if (Array.isArray(data.audio)) { // let clear action trigger first setTimeout(() => { data.audio?.forEach((audio: string) => { @@ -474,10 +477,10 @@ function playOutputStyleTemplateActions(outputIds: string[]) { const styleTemplateId = get(styles)[outputStyleId]?.template || "" if (!styleTemplateId) return - const templateSettings = get(templates)[styleTemplateId]?.settings?.actions || [] - if (!templateSettings?.length) return + const templateSettings = get(templates)[styleTemplateId]?.settings?.actions + if (!Array.isArray(templateSettings) || !templateSettings.length) return - templateSettings?.forEach((action) => runAction(action)) + templateSettings.forEach((action) => runAction(action)) }) } diff --git a/src/frontend/components/main/popups/NextTimer.svelte b/src/frontend/components/main/popups/NextTimer.svelte index b5d036781..a1d3dc123 100644 --- a/src/frontend/components/main/popups/NextTimer.svelte +++ b/src/frontend/components/main/popups/NextTimer.svelte @@ -60,15 +60,28 @@ let goToStartRefs = allActiveSlides.reduce((value, ref) => (ref.data?.end ? [...value, ref] : value), [] as LayoutRef[]) if (goToStartRefs.length === 1) { let showId = $activeShow?.id || "" - let layoutId = _show().get("settings.activeLayout") - showsCache.update((a) => { - let ref = goToStartRefs[0] - if (!ref) return a - - if (ref.type === "parent") delete a[showId].layouts[layoutId]?.slides?.[ref.index]?.end - else delete a[showId].layouts[layoutId]?.slides?.[ref.parent?.index ?? -1]?.children?.[ref.id]?.end - return a - }) + if (showId) { + let layoutId = _show(showId).get("settings.activeLayout") + showsCache.update((a) => { + const ref = goToStartRefs[0] + const show = a[showId] + if (!ref || !show) return a + + const layout = show.layouts?.[layoutId] + if (!layout) return a + + if (ref.type === "parent") { + const slide = layout.slides?.[ref.index] + if (slide) delete slide.end + } else { + const parentIndex = ref.parent?.index ?? -1 + const parentSlide = layout.slides?.[parentIndex] + const child = parentSlide?.children?.[ref.id] + if (child) delete child.end + } + return a + }) + } } history({ id: "SHOW_LAYOUT", newData: { key: "end", data: !!value, indexes: [indexes[indexes.length - 1]] }, location: { page: "show", override: "change_slide_action_loop" } }) diff --git a/src/frontend/components/show/Slides.svelte b/src/frontend/components/show/Slides.svelte index e3a78cdfd..29e8db1ba 100644 --- a/src/frontend/components/show/Slides.svelte +++ b/src/frontend/components/show/Slides.svelte @@ -49,13 +49,18 @@ function fixBrokenMedia() { if (!currentShow) return showsCache.update((a) => { + if (!a[showId]) return a Object.entries(currentShow.layouts || {}).forEach(([layoutId, layout]) => { - layout.slides.forEach((slide, i) => { - let backgroundId = slide.background - if (backgroundId && !currentShow.media[backgroundId]) { - delete a[showId].layouts[layoutId].slides[i].background - } - }) + if (Array.isArray(layout.slides)) { + layout.slides.forEach((slide, i) => { + let backgroundId = slide.background + if (backgroundId && !currentShow.media[backgroundId]) { + if (a[showId].layouts?.[layoutId]?.slides?.[i]) { + delete a[showId].layouts[layoutId].slides[i].background + } + } + }) + } }) return a }) diff --git a/src/frontend/components/show/getTextEditor.ts b/src/frontend/components/show/getTextEditor.ts index c46d1c84b..1506fa245 100644 --- a/src/frontend/components/show/getTextEditor.ts +++ b/src/frontend/components/show/getTextEditor.ts @@ -60,7 +60,7 @@ function getItems(items: Item[]) { plainText += textboxId + "\n" } - const filteredLines = item.lines?.filter((line) => line.text?.filter((lineText) => lineText.value.length).length) || [] + const filteredLines = (Array.isArray(item.lines) ? item.lines.filter((line) => Array.isArray(line.text) && line.text.filter((lineText) => lineText.value.length).length) : []) || [] filteredLines.forEach((line, lineIndex) => { let tempText = "" line.text?.forEach((txt) => { diff --git a/src/frontend/converters/openlp.ts b/src/frontend/converters/openlp.ts index 17b6c64b1..3472fd85b 100644 --- a/src/frontend/converters/openlp.ts +++ b/src/frontend/converters/openlp.ts @@ -200,9 +200,9 @@ function getSong(song: any, content: any) { lyrics = lyrics.song?.lyrics?.verse || [] if (!Array.isArray(lyrics)) lyrics = [lyrics] - lyrics = lyrics.map((a) => { + lyrics = lyrics.filter(Boolean).map((a) => { const { lines, chords } = extractChordLines(a["#cdata"] || "") - return { name: a["@type"] + a["@label"], lines, chords } + return { name: (a["@type"] || "") + (a["@label"] || ""), lines, chords } }) return lyrics @@ -217,7 +217,19 @@ function XMLtoObject(xml: string) { let lyrics = song.lyrics || {} const properties = song.properties || {} - const notes = song["#comment"] || (Array.isArray(properties.comments) ? properties.comments?.map((comment) => comment["#text"] || "").join("\n") : typeof properties.comments?.comment === "string" ? properties.comments.comment : typeof properties.comments === "string" ? properties.comments : "") || "" + const notes = + song["#comment"] || + (Array.isArray(properties.comments) + ? properties.comments + .filter(Boolean) + .map((comment) => comment["#text"] || "") + .join("\n") + : typeof properties.comments?.comment === "string" + ? properties.comments.comment + : typeof properties.comments === "string" + ? properties.comments + : "") || + "" const newSong: Song = { title: getTitle(), @@ -277,17 +289,17 @@ function XMLtoObject(xml: string) { } function getLines(lines: string | any) { - if (lines.tag) lines = lines.tag.tag?.["#text"] + if (lines?.tag) lines = lines.tag.tag?.["#text"] if (!lines) return { lines: [], chords: [] } // might be - if (lines["#text"]) lines = lines["#text"] + if (lines && lines["#text"]) lines = lines["#text"] // some openlyrics verses can have multiple tags if (Array.isArray(lines)) { const convertedLines: string[] = lines.map(convertToText) function convertToText(line: any) { - if (line["#text"]) return line["#text"] - return line + if (line && line["#text"]) return line["#text"] + return line || "" } lines = convertedLines.join("\n") } diff --git a/src/server/stage/helpers/textStyle.ts b/src/server/stage/helpers/textStyle.ts index 2a3682562..dc5331dcb 100644 --- a/src/server/stage/helpers/textStyle.ts +++ b/src/server/stage/helpers/textStyle.ts @@ -2,7 +2,7 @@ import type { Item, Line } from "../../../types/Show" // add new style to text by selection export function addStyle(selection: { start: number; end: number }[], item: Item, style: any[]): Item { - item.lines?.forEach(lineStyle) + if (Array.isArray(item.lines)) item.lines.forEach(lineStyle) return combine(item) @@ -15,7 +15,7 @@ export function addStyle(selection: { start: number; end: number }[], item: Item line.text = newText } - line.text?.forEach(textStyle) + if (Array.isArray(line.text)) line.text.forEach(textStyle) line.text = newText function textStyle(text: any) { @@ -44,7 +44,7 @@ export function addStyle(selection: { start: number; end: number }[], item: Item // combine duplicate styles function combine(item: Item): Item { - item.lines?.forEach(setLineStyle) + if (Array.isArray(item.lines)) item.lines.forEach(setLineStyle) return item @@ -171,23 +171,28 @@ export function getSelectionRange(): { start: number; end: number }[] { // return item style at text length pos export function getItemStyleAtPos(lines: Line[], pos: null | { start: number; end: number }[]) { let style: string = "" - ;(pos || lines).forEach((_a: any, i: number) => { - let currentPos: number = 0 - lines[i]?.text?.some((text: any): any => { - const value = text.value || "" + const items = pos || lines + if (Array.isArray(items)) { + items.forEach((_a: any, i: number) => { + let currentPos: number = 0 + if (Array.isArray(lines[i]?.text)) { + lines[i].text.some((text: any): any => { + const value = text.value || "" + + // if (pos) console.log(currentPos, pos[i].end, currentPos <= pos[i].end, currentPos + value.length >= pos[i].end) + if (pos?.[i] && currentPos <= pos[i].end && currentPos + value.length >= pos[i].end) { + style = text.style + return true + } - // if (pos) console.log(currentPos, pos[i].end, currentPos <= pos[i].end, currentPos + value.length >= pos[i].end) - if (pos?.[i] && currentPos <= pos[i].end && currentPos + value.length >= pos[i].end) { - style = text.style - return true + currentPos += value.length + }) } - - currentPos += value.length }) - }) + } // filter out empty lines - lines = lines.filter((a) => a?.text?.length) + if (Array.isArray(lines)) lines = lines.filter((a) => a?.text?.length) if (!style.length && lines.length) style = lines[lines.length - 1].text[lines[lines.length - 1].text.length - 1]?.style || "" @@ -197,9 +202,11 @@ export function getItemStyleAtPos(lines: Line[], pos: null | { start: number; en // get item align at selected pos export function getLastLineAlign(item: Item, selection: any): string { let last: string = "" - item?.lines!.forEach((line: any, i: number) => { - if (!selection || selection[i]?.start) last = line.align - }) + if (Array.isArray(item?.lines)) { + item.lines.forEach((line: any, i: number) => { + if (!selection || selection[i]?.start) last = line.align + }) + } return last } @@ -207,9 +214,13 @@ export function getLastLineAlign(item: Item, selection: any): string { export function getItemText(item: Item): string { let text = "" - for (const line of item?.lines ?? []) { - for (const t of line.text ?? []) { - if (t.value) text += t.value + if (Array.isArray(item?.lines)) { + for (const line of item.lines) { + if (Array.isArray(line?.text)) { + for (const t of line.text) { + if (t.value) text += t.value + } + } } } @@ -218,9 +229,11 @@ export function getItemText(item: Item): string { export function getLineText(line: any): string { let text: string = "" - line.text?.forEach((content: any) => { - text += content.value - }) + if (Array.isArray(line?.text)) { + line.text.forEach((content: any) => { + text += content.value || "" + }) + } return text } @@ -229,11 +242,15 @@ export function getItemLines(item: Item): string[] { if (!item) return [] let lines: string[] = [] - item.lines?.forEach((line: any) => { - let text = "" - line.text?.forEach((content: any) => (text += content.value)) - lines.push(text) - }) + if (Array.isArray(item.lines)) { + item.lines.forEach((line: any) => { + let text = "" + if (Array.isArray(line?.text)) { + line.text.forEach((content: any) => (text += content.value || "")) + } + lines.push(text) + }) + } return lines } From 034f3704516ce3facb300389615df79eaf8172e4 Mon Sep 17 00:00:00 2001 From: Kristoffer Date: Thu, 11 Jun 2026 10:50:07 +0200 Subject: [PATCH 04/32] Backup & Cloud stores .show files directly in the zip --- src/electron/cloud/syncManager.ts | 126 +++++++++++++++++++++--------- src/electron/data/backup.ts | 89 +++++++++------------ 2 files changed, 126 insertions(+), 89 deletions(-) diff --git a/src/electron/cloud/syncManager.ts b/src/electron/cloud/syncManager.ts index ea5c310f8..b6634f7f3 100644 --- a/src/electron/cloud/syncManager.ts +++ b/src/electron/cloud/syncManager.ts @@ -4,7 +4,7 @@ import { isProd } from ".." import { Main } from "../../types/IPC/Main" import type { Folders, Projects } from "../../types/Projects" import type { Show } from "../../types/Show" -import { isValidJSON, restoreFiles, startBackup } from "../data/backup" +import { restoreFiles, startBackup } from "../data/backup" import { _store, getStore, safeStoreSet } from "../data/store" import { compressToZip, decompressZipStream, getZipModifiedDates } from "../data/zip" import { sendMain } from "../IPC/main" @@ -125,17 +125,21 @@ export async function syncData(data: { id: SyncProviderId; churchId: string; tea const changesFile = extractedFiles.find((file) => file.name === changes_name) if (typeof changesFile?.content === "string") { const changesContent = await readFileAsync(changesFile.content) - if (isValidJSON(changesContent)) CHANGES = JSON.parse(changesContent) - if (CHANGES.version !== version) CHANGES = clone(DEFAULT_CHANGES) - cloudChanges = clone(CHANGES) - + const parsedChanges = safeParseJSON(changesContent) const deviceId = getDeviceId() - const deviceExists = CHANGES.devices.find((id) => id === deviceId) - if (!deviceExists) { - markAsNewSync() - CHANGES.devices.push(deviceId) - } else if (isNewDevice) { - removeDeviceRecords() + + if (parsedChanges) { + CHANGES = parsedChanges + if (CHANGES.version !== version) CHANGES = clone(DEFAULT_CHANGES) + cloudChanges = clone(CHANGES) + + const deviceExists = CHANGES.devices.find((id) => id === deviceId) + if (!deviceExists) { + markAsNewSync() + CHANGES.devices.push(deviceId) + } else if (isNewDevice) { + removeDeviceRecords() + } } // set to read-only always initially if not synced for 30+ days @@ -160,6 +164,10 @@ export async function syncData(data: { id: SyncProviderId; churchId: string; tea // MERGE const cloudBibleNames: string[] = [] + const cloudShowNames: string[] = [] + const replacedShows: string[] = [] + let showsFound = false + await Promise.all( extractedFiles.map(async (file) => { if (!file) return @@ -192,17 +200,55 @@ export async function syncData(data: { id: SyncProviderId; churchId: string; tea return } + // download new/modified shows (new format) + if (file.name.startsWith("SHOWS/") && file.name.endsWith(".show")) { + showsFound = true + try { + const cloudFile = await readFileAsync(cloudPath) + const parsed = safeParseJSON(cloudFile) + if (!parsed || !Array.isArray(parsed)) return + const [_id, show] = parsed as [string, Show] + + const fileName = path.basename(file.name) + const localShowPath = path.join(showsFolder, fileName) + cloudShowNames.push(fileName) + + if (data.method === "replace") { + await download(true) + return + } + + const getLocalData = async () => { + const localFile = await readFileAsync(localShowPath) + const localParsed = safeParseJSON(localFile) + return localParsed ? (localParsed[1] as Show) : null + } + + const result = await checkCloudEntry("SHOWS_CONTENT", fileName, show, getLocalData) + + if (result.action === "delete") deleteFile(localShowPath) + else if (result.action === "create") await download(true) + else if (result.action === "download") await download(false) + + async function download(isNew: boolean) { + if (!isNew) replacedShows.push(show.name) + await writeFileAsync(localShowPath, cloudFile) + } + } catch (err) { + console.error("Failed to write show:", file?.name, err) + } + return + } + const cloudFile = await readFileAsync(cloudPath) - if (!isValidJSON(cloudFile)) return - const cloudFileData = JSON.parse(cloudFile) + const cloudFileData = safeParseJSON(cloudFile) + if (!cloudFileData) return const id = path.basename(file.name, path.extname(file.name)) as keyof typeof _store - // download new/modified shows + // download new/modified shows (old format) if (file.name === "SHOWS_CONTENT.json") { - const cloudShowNames: string[] = [] - const replacedShows: string[] = [] - + showsFound = true await Promise.all( Object.entries(cloudFileData).map(async ([id, show]) => { try { @@ -217,8 +263,8 @@ export async function syncData(data: { id: SyncProviderId; churchId: string; tea const getLocalData = async () => { const localFile = await readFileAsync(localShowPath) - if (!localFile || !isValidJSON(localFile)) return null - return JSON.parse(localFile)[1] as Show + const localParsed = safeParseJSON(localFile) + return localParsed ? (localParsed[1] as Show) : null } const result = await checkCloudEntry("SHOWS_CONTENT", fileName, show, getLocalData) @@ -236,20 +282,6 @@ export async function syncData(data: { id: SyncProviderId; churchId: string; tea } }) ) - - // check any local instance not in cloud - const showNames = await readFolderAsync(showsFolder) - const localShows = getLocalOnlyKeys(cloudShowNames, showNames) - for (const fileName of localShows) { - const result = checkLocalEntry(id, fileName) - - const localShowPath = path.join(showsFolder, fileName) - if (result.action === "delete") deleteFile(localShowPath) - } - - // send to frontend - loadShows(false, replacedShows) - if (_store.SHOWS) sendMain(Main.SHOWS, _store.SHOWS.store) return } @@ -268,8 +300,8 @@ export async function syncData(data: { id: SyncProviderId; churchId: string; tea if (data.method === "replace" || isNewDevice || (await isCloudNewerThanFile(localPath, modifiedDates[file.name]))) { // try to set store directly first, otherwise move the file const cloudContent = await readFileAsync(cloudPath) - if (isValidJSON(cloudContent)) { - const parsedData = JSON.parse(cloudContent) + const parsedData = safeParseJSON(cloudContent) + if (parsedData) { await safeStoreSet(localStore, parsedData, id) } else { await moveFileAsync(cloudPath, localPath) @@ -366,6 +398,22 @@ export async function syncData(data: { id: SyncProviderId; churchId: string; tea }) ) + // check any local instance not in cloud + if (showsFound) { + const showNames = await readFolderAsync(showsFolder) + const localShows = getLocalOnlyKeys(cloudShowNames, showNames) + for (const fileName of localShows) { + const result = checkLocalEntry("SHOWS_CONTENT", fileName) + + const localShowPath = path.join(showsFolder, fileName) + if (result.action === "delete") deleteFile(localShowPath) + } + + // send to frontend + loadShows(false, replacedShows) + if (_store.SHOWS) sendMain(Main.SHOWS, _store.SHOWS.store) + } + // check any local instance not in cloud const bibleNames = await readFolderAsync(biblesFolder) const localBibles = getLocalOnlyKeys(cloudBibleNames, bibleNames) @@ -590,6 +638,14 @@ function checkLocalEntry(id: ChangeId, key: string) { return ledger.resolveLocalEntry(id, key) } +function safeParseJSON(text: string) { + try { + return text ? JSON.parse(text) : null + } catch { + return null + } +} + // SYNC LOGIC // if not found locally, and marked as "deleted" in cloud: skip // if not found locally, and marked as "created" in cloud: download diff --git a/src/electron/data/backup.ts b/src/electron/data/backup.ts index 596e068b5..cad47fc41 100644 --- a/src/electron/data/backup.ts +++ b/src/electron/data/backup.ts @@ -3,10 +3,9 @@ import path from "path" import { Main } from "../../types/IPC/Main" import { ToMain } from "../../types/IPC/ToMain" import type { SaveActions } from "../../types/Save" -import type { Show, Shows, TrimmedShow } from "../../types/Show" +import type { Show, Shows } from "../../types/Show" import { sendMain, sendToMain } from "../IPC/main" import { deleteFile, deleteFolder, doesPathExist, getDataFolderPath, getFileStats, getTimePointString, loadShows, makeDir, openInSystem, readFile, readFolder, selectFilesDialog, writeFile } from "../utils/files" -import { wait } from "../utils/helpers" import { _store, getStore, setStore, storeFilesData } from "./store" import { compressToZip, decompressZip } from "./zip" @@ -52,75 +51,39 @@ export async function startBackup({ customTriggers, isCloudSync }: { customTrigg /// // async function syncStores(id: keyof typeof _store) { - const currentData = getStore(id) - if (!currentData) return + const store = _store[id] + if (!store) return const name = id + ".json" - const content: string = JSON.stringify(currentData) - entries.push({ name, content }) + entries.push({ name, filePath: store.path }) } async function syncBibles() { const biblesPath = getDataFolderPath("scriptures") + if (!fs.existsSync(biblesPath)) return + const bibleFiles = readFolder(biblesPath) - await Promise.all( - bibleFiles.map(async (fileName) => { - const sourcePath = path.join(biblesPath, fileName) - const destPath = `BIBLE_${fileName}` - entries.push({ name: destPath, filePath: sourcePath }) - }) - ) + bibleFiles.forEach((fileName) => { + const sourcePath = path.join(biblesPath, fileName) + const destPath = `BIBLE_${fileName}` + entries.push({ name: destPath, filePath: sourcePath }) + }) } async function syncAllShows() { if (!shows) return - const allShows: Shows = {} const showsPath = getDataFolderPath("shows") const showEntries = Object.entries(shows) + const showFilesOnDisk = new Set(readFolder(showsPath)) - // avoid opening too many files at once - let batchSize = 100 - - try { - await readAllShows() - } catch (err: any) { - if (err.code === "EMFILE") { - console.warn("EMFILE encountered, retrying with smaller batch size...") - batchSize = 20 - for (const key in allShows) delete allShows[key] - await readAllShows() - } else { - throw err - } - } - - async function readAllShows() { - for (let i = 0; i < showEntries.length; i += batchSize) { - const batch = showEntries.slice(i, i + batchSize) - await Promise.all( - batch.map(async ([id, show]: [string, TrimmedShow]) => { - const fileName = (show.name || id) + ".show" - const localShowPath = path.join(showsPath, fileName) - - try { - const localContent = await fs.promises.readFile(localShowPath, "utf8") - if (localContent && isValidJSON(localContent)) allShows[id] = JSON.parse(localContent)[1] - } catch (err: any) { - if (err.code === "EMFILE") throw err - } - }) - ) - - // ensure all shows are added correctly - // https://github.com/ChurchApps/FreeShow/issues/1492 - await wait(20) + for (const [id, show] of showEntries) { + const fileName = (show.name || id) + ".show" + if (showFilesOnDisk.has(fileName)) { + entries.push({ name: "SHOWS/" + fileName, filePath: path.join(showsPath, fileName) }) } } - - const content: string = JSON.stringify(allShows) - entries.push({ name: "SHOWS_CONTENT.json", content }) } } @@ -196,12 +159,20 @@ export async function restoreFiles(data?: { path: string }) { .filter(([_, data]) => data.portable) .map(([key, _]) => key) + let showsRestored = false files.forEach((file: { name: string; content: string | Buffer }) => { if (typeof file.content !== "string") return const filePath = file.name if (filePath.includes("SHOWS_CONTENT")) { restoreShows(file.content) + showsRestored = true + return + } + + if (filePath.startsWith("SHOWS/")) { + restoreSingleShow(file.name, file.content) + showsRestored = true return } @@ -216,6 +187,8 @@ export async function restoreFiles(data?: { path: string }) { restoreStore(file.content, storeId as keyof typeof _store) }) + if (showsRestored) sendMain(Main.SHOWS, loadShows()) + sendToMain(ToMain.RESTORE2, { finished: true }) return @@ -250,8 +223,16 @@ export async function restoreFiles(data?: { path: string }) { const showPath: string = path.resolve(showsPath, (value.name || id) + ".show") writeFile(showPath, JSON.stringify([id, value]), id) } + } - sendMain(Main.SHOWS, loadShows()) + function restoreSingleShow(name: string, content: string) { + if (!content || !isValidJSON(content)) return + + const fileName = path.basename(name) + const showPath = path.resolve(showsPath, fileName) + + if (!doesPathExist(showsPath)) makeDir(showsPath) + writeFile(showPath, content) } } From 74664325156dcc18207b38e841e0d0ad73a64332 Mon Sep 17 00:00:00 2001 From: Kristoffer Date: Mon, 15 Jun 2026 09:34:11 +0200 Subject: [PATCH 05/32] Fixed Text edit item order #3380 --- src/frontend/components/show/formatTextEditor.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/components/show/formatTextEditor.ts b/src/frontend/components/show/formatTextEditor.ts index 9510a2eef..71ed3cc56 100644 --- a/src/frontend/components/show/formatTextEditor.ts +++ b/src/frontend/components/show/formatTextEditor.ts @@ -266,10 +266,10 @@ export function formatText(text: string, showId = "") { items = [...removeEmptyTextboxes(oldItems).filter((a) => (a.type || "text") === "text"), ...newItems] } else { textboxItemIndexes - .sort((a, b) => b - a) + .sort((a, b) => a - b) .forEach((index) => { // set to default if text has been removed - items[index] = newItems.splice(index, 1)[0] || clone(defaultItem) + items[index] = newItems.shift() || clone(defaultItem) }) // new items added From 0d2bde14a57231591b56eea540e1715c112be6f6 Mon Sep 17 00:00:00 2001 From: Kristoffer Date: Mon, 15 Jun 2026 10:09:49 +0200 Subject: [PATCH 06/32] Fixed slide Specific outputs not working properly with shortcuts #3370 --- .../components/helpers/OutputHelper.ts | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/frontend/components/helpers/OutputHelper.ts b/src/frontend/components/helpers/OutputHelper.ts index f6c5273b4..eadf7ab05 100644 --- a/src/frontend/components/helpers/OutputHelper.ts +++ b/src/frontend/components/helpers/OutputHelper.ts @@ -2,7 +2,7 @@ import { get } from "svelte/store" import { Main } from "../../../types/IPC/Main" import type { OutData } from "../../../types/Output" import type { ShowRef } from "../../../types/Projects" -import type { OutSlide, Slide } from "../../../types/Show" +import type { LayoutRef, OutSlide, Slide } from "../../../types/Show" import { AudioPlayer } from "../../audio/audioPlayer" import { sendMain } from "../../IPC/main" import { activeFocus, activeProject, activeShow, focusMode, outLocked, outputs, outputSlideCache, projects, showsCache, special } from "../../stores" @@ -52,7 +52,7 @@ export class OutputHelper { const show = this.getShow(outputId) if (!show) return - const nextSlide = this.getNextSlide({ ...show, index: undefined }) + const nextSlide = this.getNextSlide(outputId, { ...show, index: undefined }) if (!nextSlide) return this.playSlide(outputId, nextSlide) @@ -62,7 +62,7 @@ export class OutputHelper { const show = this.getShow(outputId) if (!show) return - const previousSlide = this.getPreviousSlide({ ...show, index: undefined }) + const previousSlide = this.getPreviousSlide(outputId, { ...show, index: undefined }) if (!previousSlide) return this.playSlide(outputId, previousSlide) @@ -79,7 +79,7 @@ export class OutputHelper { private static play(outputId: string, next: boolean, options: Options) { const show = this.getShow(outputId, next, options) if (show) { - const newSlide = this.getSubsequent(show, next) + const newSlide = this.getSubsequent(outputId, show, next) if (!newSlide) return this.changeProjectItem(outputId, show, next, options) if (!options.playNext && this.quickChangeBack(outputId, show, next, options)) return @@ -120,7 +120,7 @@ export class OutputHelper { let projectItems = this.getProjectItems() if (active?.index === undefined || active.id === show.id) return false if (projectItems[active.index + (next ? 1 : -1)]?.id !== show.id) return false - if (next ? this.getPreviousSlide(show) : this.getNextSlide(show)) return false + if (next ? this.getPreviousSlide(outputId, show) : this.getNextSlide(outputId, show)) return false this.changeProjectItem(outputId, active, next, options) return true @@ -151,7 +151,7 @@ export class OutputHelper { // play directly in focus mode if ((newItem?.type || "show") === "show") { - const newOut = this.getSubsequent({ id: newItem.id, layout: newItem.layout }, next) + const newOut = this.getSubsequent(outputId, { id: newItem.id, layout: newItem.layout }, next) if (newOut) this.playSlide(outputId, newOut, options.slideLayers) } else { this.playItem(outputId, newItem, next, options) @@ -167,7 +167,7 @@ export class OutputHelper { if ((newItem?.type || "show") === "show") { // allow show to load first setTimeout(() => { - const newOut = this.getSubsequent({ id: newItem.id, layout: newItem.layout }, next) + const newOut = this.getSubsequent(outputId, { id: newItem.id, layout: newItem.layout }, next) if (newOut) this.playSlide(outputId, newOut, options.slideLayers) }, 10) } else { @@ -211,7 +211,7 @@ export class OutputHelper { if (options.isSpace && !this.outShowIsSameAsActive(outSlide, activeOutShow)) return activeOutShow // prioritize outputted show in focus mode, if not reached end - if (get(focusMode) && outSlide && (nextCheck ? this.getSubsequent(outSlide, nextCheck) : true)) return this.isShow(outSlide) ? outSlide : null + if (get(focusMode) && outSlide && (nextCheck ? this.getSubsequent(outputId, outSlide, nextCheck) : true)) return this.isShow(outSlide) ? outSlide : null // must be a show item if (!this.isShow(outSlide)) return activeOutShow @@ -220,7 +220,7 @@ export class OutputHelper { if (options.playNext || nextCheck === null) return outSlide // outputted show if not reached end, or if active show is the same - if (this.getSubsequent(outSlide, nextCheck) || this.outShowIsSameAsActive(outSlide, activeOutShow)) return outSlide + if (this.getSubsequent(outputId, outSlide, nextCheck) || this.outShowIsSameAsActive(outSlide, activeOutShow)) return outSlide // active show if any return activeOutShow @@ -290,11 +290,11 @@ export class OutputHelper { // 2. Any slides in outputted show // 3. Next project item when reached end // 4. Active show/item - private static getSubsequent(data: OutSlide | null, next: boolean): OutSlide | null { - return next ? this.getNextSlide(data) : this.getPreviousSlide(data) + private static getSubsequent(outputId: string, data: OutSlide | null, next: boolean): OutSlide | null { + return next ? this.getNextSlide(outputId, data) : this.getPreviousSlide(outputId, data) } - private static getNextSlide(data: OutSlide | null): OutSlide | null { + private static getNextSlide(outputId: string, data: OutSlide | null): OutSlide | null { if (!data) return null data = clone(data) @@ -323,13 +323,13 @@ export class OutputHelper { if (layoutRef[data.index]?.data?.end) data.index = -1 data.index++ - while (layoutRef[data.index]?.data?.disabled) data.index++ + while (layoutRef[data.index] && this.slideCannotBeOutputted(outputId, layoutRef[data.index])) data.index++ } return layoutRef[data.index] ? { layout: data.layout, index: data.index, ...outSlideData } : null } - private static getPreviousSlide(data: OutSlide | null): OutSlide | null { + private static getPreviousSlide(outputId: string, data: OutSlide | null): OutSlide | null { if (!data) return null data = clone(data) @@ -355,7 +355,7 @@ export class OutputHelper { if (linesReveal.previousReveal > -1 && clickReveal._isRevealed) outSlideData.itemClickReveal = true } else { data.index-- - while (layoutRef[data.index]?.data?.disabled) data.index-- + while (layoutRef[data.index] && this.slideCannotBeOutputted(outputId, layoutRef[data.index])) data.index-- const newShowSlide: Slide | null = _show(data.id).slides([layoutRef?.[data.index]?.id]).get()?.[0] || null const styleLines = this.checkStyleLines(data, newShowSlide) @@ -370,6 +370,20 @@ export class OutputHelper { return layoutRef[data.index] ? { layout: data.layout, index: data.index, ...outSlideData } : null } + // skip slides that are bound to specific output not customId + private static slideCannotBeOutputted(outputId: string, ref: LayoutRef) { + if (!ref) return false // should always exist - but this breaks the loop if not + + // disabled + if (ref.data?.disabled) return true + + // bound to specific outputs, but not this one + const bindings = ref.data?.bindings || [] + if (bindings.length && !bindings.includes(outputId)) return true + + return false + } + private static checkStyleLines(data: OutSlide, slide: Slide | null) { const amountOfLinesToShow = getFewestOutputLines() const linesIndex = amountOfLinesToShow ? data.line || 0 : null From 3f0f45549ee7b08b8c23c8f34ae5016dc644403d Mon Sep 17 00:00:00 2001 From: Kristoffer Date: Mon, 15 Jun 2026 10:11:55 +0200 Subject: [PATCH 07/32] Removed leftover comment --- src/frontend/components/helpers/OutputHelper.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/frontend/components/helpers/OutputHelper.ts b/src/frontend/components/helpers/OutputHelper.ts index eadf7ab05..3ef071316 100644 --- a/src/frontend/components/helpers/OutputHelper.ts +++ b/src/frontend/components/helpers/OutputHelper.ts @@ -370,7 +370,6 @@ export class OutputHelper { return layoutRef[data.index] ? { layout: data.layout, index: data.index, ...outSlideData } : null } - // skip slides that are bound to specific output not customId private static slideCannotBeOutputted(outputId: string, ref: LayoutRef) { if (!ref) return false // should always exist - but this breaks the loop if not From 3823fb0b9f157df05f7a744146c4b7e552afa9c3 Mon Sep 17 00:00:00 2001 From: John Cameron Furey <174623577+jcfurey@users.noreply.github.com> Date: Tue, 16 Jun 2026 08:07:21 -0500 Subject: [PATCH 08/32] Repair show/lyrics search scorer, add caching, and support exact-phrase queries (#3390) --- src/electron/utils/files.ts | 15 +++ src/frontend/utils/search.test.ts | 148 ++++++++++++++++++++++++ src/frontend/utils/search.ts | 182 +++++++++++++----------------- 3 files changed, 240 insertions(+), 105 deletions(-) create mode 100644 src/frontend/utils/search.test.ts diff --git a/src/electron/utils/files.ts b/src/electron/utils/files.ts index 930955802..75444a9ca 100644 --- a/src/electron/utils/files.ts +++ b/src/electron/utils/files.ts @@ -1234,6 +1234,7 @@ export function loadShows(returnShows = false, reCacheNames: string[] = []) { const cachedShows = getStore("SHOWS") || {} const newCachedShows: TrimmedShows = {} const textCache: { [key: string]: string } = {} + const existingCacheText: { [key: string]: string } = getStore("CACHE")?.text || {} // create a map for quick lookup of cached shows by name const cachedShowNames = new Map() @@ -1251,6 +1252,12 @@ export function loadShows(returnShows = false, reCacheNames: string[] = []) { const matchingShowId = cachedShowNames.get(name) if (matchingShowId && !newCachedShows[matchingShowId]) { newCachedShows[matchingShowId] = cachedShows[matchingShowId] + // backfill: build text for an already-cached show that was never text-cached (e.g. it existed before the text cache) + if (!existingCacheText[matchingShowId]) { + const cachedShowData = parseShow(readFile(path.join(showsPath, `${name}.show`)) || "{}") + const cachedTxt = cachedShowData?.[1] ? getTextCacheString(cachedShowData[1]) : "" + if (cachedTxt) textCache[matchingShowId] = cachedTxt + } return } @@ -1302,6 +1309,7 @@ export async function loadShowsAsync(returnShows = false, reCacheNames: string[] const cachedShows = getStore("SHOWS") || {} const newCachedShows: TrimmedShows = {} const textCache: { [key: string]: string } = {} + const existingCacheText: { [key: string]: string } = getStore("CACHE")?.text || {} // send already cached shows to the frontend immediately if (!returnShows && !reCacheNames.length && Object.keys(cachedShows).length) { @@ -1324,6 +1332,13 @@ export async function loadShowsAsync(returnShows = false, reCacheNames: string[] const matchingShowId = cachedShowNames.get(name) if (matchingShowId && !newCachedShows[matchingShowId]) { newCachedShows[matchingShowId] = cachedShows[matchingShowId] + // backfill: build text for an already-cached show that was never text-cached + if (!existingCacheText[matchingShowId]) { + hadIo = true + const cachedShowData = parseShow((await readFileAsync(path.join(showsPath, `${name}.show`))) || "{}") + const cachedTxt = cachedShowData?.[1] ? getTextCacheString(cachedShowData[1]) : "" + if (cachedTxt) textCache[matchingShowId] = cachedTxt + } return } diff --git a/src/frontend/utils/search.test.ts b/src/frontend/utils/search.test.ts new file mode 100644 index 000000000..9708cf1b7 --- /dev/null +++ b/src/frontend/utils/search.test.ts @@ -0,0 +1,148 @@ +import { beforeEach, describe, expect, it, vi } from "vitest" + +// search.ts pulls in stores + heavy collaborators; stub them so the pure scorer can run. +const h = vi.hoisted(() => { + const makeStore = (initial: unknown) => { + let value = initial + return { _set: (v: unknown) => (value = v), subscribe: (fn: (v: unknown) => void) => (fn(value), () => {}) } + } + return { textCache: makeStore({}), categories: makeStore({}), drawerTabsData: makeStore({}) } +}) +vi.mock("../stores", () => ({ textCache: h.textCache, categories: h.categories, drawerTabsData: h.drawerTabsData })) +vi.mock("../components/helpers/array", () => ({ + sortObjectNumbers: (arr: any[], key: string, desc = false) => [...arr].sort((a, b) => (desc ? (b[key] || 0) - (a[key] || 0) : (a[key] || 0) - (b[key] || 0))) +})) +// mirror the real Levenshtein-based similarity so the test reflects production behaviour +vi.mock("../converters/txt", () => { + const editDistance = (a: string, b: string) => { + a = a.toLowerCase() + b = b.toLowerCase() + const costs: number[] = [] + for (let i = 0; i <= a.length; i++) { + let last = i + for (let j = 0; j <= b.length; j++) { + if (i === 0) costs[j] = j + else if (j > 0) { + let next = costs[j - 1] + if (a[i - 1] !== b[j - 1]) next = Math.min(Math.min(next, last), costs[j]) + 1 + costs[j - 1] = last + last = next + } + } + if (i > 0) costs[b.length] = last + } + return costs[b.length] + } + const similarity = (s1: string, s2: string) => { + const longer = s1.length >= s2.length ? s1 : s2 + const shorter = s1.length >= s2.length ? s2 : s1 + if (!longer.length) return 1 + return (longer.length - editDistance(longer, shorter)) / longer.length + } + return { similarity } +}) + +import { formatSearch, showSearch, showSearchFilter } from "./search" + +const shows = [ + { id: "amazing", name: "Amazing Grace" }, + { id: "gracealone", name: "Grace Alone" }, + { id: "great", name: "How Great Thou Art" }, + { id: "mp", name: "Blessed Be", quickAccess: { number: "MP133" } } +] as any + +const ids = (results: any[]) => results.map((r) => r.id) + +describe("formatSearch", () => { + it("lowercases and strips punctuation + diacritics", () => { + expect(formatSearch("Café, Réal!")).toBe("cafe real") + }) + it("optionally removes spaces", () => { + expect(formatSearch("amazing grace", true)).toBe("amazinggrace") + }) + it("returns empty string for non-strings", () => { + expect(formatSearch(undefined as unknown as string)).toBe("") + }) +}) + +describe("showSearchFilter", () => { + beforeEach(() => h.textCache._set({})) + + it("scores an exact title match 100", () => { + expect(showSearchFilter("Amazing Grace", shows[0])).toBe(100) + }) + it("scores a title starts-with match 100", () => { + expect(showSearchFilter("amaz", shows[0])).toBe(100) + }) + it("scores a song-number exact match 100", () => { + expect(showSearchFilter("mp133", shows[3])).toBe(100) + }) + it("ranks a title-word hit above a content-only hit", () => { + h.textCache._set({ great: "amazing love how can it be" }) + const titleHit = showSearchFilter("amazing", shows[0]) // word in the title + const contentHit = showSearchFilter("amazing", shows[2]) // word only in content + expect(contentHit).toBeGreaterThan(0) + expect(titleHit).toBeGreaterThan(contentHit) + }) + it("returns 0 for no match", () => { + expect(showSearchFilter("xylophone", shows[0])).toBe(0) + }) + it("ignores empty / punctuation-only queries (no match-everything bug)", () => { + expect(showSearchFilter("", shows[0])).toBe(0) + expect(showSearchFilter("!!!", shows[0])).toBe(0) + }) +}) + +describe("showSearch ranking", () => { + beforeEach(() => h.textCache._set({})) + + it("ranks a show containing ALL query words above one with only some (multi-word)", () => { + const res = showSearch("amazing grace", shows) + expect(ids(res)[0]).toBe("amazing") + expect(ids(res)).toContain("gracealone") + expect(ids(res).indexOf("amazing")).toBeLessThan(ids(res).indexOf("gracealone")) + }) + it("finds a show by lyric content when the title doesn't match", () => { + h.textCache._set({ great: "thou my everlasting portion more than friend or life to me" }) + const res = showSearch("everlasting portion", shows) + expect(ids(res)[0]).toBe("great") + }) + it("excludes non-matching shows", () => { + const res = showSearch("zzz nonexistent", shows) + expect(res.some((r) => r.id === "amazing")).toBe(false) + }) + it("normalizes the top match to 100", () => { + const res = showSearch("grace", shows) + expect(res[0].match).toBe(100) + }) + it("does not flood results with unrelated shows (fuzzy similarity alone never matches)", () => { + // regression: similarity() is non-zero for unrelated text; it must not include non-matching shows + const res = showSearch("kitchen", shows) + expect(res.length).toBe(0) + }) + it("still matches a close typo via fuzzy title similarity", () => { + const res = showSearch("amzinggrace", shows) + expect(res[0]?.id).toBe("amazing") + }) +}) + +describe("exact phrase (quoted) search", () => { + beforeEach(() => h.textCache._set({})) + + it("matches a quoted phrase that appears in the title", () => { + expect(showSearchFilter('"amazing grace"', shows[0])).toBe(100) + }) + it("does not match without the exact phrase, and ignores fuzzy/typos", () => { + expect(showSearchFilter('"amazing grace"', shows[1])).toBe(0) + expect(showSearchFilter('"amzing grace"', shows[0])).toBe(0) + }) + it("matches a quoted phrase found in lyrics/content", () => { + h.textCache._set({ great: "thou my everlasting portion more than friend" }) + const res = showSearch('"everlasting portion"', shows) + expect(res[0]?.id).toBe("great") + }) + it("returns nothing when the quoted phrase matches no show", () => { + const res = showSearch('"not a real phrase"', shows) + expect(res.length).toBe(0) + }) +}) diff --git a/src/frontend/utils/search.ts b/src/frontend/utils/search.ts index 78d156e58..258f15ae7 100644 --- a/src/frontend/utils/search.ts +++ b/src/frontend/utils/search.ts @@ -27,13 +27,8 @@ export function isRefinement(newTokens: string[], oldTokens: string[]): boolean } export function showSearch(searchValue: string, shows: ShowList[]): ShowList[] { - // WIP return fastSearch(searchValue, shows) - let newShows: ShowList[] = [] - // fix invalid regular expression - searchValue = searchValue.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1") - shows.forEach((s) => { // don't search show if archived const isArchived = get(categories)[s.category || ""]?.isArchive @@ -46,126 +41,111 @@ export function showSearch(searchValue: string, shows: ShowList[]): ShowList[] { // change all values relative to the highest value const highestValue = newShows[0]?.match || 0 - newShows = newShows.map((a) => ({ ...a, originalMatch: a.match, match: ((a.match || 0) / highestValue) * 100 })) + newShows = newShows.map((a) => ({ ...a, originalMatch: a.match, match: highestValue ? ((a.match || 0) / highestValue) * 100 : 0 })) return newShows } +// Scoring model (max ~130, clamped to 99 for non-exact): +// - exact song number / CCLI / title, or title starts-with -> 100 +// - title word coverage -> up to 65 (a title hit always beats a content-only hit) +// - content word coverage -> up to 35 (multi-word: more matched words ranks higher) +// - fuzzy title similarity -> up to 20 (typo tolerance) +// - content density bonus -> up to 10 (tiebreaker for repeated/contiguous hits) export function showSearchFilter(searchValue: string, show: ShowList) { if (!show.name) return 0 - // WIP tag search? + // a "quoted" query forces a strict, literal phrase match (no fuzzy / per-word scatter) + const trimmedQuery = searchValue.trim() + if (trimmedQuery.length > 2 && trimmedQuery.startsWith('"') && trimmedQuery.endsWith('"')) { + return exactPhraseScore(trimmedQuery.slice(1, -1), show) + } - // Priority 0: Song Number Exact Match (supports alphanumeric like "MP133") const songNumber: string = show.quickAccess?.number || "" const formattedSongNumber = formatSearch(songNumber, true) const formattedSearchValue = formatSearch(searchValue, true) + + // Priority 0: song number exact match (supports alphanumeric like "MP133") if (songNumber && formattedSongNumber === formattedSearchValue) return 100 - // Priority 0.5: CCLI Exact Match + // Priority 0.5: CCLI exact match const songId = show.quickAccess?.metadata?.CCLI || "" - if (songId.toString() === searchValue) return 100 + if (songId && songId.toString() === searchValue.trim()) return 100 const showName = formatSearch(show.name, true) - const showNameWithNumber = songNumber + showName + const showNameWithNumber = formattedSongNumber + showName - // Priority 1: Title Exact Match + // Priority 1: title exact match if (formattedSearchValue === showName || formattedSearchValue === showNameWithNumber) return 100 - - // Priority 1.25: Song Number Starts With Match - // if (songNumber && formattedSongNumber.startsWith(formattedSearchValue)) return 100 - - // Priority 1.5: Title Word Start Match - if (showName.startsWith(formattedSearchValue)) return 100 - - const cache = get(textCache)[show.id] || "" - - // Multi-word search - check if ALL words appear in content - const multiWordMatchScore = calculateMultiWordMatch(searchValue, cache, show.name) - - // Priority 2: Content Includes Percentage Match - const contentIncludesMatchScore = calculateContentIncludesScore(cache, searchValue) // + calculateContentIncludesScore(cache, searchValue, true) - - // Priority 3: Title Word-for-Word Match - const titleWordMatch = matchWords(showNameWithNumber, searchValue) - const titleIncludesMatchScore = titleWordMatch * 0.5 * 100 // max 50% - - // Priority 4: Title Letter-for-Letter Match - const titleSimilarity = similarity(showNameWithNumber, removeShortWords(formatSearch(searchValue, true))) - const titleSimilarityMatchScore = titleSimilarity * 0.3 * 100 // max 30% - - // Priority 5: Content Word-for-Word Match - let contentWordMatchScore = 0 - if (cache) { - const formattedCache = formatSearch(cache, true) - const wordMatchCount = matchWords(formattedCache, searchValue) - const wordMatchCountExtra = matchWords(formattedCache, removeShortWords(searchValue)) - contentWordMatchScore = Math.min(wordMatchCount, 100) * 0.03 + Math.min(wordMatchCountExtra, 100) * 0.07 // max 10% - } - - // Priority 6: Content Letter-for-Letter Match - // let contentSimilarityMatchScore = 0 - // if (cache) { - // const contentSimilarity = similarity(removeShortWords(formatSearch(cache, true)), removeShortWords(formatSearch(searchValue, true))) - // contentSimilarityMatchScore = contentSimilarity * 0.05 * 100 // max 5% - // } - - const combinedScore = multiWordMatchScore + contentIncludesMatchScore + titleIncludesMatchScore + titleSimilarityMatchScore + contentWordMatchScore - return combinedScore >= 100 ? 99 : combinedScore < 3 ? 0 : combinedScore + // Priority 1.5: title starts-with match (guard empty/punctuation-only queries so they don't match everything) + if (formattedSearchValue && showName.startsWith(formattedSearchValue)) return 100 + + // accent/case/punctuation-insensitive query words + const queryWords = tokenize(formatSearch(searchValue, false)) + if (!queryWords.length) return 0 + // for content, ignore very short words (the/of/a) unless that's all the query has + const longWords = queryWords.filter((w) => w.length >= 3) + const wordsForContent = longWords.length ? longWords : queryWords + + const titleText = formatSearch(`${songNumber} ${show.name}`, false) + const contentText = formatSearch(get(textCache)[show.id] || "", false) + + // Word coverage = fraction of query words present. Title is weighted well above + // content, so a title hit outranks a content-only hit and multi-word queries + // rank shows that contain MORE of the words higher. + const titleCoverageScore = wordCoverage(titleText, queryWords) * 65 + const contentCoverageScore = wordCoverage(contentText, wordsForContent) * 35 + + // Small tiebreaker: reward repeated/contiguous occurrences of the query in content + const contentDensityScore = contentDensity(contentText, formattedSearchValue) + + // Fuzzy title similarity (0-1). IMPORTANT: similarity() is non-zero even for unrelated + // text, so it must never make a show match on its own — only a strong near-match (typo) + // qualifies; otherwise it just refines ranking among shows that already matched. + const trimmedSearch = formatSearch(removeShortWords(formatSearch(searchValue, false)), true) + const titleSimilarity = trimmedSearch ? similarity(showNameWithNumber, trimmedSearch) : 0 + + // Require a real match: an actual word/phrase hit, or a strong fuzzy title match. + if (!(titleCoverageScore || contentCoverageScore || contentDensityScore || titleSimilarity >= 0.7)) return 0 + + const combinedScore = titleCoverageScore + contentCoverageScore + titleSimilarity * 20 + contentDensityScore + return combinedScore >= 100 ? 99 : combinedScore } -function calculateMultiWordMatch(searchValue: string, cache: string, showName: string): number { - return 0 // WIP got worse results with this - const queryWords = tokenize(searchValue).filter((w) => w.length >= 3) - const contentLower = formatSearch(cache, false) - const nameLower = formatSearch(showName, false) - - let wordMatchScore = 0 - if (queryWords.length > 0) { - let nameMatches = 0 - let contentMatches = 0 +// strict literal-phrase match for quoted queries: the phrase must appear contiguously (normalized), no fuzzy +function exactPhraseScore(phrase: string, show: ShowList): number { + const needle = formatSearch(phrase, false) + if (!needle) return 0 - for (const word of queryWords) { - if (nameLower.includes(word)) nameMatches++ - if (contentLower.includes(word)) contentMatches++ - } + const songNumber = show.quickAccess?.number || "" + const title = formatSearch(`${songNumber} ${show.name}`, false) + if (title.includes(needle)) return 100 - // Score based on percentage of words matched - const nameMatchRatio = nameMatches / queryWords.length - const contentMatchRatio = contentMatches / queryWords.length - - // Name matches are more valuable - wordMatchScore = nameMatchRatio * 40 + contentMatchRatio * 30 - } + const content = formatSearch(get(textCache)[show.id] || "", false) + if (content.includes(needle)) return 60 - return wordMatchScore + return 0 } -function calculateContentIncludesScore(cache: string, search: string, noShortWords = false): number { - if (!cache) return 0 - - // remove short words - cache = formatSearch(noShortWords ? removeShortWords(cache) : cache, true) - search = formatSearch(noShortWords ? removeShortWords(search) : search, true) - - let re - try { - re = new RegExp(search, "g") - } catch (err) { - console.error(err) - return 0 - } +// fraction (0-1) of words present in text +function wordCoverage(text: string, words: string[]): number { + if (!words.length || !text) return 0 + const matched = words.filter((word) => text.includes(word)).length + return matched / words.length +} - const occurrences = (cache.match(re) || []).length - const cacheLength = cache.length +// reward repeated/contiguous occurrences of the de-spaced query; capped small (max 10) +function contentDensity(contentText: string, despacedQuery: string): number { + if (!contentText || despacedQuery.length < 2) return 0 - // content includes match score, based on occurrences relative to cache length - if (cacheLength > 0) { - const percentageMatch = Math.min(((occurrences * search.length) / cacheLength) * 40, 1) - // return percentageMatch * (noShortWords ? 20 : 50) // max 70% - return percentageMatch * 70 // max 70% + const haystack = contentText.replace(/\s+/g, "") + let count = 0 + let i = haystack.indexOf(despacedQuery) + while (i !== -1) { + count++ + i = haystack.indexOf(despacedQuery, i + despacedQuery.length) } - return 0 + return Math.min(count * 3, 10) } function removeShortWords(value: string) { @@ -174,11 +154,3 @@ function removeShortWords(value: string) { .filter((a) => a.length > 2) .join(" ") } - -function matchWords(text: string, value: string): number { - const words = value.split(" ").filter(Boolean) - const matchCount = words.filter((word) => text.includes(word)).length - - // value between 0 and 1 - return matchCount / words.length -} From 11d01eaf4ffcdd7f301b40d0a1b6da114ae6283d Mon Sep 17 00:00:00 2001 From: Kristoffer Date: Wed, 17 Jun 2026 09:16:19 +0200 Subject: [PATCH 09/32] Locale placeholders --- public/lang/en.json | 1 + .../components/settings/tabs/Outputs.svelte | 6 +++--- src/frontend/utils/language.ts | 19 ++++++++++++------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/public/lang/en.json b/public/lang/en.json index c8031cf14..4dc298334 100644 --- a/public/lang/en.json +++ b/public/lang/en.json @@ -680,6 +680,7 @@ "done": "Done", "disable": "Disable", "enable": "Enable", + "enable_specific": "Enable $1", "undo": "Undo", "redo": "Redo", "cut": "Cut", diff --git a/src/frontend/components/settings/tabs/Outputs.svelte b/src/frontend/components/settings/tabs/Outputs.svelte index 6af6c8796..652b9a5a0 100644 --- a/src/frontend/components/settings/tabs/Outputs.svelte +++ b/src/frontend/components/settings/tabs/Outputs.svelte @@ -375,7 +375,7 @@ <InputRow arrow={currentOutput?.ndi} bind:open={ndiMenuOpened}> - <MaterialToggleSwitch label="actions.enable NDI®" style="width: 100%;" checked={currentOutput?.ndi} defaultValue={false} data={$ndiData[currentOutput?.id || ""]?.connections || null} on:change={(e) => updateOutput("ndi", e.detail)} /> + <MaterialToggleSwitch label={translateText("actions.enable_specific", null, ["NDI®"])} style="width: 100%;" checked={currentOutput?.ndi} defaultValue={false} data={$ndiData[currentOutput?.id || ""]?.connections || null} on:change={(e) => updateOutput("ndi", e.detail)} /> <svelte:fragment slot="menu"> {#if currentOutput} @@ -394,7 +394,7 @@ <Title label="Blackmagic Design" icon="blackmagic" /> <InputRow arrow={currentOutput?.blackmagic} bind:open={bmdMenuOpened}> - <MaterialToggleSwitch label="actions.enable Blackmagic" style="width: 100%;" checked={currentOutput?.blackmagic} defaultValue={false} on:change={(e) => updateOutput("blackmagic", e.detail)} /> + <MaterialToggleSwitch label={translateText("actions.enable_specific", null, ["Blackmagic"])} style="width: 100%;" checked={currentOutput?.blackmagic} defaultValue={false} on:change={(e) => updateOutput("blackmagic", e.detail)} /> <svelte:fragment slot="menu"> <MaterialDropdown @@ -428,7 +428,7 @@ <Title label="WebRTC Streaming" icon="record" /> <InputRow arrow={currentOutput?.webrtc} bind:open={webrtcMenuOpened}> - <MaterialToggleSwitch label="actions.enable WebRTC" style="width: 100%;" checked={currentOutput?.webrtc} defaultValue={false} on:change={(e) => updateOutput("webrtc", e.detail)} /> + <MaterialToggleSwitch label={translateText("actions.enable_specific", null, ["WebRTC"])} style="width: 100%;" checked={currentOutput?.webrtc} defaultValue={false} on:change={(e) => updateOutput("webrtc", e.detail)} /> <svelte:fragment slot="menu"> {#if currentOutput} diff --git a/src/frontend/utils/language.ts b/src/frontend/utils/language.ts index 5c1087286..d98dfd736 100644 --- a/src/frontend/utils/language.ts +++ b/src/frontend/utils/language.ts @@ -80,18 +80,23 @@ function setLanguage(locale = "", init = false) { // new translate function // can take a "main.yes" into "Yes", or "main.yes [y]" into "Yes [y]" -export function translateText(text: string, _updater: any = null) { +export function translateText(text: string, _updater: any = null, replaceValues: string[] = []) { if (typeof text !== "string" || !text) return "" const dict = get(dictionary) - return text.replace(/\$?([a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)/g, (match, key1, key2) => { - if (dict[key1] && dict[key1][key2]) { - return dict[key1][key2] - } + return text + .replace(/\$?([a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)/g, (match, key1, key2) => { + if (dict[key1] && dict[key1][key2]) { + return dict[key1][key2] + } - return match - }) + return match + }) + .replace(/\$(\d+)/g, (match, index) => { + const value = replaceValues[Number(index) - 1] + return value !== undefined ? value : match + }) } export { setLanguage } From 388e87b9c76b0fde80fde2343ef1d18e20a436fc Mon Sep 17 00:00:00 2001 From: Kristoffer <kristoffervassbo@gmail.com> Date: Wed, 17 Jun 2026 10:26:49 +0200 Subject: [PATCH 10/32] Fixed NDI Audio issue --- src/electron/ndi/NdiSender.ts | 58 +++++++++++++++-------------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/src/electron/ndi/NdiSender.ts b/src/electron/ndi/NdiSender.ts index 8ca79c563..c4dcb50cc 100644 --- a/src/electron/ndi/NdiSender.ts +++ b/src/electron/ndi/NdiSender.ts @@ -41,6 +41,9 @@ export class NdiSender { private static readonly CONNECTION_POLL_INTERVAL_MS = 1000 private static readonly TIMECODE_DIVISOR = BigInt(100) + static timeStart = BigInt(Date.now()) * BigInt(1e6) - process.hrtime.bigint() + static audioSamplesSent: bigint | null = null + static NDI: { [key: string]: { name: string @@ -55,10 +58,6 @@ export class NdiSender { paddedVideoBuffer?: Buffer paddedVideoBufferStride?: number paddedVideoBufferHeight?: number - timeStart?: bigint - frameNumber?: number - lastFramerate?: number - audioSamplesSent?: bigint } } = {} @@ -99,15 +98,6 @@ export class NdiSender { } } - private static calculateTimeStart(): bigint { - return BigInt(Date.now()) * BigInt(1e6) - process.hrtime.bigint() - } - - private static calculateTimecode(timeStart: bigint, frameNumber: number, framerate: number): bigint { - const frameIntervalNs = BigInt(Math.round((1000 / framerate) * 1e6)) - return (timeStart + BigInt(frameNumber) * frameIntervalNs) / this.TIMECODE_DIVISOR - } - static initNameNDI(name?: string, outputName?: string) { return name || `FreeShow NDI${outputName ? ` - ${outputName}` : ""}` } @@ -117,10 +107,7 @@ export class NdiSender { this.NDI[id] = { name, - groups, - timeStart: this.calculateTimeStart(), - frameNumber: 0, - lastFramerate: 0 + groups } console.info("NDI - creating sender: " + this.NDI[id].name, groups ? `; In group: ${groups}` : "") @@ -162,7 +149,7 @@ export class NdiSender { static async sendVideoBufferNDI(id: string, buffer: Buffer, { size = { width: 1280, height: 720 }, ratio = 16 / 9, framerate = 1, transparent = true }) { const senderData = this.NDI[id] - if (!senderData?.timeStart || !senderData.sender) return + if (!senderData?.sender) return const grandiose = await loadGrandiose() if (!grandiose) return @@ -173,16 +160,7 @@ export class NdiSender { const fourCC = transparent ? grandiose.FOURCC_BGRA : grandiose.FOURCC_BGRX if (!transparent) util.ImageBufferAdjustment.BGRAtoBGRX(buffer) - // reset frame counter on framerate change to prevent accumulated delay - if (senderData.lastFramerate !== framerate) { - senderData.frameNumber = 0 - senderData.lastFramerate = framerate - senderData.timeStart = this.calculateTimeStart() - senderData.audioSamplesSent = BigInt(0) - } - - const timecode = this.calculateTimecode(senderData.timeStart, senderData.frameNumber ?? 0, framerate) - senderData.frameNumber = (senderData.frameNumber ?? 0) + 1 + const timecode = (this.timeStart + process.hrtime.bigint()) / this.TIMECODE_DIVISOR // Pad width to 16-byte alignment for NDI const paddedWidth = (size.width + this.PADDING_ALIGNMENT - 1) & ~(this.PADDING_ALIGNMENT - 1) @@ -242,8 +220,8 @@ export class NdiSender { } static async sendAudioBufferNDI(buffer: Buffer, { sampleRate, channelCount }: { sampleRate: number; channelCount: number }) { - const activeSender = Object.values(this.NDI).find((s) => s?.sendAudio && s?.timeStart) - if (!activeSender?.timeStart) return + const activeSender = Object.values(this.NDI).find((s) => s?.sendAudio) + if (!activeSender) return const ndiAudioBuffer = convertPCMtoPlanarFloat32(buffer, channelCount) if (!ndiAudioBuffer) return @@ -253,16 +231,28 @@ export class NdiSender { const noSamples = Math.trunc(ndiAudioBuffer.byteLength / channelCount / this.BYTES_PER_FLOAT32) - if (activeSender.audioSamplesSent === undefined) activeSender.audioSamplesSent = BigInt(0) + const currentHrTime = process.hrtime.bigint() + + if (this.audioSamplesSent === null || this.audioSamplesSent === undefined) { + this.audioSamplesSent = (currentHrTime * BigInt(sampleRate)) / BigInt(1e9) + } else { + const expectedHrTime = (this.audioSamplesSent * BigInt(1e9)) / BigInt(sampleRate) + const driftNs = currentHrTime - expectedHrTime + const driftMs = Number(driftNs) / 1e6 + + if (Math.abs(driftMs) > 500) { + this.audioSamplesSent = (currentHrTime * BigInt(sampleRate)) / BigInt(1e9) + } + } - const timecode = (activeSender.timeStart + (activeSender.audioSamplesSent * BigInt(1000000000)) / BigInt(sampleRate)) / this.TIMECODE_DIVISOR - activeSender.audioSamplesSent += BigInt(noSamples) + const timecode = (this.timeStart + (this.audioSamplesSent * BigInt(1e9)) / BigInt(sampleRate)) / this.TIMECODE_DIVISOR + this.audioSamplesSent += BigInt(noSamples) const frame = { timecode, sampleRate, noChannels: channelCount, - noSamples: Math.trunc(ndiAudioBuffer.byteLength / channelCount / this.BYTES_PER_FLOAT32), + noSamples, channelStrideBytes: Math.trunc(ndiAudioBuffer.byteLength / channelCount), fourCC: grandiose.FOURCC_FLTp, data: ndiAudioBuffer From 9e03a26d284e762c1ba46c8f713414688b1c9702 Mon Sep 17 00:00:00 2001 From: Kristoffer <kristoffervassbo@gmail.com> Date: Wed, 17 Jun 2026 10:45:08 +0200 Subject: [PATCH 11/32] Updated Korean language --- public/lang/ko.json | 52 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/public/lang/ko.json b/public/lang/ko.json index 6bf2dd930..04cd764ce 100644 --- a/public/lang/ko.json +++ b/public/lang/ko.json @@ -83,6 +83,7 @@ "context_trim": "각 줄의 시작과 끝에 있는 공백과 구두점을 제거하고, 이중 공백을 지웁니다." }, "tips": { + "show_add_project": "엔터 키를 눌러 프로젝트에 추가", "trigger": "트리거는 일반적으로 카메라의 사전 설정을 바꾸기 위한 HTTP 요청을 보내는 데 사용합니다.", "global_options": "이 선택 사항들은 전체에 적용됩니다.", "consider_templates": "여러 슬라이드에 같은 변경사항을 적용한다면, '템플릿' 사용을 고려해보세요.", @@ -185,6 +186,8 @@ }, "media": { "_loop": "반복", + "soft_loop": "부드러운 반복", + "auto_next": "자동으로 다음 재생", "play": "재생", "play_multiple": "여러 번 재생", "toggle_shuffle": "셔플 토글", @@ -192,6 +195,7 @@ "previous": "이전", "play_no_audio": "오디오 없이 재생", "play_no_filters": "필터 없이 재생", + "fade_out": "페이드 아웃", "favourite": "즐겨찾기", "effects_library_add": "효과 라이브러리에 추가", "effects_library_remove": "효과 라이브러리에서 제거", @@ -201,6 +205,7 @@ "forward10": "10초 앞으로", "volume": "볼륨", "gain": "게인", + "pitch": "음정", "speed": "속도", "flip": "뒤집기", "flip_horizontally": "가로로 뒤집기", @@ -231,12 +236,35 @@ "open_audio_folder": "연결된 오디오 폴더 열기", "create_audio_folder": "오디오 폴더 생성", "custom_output": "사용자 정의 오디오 내보내기", + "channel": "오디오 채널", "mute_when_video_plays": "비디오 작동시 소리 안나옴", "allow_gaining": "증폭 허용", "allow_gaining_tip": "100%보다 큰 볼륨 허용(소리가 왜곡될 수 있음)", "mixer": "믹서", "equalizer": "이퀄라이저", + "gate": "게이트", + "compressor": "컴프레서", + "limiter": "리미터", + "reverb": "잔향효과", + "delay": "지연효과", + "filter": "필터", + "stereo_shaper": "스테레오 셰이퍼", "preset": "사전설정", + "threshold": "임계값", + "ratio": "비율", + "knee": "기울기 특성", + "attack": "반응 속도", + "release": "회복 속도", + "ceiling": "한계", + "hysteresis": "이력 현상", + "feedback": "되먹임", + "wet": "적용 정도", + "room_size": "공간 크기", + "dampening": "흡수", + "filter_type": "필터 유형", + "frequency": "주파수", + "q_factor": "Q 값", + "width": "폭", "main": "메인", "metronome": "메트로놈", "click_sound": "클릭 소리", @@ -507,12 +535,15 @@ "manage_metadata": "메타데이터 관리", "manage_dynamic_values": "동적 값 관리", "template_style_overrides": "스타일 덮어씌우기", + "regex_manager": "정규식 관리자", "choose_camera": "카메라 선택", "manage_tags": "꼬리표 관리", "sync_categories": "분류 동기화", "effect_items": "효과 항목", "timeline": "시간선", "timecode": "타임코드", + "drawer_search_options": "검색 선택사항", + "template_info": "템플릿 정보", "initialize": "FreeShow에 오신 것을 환영합니다", "unsaved": "정말 종료하시겠습니까?", "cancel": "취소", @@ -569,6 +600,7 @@ "show_convert": "쇼로 변환", "project": "새로운 프로젝트", "section": "새로운 구역", + "placeholder": "자리 표시자", "style": "새로운 스타일", "profile": "새로운 프로필", "timer": "새로운 타이머", @@ -635,11 +667,13 @@ "imported": "가져왔습니다!", "duplicate": "복제", "duplicated": "복제함", + "make_unique": "고유화", "delete": "삭제", "delete_slide_short": "슬라이드 삭제", "delete_slide": "쇼에서 슬라이드 삭제", "delete_group": "그룹 삭제", "delete_all": "모두 삭제", + "delete_project_folder_tip": "이 폴더를 삭제하면 그 안에 있는 모든 프로젝트와 폴더도 함께 삭제됩니다.", "close": "닫기", "save": "저장", "done": "완료", @@ -783,8 +817,10 @@ "next_project_item": "다음 프로젝트 항목", "previous_project_item": "이전 프로젝트 항목", "index_select_project_item": "해당 색인의 프로젝트 항목 열기", + "name_start_project_item": "이름으로 프로젝트 항목 시작", "name_select_show": "해당 이름의 쇼 열기", "set_template_active": "활성 쇼에 템플릿 설정", + "change_slide_timeline_speed": "슬라이드 시간선 속도 변경", "random_slide": "임의의 슬라이드 재생", "index_select_slide": "해당 색인의 슬라이드 재생", "name_select_slide": "해당 이름의 슬라이드 재생", @@ -826,6 +862,7 @@ "activate_output_changed": "출력 변경시 활성화", "activate_slide_clicked": "슬라이드를 누를 때 활성화", "activate_group_start": "그룹이 시작되었을 때 활성화", + "activate_pdf_start": "PDF를 시작할 때 활성화", "activate_video_starting": "동영상을 시작할 때 활성화", "activate_video_ending": "동영상이 끝날 때 활성화", "activate_audio_starting": "오디오를 시작할 때 활성화", @@ -856,7 +893,9 @@ }, "timeline": { "toggle_timeline": "시간선 토글", - "start_time": "시작 시각" + "start_time": "시작 시각", + "add_keyframe": "키프레임 추가", + "toggle_curve_editor": "곡선 편집기 토글" }, "animate": { "change": "변경", @@ -1065,6 +1104,7 @@ "special": "특수", "scrolling": "스크롤", "scrolling_speed": "스크롤 시간", + "gap": "간격", "top_bottom": "위에서 아래로", "bottom_top": "아래에서 위로", "left_right": "왼쪽에서 오른쪽", @@ -1394,9 +1434,15 @@ "state_locked": "잠김", "disabled_layers": "비활성 레이어", "mute": "출력 음소거", - "unmute": "출력 음소거 해제" + "unmute": "출력 음소거 해제", + "start_streaming": "스트리밍 시작", + "stop_streaming": "스트리밍 중단", + "confirm_stop": "정말 스트리밍을 중단하시겠습니까?", + "is_live": "송출", + "go_live": "송출하기" }, "profile": { + "profile": "프로필", "profiles_hint": "사용자가 프로그램의 특정 부분만 제어하거나 편집할 수 있도록 접근을 제한하는 여러 개의 프로필/역할을 만듭니다.", "admin": "관리자", "none": "없음", @@ -1466,7 +1512,7 @@ "combine_with_text": "텍스트와 결합", "first_slide_reference": "첫 번째 슬라이드에 참조 표시", "reference_at_bottom": "하단으로 이동", - "red_jesus": "예수님의 말씀을 빨간색으로", + "red_jesus": "예수님 말씀 강조", "show_all": "모두 보기", "search": "성경에서 검색" }, From 2488291cabab80666bb3819fcac9408c671e528e Mon Sep 17 00:00:00 2001 From: Kristoffer <kristoffervassbo@gmail.com> Date: Wed, 17 Jun 2026 10:56:44 +0200 Subject: [PATCH 12/32] Scripture shows now prioritize the category icon - Fixed certain space characters not rendered - Fixed deleting duplicated shows issue --- .../edit/editbox/EditboxLines.svelte | 4 ++-- .../components/inputs/ShowButton.svelte | 8 +++---- .../main/popups/DeleteDuplicatedShows.svelte | 23 +++++++++++-------- .../components/slide/TextboxLines.svelte | 6 ++--- .../remote/components/show/Textbox.svelte | 4 ++-- src/server/stage/components/Textbox.svelte | 4 ++-- 6 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/frontend/components/edit/editbox/EditboxLines.svelte b/src/frontend/components/edit/editbox/EditboxLines.svelte index 49f7ea887..5e2de0afc 100644 --- a/src/frontend/components/edit/editbox/EditboxLines.svelte +++ b/src/frontend/components/edit/editbox/EditboxLines.svelte @@ -859,8 +859,8 @@ } .edit :global(.break) { - /* balanced breaking, looks much cleaner */ - text-wrap: balance; + text-wrap: balance; /* balanced breaking, looks much cleaner */ + white-space: pre; /* preserve special spaces from Text edit */ } .edit :global(.break.normalWrap) { text-wrap: unset; diff --git a/src/frontend/components/inputs/ShowButton.svelte b/src/frontend/components/inputs/ShowButton.svelte index 25bd45e87..005ffc523 100644 --- a/src/frontend/components/inputs/ShowButton.svelte +++ b/src/frontend/components/inputs/ShowButton.svelte @@ -59,15 +59,15 @@ subicon = "" if (type === "show") { if ($shows[show.id]?.private) iconID = "private" - else if ($showsCache[show.id]?.reference?.type === "scripture") { + else if ($shows[show.id]?.category && $categories[$shows[show.id].category || ""]) { + custom = true + iconID = $categories[$shows[show.id].category || ""].icon || null + } else if ($showsCache[show.id]?.reference?.type === "scripture") { custom = true iconID = "scripture" } else if ($showsCache[show.id]?.reference?.type === "calendar") { custom = true iconID = "event" - } else if ($shows[show.id]?.category && $categories[$shows[show.id].category || ""]) { - custom = true - iconID = $categories[$shows[show.id].category || ""].icon || null } else iconID = "noIcon" } else if (type === "audio") { iconID = "music" diff --git a/src/frontend/components/main/popups/DeleteDuplicatedShows.svelte b/src/frontend/components/main/popups/DeleteDuplicatedShows.svelte index 2e8104c44..fa1abdd67 100644 --- a/src/frontend/components/main/popups/DeleteDuplicatedShows.svelte +++ b/src/frontend/components/main/popups/DeleteDuplicatedShows.svelte @@ -1,6 +1,6 @@ <script lang="ts"> import { Show } from "../../../../types/Show" - import { activePage, activePopup, popupData, shows, showsCache } from "../../../stores" + import { activePopup, popupData, shows, showsCache } from "../../../stores" import { getSlideText } from "../../edit/scripts/textStyle" import { history } from "../../helpers/history" import { loadShows } from "../../helpers/setShow" @@ -44,15 +44,13 @@ deleteShows(deleteIds) // loading = false - activePopup.set(null) - activePage.set("show") + close() } const oldest = getOldestShows() function deleteOldest() { deleteShows(oldest) - activePopup.set(null) - activePage.set("show") + close() } function getOldestShows() { let deleteIds: string[] = [] @@ -85,8 +83,7 @@ const newest = getNewestShows() function deleteNewest() { deleteShows(newest) - activePopup.set(null) - activePage.set("show") + close() } function getNewestShows() { let deleteIds: string[] = [] @@ -114,6 +111,15 @@ return deleteIds } + function close() { + // activePage.set("show") + + // give time for deletion to run + setTimeout(() => { + if ($activePopup === "delete_duplicated_shows") activePopup.set(null) + }, 500) + } + // MANUAL let manualDeletion = false @@ -142,8 +148,7 @@ let loadedTexts: string[] = [] function next() { if (!data[manualIndex + 1]) { - activePopup.set(null) - activePage.set("show") + close() return } diff --git a/src/frontend/components/slide/TextboxLines.svelte b/src/frontend/components/slide/TextboxLines.svelte index 4aa22a91b..007134bf8 100644 --- a/src/frontend/components/slide/TextboxLines.svelte +++ b/src/frontend/components/slide/TextboxLines.svelte @@ -456,10 +456,10 @@ overflow-wrap: break-word; /* line-break: after-white-space; - -webkit-line-break: after-white-space; */ + -webkit-line-break: after-white-space; */ - /* balanced breaking, looks much cleaner */ - text-wrap: balance; + text-wrap: balance; /* balanced breaking, looks much cleaner */ + white-space: pre; /* preserve special spaces from Text edit */ } /* normal wrap if "Text on one line (nowrap)" or Justify aligned */ diff --git a/src/server/remote/components/show/Textbox.svelte b/src/server/remote/components/show/Textbox.svelte index 01d6d589e..c6ef4eb3e 100644 --- a/src/server/remote/components/show/Textbox.svelte +++ b/src/server/remote/components/show/Textbox.svelte @@ -124,8 +124,8 @@ /* line-break: after-white-space; -webkit-line-break: after-white-space; */ - /* balanced breaking, looks much cleaner */ - text-wrap: balance; + text-wrap: balance; /* balanced breaking, looks much cleaner */ + white-space: pre; /* preserve special spaces from Text edit */ } .break span { diff --git a/src/server/stage/components/Textbox.svelte b/src/server/stage/components/Textbox.svelte index cf307b762..d2257efca 100644 --- a/src/server/stage/components/Textbox.svelte +++ b/src/server/stage/components/Textbox.svelte @@ -618,8 +618,8 @@ /* line-break: after-white-space; -webkit-line-break: after-white-space; */ - /* balanced breaking, looks much cleaner */ - text-wrap: balance; + text-wrap: balance; /* balanced breaking, looks much cleaner */ + white-space: pre; /* preserve special spaces from Text edit */ } /* span { From ee6818db50670c197983cc2c9fb983aae89e8cc2 Mon Sep 17 00:00:00 2001 From: Kristoffer <kristoffervassbo@gmail.com> Date: Wed, 17 Jun 2026 11:38:55 +0200 Subject: [PATCH 13/32] Added CSS variable --slide-group-color --- .../components/edit/editbox/Editbox.svelte | 6 ++-- .../components/helpers/showActions.ts | 29 +++++++++++++++-- .../main/popups/DynamicValues.svelte | 5 ++- src/frontend/components/slide/Textbox.svelte | 10 ++++-- src/frontend/components/stage/Stagebox.svelte | 12 +++++-- .../components/stage/tools/Items.svelte | 2 +- src/server/stage/components/Stagebox.svelte | 31 +++++++++++++++++-- src/server/stage/components/Textbox.svelte | 27 +++++++++++++++- 8 files changed, 107 insertions(+), 15 deletions(-) diff --git a/src/frontend/components/edit/editbox/Editbox.svelte b/src/frontend/components/edit/editbox/Editbox.svelte index 0e17907f1..e4b1a52bb 100644 --- a/src/frontend/components/edit/editbox/Editbox.svelte +++ b/src/frontend/components/edit/editbox/Editbox.svelte @@ -7,7 +7,7 @@ import { history } from "../../helpers/history" import { getExtension, getFileName, getMediaType } from "../../helpers/media" import { getFirstActiveOutput, getOutputResolution, percentageStylePos } from "../../helpers/output" - import { getNumberVariables } from "../../helpers/showActions" + import { createCSSVariables } from "../../helpers/showActions" import MaterialButton from "../../inputs/MaterialButton.svelte" import SlideItems from "../../slide/SlideItems.svelte" import EditboxCropping from "./EditboxCropping.svelte" @@ -179,8 +179,8 @@ $: currentSlide = (ref.type || "show") === "show" ? $showsCache[active || ""]?.slides?.[ref.id] : null // WIP get group slide $: isLocked = (ref.type || "show") !== "show" ? false : $showsCache[active || ""]?.locked || currentSlide?.locked || profile.global === "read" || profile[$showsCache[active || ""]?.category || ""] === "read" - // give CSS access to number variable values - $: cssVariables = getNumberVariables($variables) + // give CSS access to certain dynamic values + $: cssVariables = createCSSVariables($variables) const isOptimized = $special.optimizedMode let previewCropType: "clip" | "ppt" = "clip" diff --git a/src/frontend/components/helpers/showActions.ts b/src/frontend/components/helpers/showActions.ts index acc672dfc..034aca23b 100644 --- a/src/frontend/components/helpers/showActions.ts +++ b/src/frontend/components/helpers/showActions.ts @@ -1051,11 +1051,21 @@ const dynamicValues = { const group = show?.slides?.[ref[parentIndex]?.id]?.group || "" return getGroupName({ show, showId: outSlide?.id }, ref[parentIndex]?.id, group, parentIndex, false, false) }, + slide_group_color: ({ show, ref, slideIndex }) => { + const parentIndex = ref[slideIndex]?.parent?.layoutIndex ?? slideIndex + const groupColor = show?.slides?.[ref[parentIndex]?.id]?.color || "" + return groupColor + }, slide_group_next: ({ show, ref, slideIndex, outSlide }) => { const parentIndex = ref[slideIndex + 1]?.parent?.layoutIndex ?? slideIndex + 1 const group = show?.slides?.[ref[parentIndex]?.id]?.group || "" return getGroupName({ show, showId: outSlide?.id }, ref[parentIndex]?.id, group, parentIndex, false, false) }, + slide_group_next_color: ({ show, ref, slideIndex }) => { + const parentIndex = ref[slideIndex + 1]?.parent?.layoutIndex ?? slideIndex + 1 + const groupColor = show?.slides?.[ref[parentIndex]?.id]?.color || "" + return groupColor + }, slide_group_upcoming: ({ show, ref, slideIndex, outSlide }) => { if (slideIndex < 0) return "" let nextParentIndex = slideIndex + 1 @@ -1063,6 +1073,13 @@ const dynamicValues = { const group = show?.slides?.[ref[nextParentIndex]?.id]?.group || "" return getGroupName({ show, showId: outSlide?.id }, ref[nextParentIndex]?.id, group, nextParentIndex, false, false) }, + slide_group_upcoming_color: ({ show, ref, slideIndex }) => { + if (slideIndex < 0) return "" + let nextParentIndex = slideIndex + 1 + while (ref[nextParentIndex]?.type !== "parent" && nextParentIndex < ref.length) nextParentIndex++ + const groupColor = show?.slides?.[ref[nextParentIndex]?.id]?.color || "" + return groupColor + }, slide_notes: ({ show, ref, slideIndex }) => show?.slides?.[ref[slideIndex]?.id]?.notes || "", slide_notes_next: ({ show, ref, slideIndex }) => show?.slides?.[ref[slideIndex + 1]?.id]?.notes || "", @@ -1155,9 +1172,17 @@ export function getVariableNameId(name: string) { return name.toLowerCase().trim().replaceAll(" ", "_") } -export function getNumberVariables(variableUpdater = get(variables), _dynamicUpdaters: any = null) { +export function createCSSVariables(variableUpdater = get(variables), _dynamicUpdaters: any = null, type: "default" | "stage" = "default", _updateTrigger: any = null) { + // add all number variables const numberVariables = Object.values(variableUpdater || {}).filter((a) => a && (a.type === "number" || a.type === "random_number" || (a.type === "text" && a.text?.includes("{")))) - return numberVariables.reduce((css, v) => (css += `--variable-${getVariableNameId(v.name)}: ${v.type === "text" ? getDynamicValue(v.text || "") : (v.number ?? (v.default || 0))};`), "") + let css = numberVariables.reduce((css, v) => (css += `--variable-${getVariableNameId(v.name)}: ${v.type === "text" ? getDynamicValue(v.text || "", type) : (v.number ?? (v.default || 0))};`), "") + + // add color dynamic values + css += `--slide-group-color: ${getDynamicValue("slide_group_color", type)};` + css += `--slide-group-next-color: ${getDynamicValue("slide_group_next_color", type)};` + css += `--slide-group-upcoming-color: ${getDynamicValue("slide_group_upcoming_color", type)};` + + return css } // PROJECT SECTION DATA diff --git a/src/frontend/components/main/popups/DynamicValues.svelte b/src/frontend/components/main/popups/DynamicValues.svelte index f829a3c0b..487d04911 100644 --- a/src/frontend/components/main/popups/DynamicValues.svelte +++ b/src/frontend/components/main/popups/DynamicValues.svelte @@ -35,8 +35,11 @@ function getValues() { let list = getDynamicIds(false, mode, showAll).map((id) => ({ id })) + // WIP organize better - and don't hide any values + // _color is only for CSS "var(--slide-group-color)", but we should show them in a seperate way + const isStage = $activePage === "stage" - const hidden = ["slide_text_current"] + const hidden = ["slide_text_current", "slide_group_color", "slide_group_next_color", "slide_group_upcoming_color"] const nonStageHidden = ["show_text_full"] const stageHidden = ["slide_text_previous", "slide_text_next"] if (isStage) list = list.filter((a) => !hidden.includes(a.id) && !stageHidden.includes(a.id)) diff --git a/src/frontend/components/slide/Textbox.svelte b/src/frontend/components/slide/Textbox.svelte index 202f8bf63..6796bf88b 100644 --- a/src/frontend/components/slide/Textbox.svelte +++ b/src/frontend/components/slide/Textbox.svelte @@ -10,7 +10,7 @@ import { getItemText } from "../edit/scripts/textStyle" import { clone } from "../helpers/array" import { getActiveOutputs, getAllActiveOutputs, getFirstActiveOutput, getOutputLines, getOutputResolution, percentageStylePos } from "../helpers/output" - import { getNumberVariables } from "../helpers/showActions" + import { createCSSVariables } from "../helpers/showActions" import { getStyles } from "../helpers/style" import SlideItems from "./SlideItems.svelte" import TextboxLines from "./TextboxLines.svelte" @@ -120,6 +120,7 @@ if (dateInterval) clearInterval(dateInterval) if (loopStop) clearTimeout(loopStop) if (paddingCorrTimeout) clearTimeout(paddingCorrTimeout) + if (cssInterval) clearInterval(cssInterval) }) // $: if (item.type === "timer") ref.id = item.timer!.id! @@ -747,8 +748,11 @@ send(OUTPUT, ["ACTION_MAIN"], { id: item.button.release }) } - // give CSS access to number variable values - $: cssVariables = getNumberVariables($variables, $outputs) + let updateTrigger = 0 + let cssInterval = setInterval(() => updateTrigger++, 1000) + + // give CSS access to certain dynamic values + $: cssVariables = createCSSVariables($variables, $outputs, isStage ? "stage" : "default", updateTrigger) // initialize default filter values to get the transition working (should use animation) // https://stackoverflow.com/questions/68632554/css-backdrop-filter-does-not-work-with-transition diff --git a/src/frontend/components/stage/Stagebox.svelte b/src/frontend/components/stage/Stagebox.svelte index bed568c5c..2f2b0a20b 100644 --- a/src/frontend/components/stage/Stagebox.svelte +++ b/src/frontend/components/stage/Stagebox.svelte @@ -11,6 +11,7 @@ import { clone, keysToID, sortByName } from "../helpers/array" import Icon from "../helpers/Icon.svelte" import { getActiveOutputs, getStageResolution, percentageStylePos } from "../helpers/output" + import { createCSSVariables } from "../helpers/showActions" import { getStyles } from "../helpers/style" import Button from "../inputs/Button.svelte" import Media from "../output/layers/Media.svelte" @@ -264,14 +265,21 @@ $: contextId = item?.type === "text" ? "stage_text_item" : item?.type === "current_output" ? "stage_item_output" : "stage_item" let conditionsUpdater = 0 + let updateTrigger = 0 const updaterInterval = setInterval(() => conditionsUpdater++, 3000) - onDestroy(() => clearInterval(updaterInterval)) + const cssInterval = setInterval(() => updateTrigger++, 1000) + onDestroy(() => { + clearInterval(updaterInterval) + clearInterval(cssInterval) + }) $: currentItemText = item ? (item.type === "slide_text" ? getSlideTextItems(stageLayout!, item).map(getItemText).join("") : getItemText(stageItemToItem(item))) : "" $: showItemState = edit ? isConditionMet(item?.conditions?.showItem, currentItemText, "stage", conditionsUpdater) : false // fixed letter width $: fixedWidth = item?.type === "timer" || item?.type === "clock" ? "font-feature-settings: 'tnum' 1;" : "" + + $: cssVariables = createCSSVariables($variables, $outputs, "stage", updateTrigger) </script> <svelte:window on:keydown={keydown} on:mousedown={deselect} /> @@ -284,7 +292,7 @@ class:selected={edit && $activeStage.items.includes(id)} class:isDisabledVariable class:isOutput={!!$currentWindow} - style="{getCustomStyle(itemStyle)}{id.includes('slide') && !id.includes('tracker') ? '' : textStyle}{edit ? `outline: ${3 / ratio}px solid rgb(255 255 255 / 0.2);` : ''}--labelColor: {currentShow?.settings?.labelColor || '#d0a853'};{fixedWidth}" + style="{getCustomStyle(itemStyle)}{id.includes('slide') && !id.includes('tracker') ? '' : textStyle}{edit ? `outline: ${3 / ratio}px solid rgb(255 255 255 / 0.2);` : ''}--labelColor: {currentShow?.settings?.labelColor || '#d0a853'};{fixedWidth}{cssVariables}" on:mousedown={mousedown} > {#if currentShow?.settings?.labels && id && item} diff --git a/src/frontend/components/stage/tools/Items.svelte b/src/frontend/components/stage/tools/Items.svelte index 0701d10c1..ee02f91e3 100644 --- a/src/frontend/components/stage/tools/Items.svelte +++ b/src/frontend/components/stage/tools/Items.svelte @@ -132,7 +132,7 @@ $: allItems = getSortedStageItems(stageId, $stageShows) $: invertedItemList = Array.isArray(allItems) ? clone(allItems).reverse() : [] - const excludeValues = ["project_", "time_", "exif_", "audio_", "meta_", "slide_text_", "show_text_full"] + const excludeValues = ["project_", "time_", "exif_", "audio_", "meta_", "slide_text_", "show_text_full", "slide_group_color", "slide_group_next_color", "slide_group_upcoming_color"] const ref = { type: "stage" } const dynamicValues = getDynamicIds() .filter((id) => !excludeValues.find((v) => id.includes(v))) // || id.startsWith("project_") diff --git a/src/server/stage/components/Stagebox.svelte b/src/server/stage/components/Stagebox.svelte index bf15d49b6..cc62fb311 100644 --- a/src/server/stage/components/Stagebox.svelte +++ b/src/server/stage/components/Stagebox.svelte @@ -11,6 +11,7 @@ import SlideProgress from "../items/SlideProgress.svelte" import SlideText from "../items/SlideText.svelte" import VideoTime from "../items/VideoTime.svelte" + import { _getDynamicValue } from "../util/itemHelpers" import { activeTimers, background, media, output, outputSlideCache, progressData, stream, timers, variables } from "../util/stores" import MediaOutput from "./MediaOutput.svelte" import PreviewCanvas from "./PreviewCanvas.svelte" @@ -30,7 +31,11 @@ // timer let today = new Date() const dateInterval = setInterval(() => (today = new Date()), 1000) - onDestroy(() => clearInterval(dateInterval)) + + onDestroy(() => { + clearInterval(dateInterval) + clearInterval(cssInterval) + }) let itemStyles: any = getStyles(item.style, true) $: fontSize = Number(itemStyles?.["font-size"] || 0) || 100 // item.autoFontSize || @@ -107,11 +112,33 @@ // fixed letter width $: fixedWidth = item?.type === "timer" || item?.type === "clock" ? "font-feature-settings: 'tnum' 1;" : "" + + function getVariableNameId(name: string) { + if (typeof name !== "string") return "" + return name.toLowerCase().trim().replaceAll(" ", "_") + } + + function createCSSVariables(variableUpdater: any, _updateTrigger: any = null) { + if (!variableUpdater) return "" + const numberVariables = Object.values(variableUpdater).filter((a: any) => a && (a.type === "number" || a.type === "random_number" || (a.type === "text" && a.text?.includes("{")))) + let css = numberVariables.reduce((css: string, v: any) => (css += `--variable-${getVariableNameId(v.name)}: ${v.type === "text" ? _getDynamicValue(v.text || "") : (v.number ?? (v.default || 0))};`), "") + + css += `--slide-group-color: ${_getDynamicValue("slide_group_color")};` + css += `--slide-group-next-color: ${_getDynamicValue("slide_group_next_color")};` + css += `--slide-group-upcoming-color: ${_getDynamicValue("slide_group_upcoming_color")};` + + return css + } + + let updateTrigger = 0 + const cssInterval = setInterval(() => updateTrigger++, 1000) + + $: cssVariables = createCSSVariables($variables, updateTrigger) </script> <!-- style + (id.includes("current_output") ? "" : newSizes) --> <!-- {show.settings.autoStretch === false ? '' : newSizes} --> -<div class="item" class:border={stageLayout?.settings.labels} class:isDisabledVariable style="{itemStyle}{id.includes('slide') && !id.includes('tracker') ? '' : textStyle}{newSizes}--labelColor: {stageLayout?.settings?.labelColor || '#d0a853'};{fixedWidth}"> +<div class="item" class:border={stageLayout?.settings.labels} class:isDisabledVariable style="{itemStyle}{id.includes('slide') && !id.includes('tracker') ? '' : textStyle}{newSizes}--labelColor: {stageLayout?.settings?.labelColor || '#d0a853'};{fixedWidth}{cssVariables}"> {#if stageLayout?.settings.labels} <div class="label">{item.label || ""}</div> {/if} diff --git a/src/server/stage/components/Textbox.svelte b/src/server/stage/components/Textbox.svelte index d2257efca..255f9fe0b 100644 --- a/src/server/stage/components/Textbox.svelte +++ b/src/server/stage/components/Textbox.svelte @@ -11,6 +11,7 @@ import { send } from "../util/socket" import { dictionary, updateTransposed, variables } from "../util/stores" import ListView from "./ListView.svelte" + import { _getDynamicValue } from "../util/itemHelpers" import { getItemText } from "../helpers/textStyle" export let showId: string @@ -259,10 +260,12 @@ if (!hasDynamicValues) return updateDynamic++ } + onDestroy(() => { stopInterval() if (eventTimeout) clearTimeout(eventTimeout) if (blockTimeout) clearTimeout(blockTimeout) + clearInterval(cssInterval) }) $: chordFontSize = chordLines.length ? (stageItem?.chords?.size || stageItem?.chordsData?.size || 60) * 0.65 : 0 @@ -416,6 +419,28 @@ release() } } + + function getVariableNameId(name: string) { + if (typeof name !== "string") return "" + return name.toLowerCase().trim().replaceAll(" ", "_") + } + + function createCSSVariables(variableUpdater: any, _updateTrigger: any = null) { + if (!variableUpdater) return "" + const numberVariables = Object.values(variableUpdater).filter((a: any) => a && (a.type === "number" || a.type === "random_number" || (a.type === "text" && a.text?.includes("{")))) + let css = numberVariables.reduce((css: string, v: any) => (css += `--variable-${getVariableNameId(v.name)}: ${v.type === "text" ? _getDynamicValue(v.text || "") : (v.number ?? (v.default || 0))};`), "") + + css += `--slide-group-color: ${_getDynamicValue("slide_group_color")};` + css += `--slide-group-next-color: ${_getDynamicValue("slide_group_next_color")};` + css += `--slide-group-upcoming-color: ${_getDynamicValue("slide_group_upcoming_color")};` + + return css + } + + let updateTrigger = 0 + const cssInterval = setInterval(() => updateTrigger++, 1000) + + $: cssVariables = createCSSVariables($variables, updateTrigger) </script> <svelte:window on:click={closeActions} /> @@ -425,7 +450,7 @@ bind:this={thisElem} class="item" class:clicked={item.button?.press || item.button?.release} - style={style ? getCustomStyle(itemStyle) : null} + style={style ? (getCustomStyle(itemStyle) || "") + cssVariables : null} class:chords={chordLines.length} class:clickable={item.button?.press || item.button?.release} class:reveal={item.clickReveal && !clickRevealed} From 598e759115b48de0029808a875141aea2cee7095 Mon Sep 17 00:00:00 2001 From: Kristoffer <kristoffervassbo@gmail.com> Date: Wed, 17 Jun 2026 11:46:03 +0200 Subject: [PATCH 14/32] Action custom activation on Foreground/background specific videos --- src/frontend/components/actions/customActivation.ts | 4 +++- src/frontend/components/drawer/pages/Actions.svelte | 2 +- src/frontend/components/helpers/output.ts | 9 +++++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/frontend/components/actions/customActivation.ts b/src/frontend/components/actions/customActivation.ts index 8d92980f4..42f4d398b 100644 --- a/src/frontend/components/actions/customActivation.ts +++ b/src/frontend/components/actions/customActivation.ts @@ -23,7 +23,9 @@ export const customActionActivations = [ { id: "scripture_start", common: true, name: "actions.activate_scripture_start", icon: "scripture" }, - { id: "video_start", common: true, name: "actions.activate_video_starting", icon: "video" }, + { id: "video_start", common: true, name: "actions.activate_video_starting: actions.any", icon: "video" }, + { id: "video_start_foreground", name: "actions.activate_video_starting: preview.foreground", icon: "video" }, + { id: "video_start_background", name: "actions.activate_video_starting: preview.background", icon: "video" }, { id: "video_end", common: true, name: "actions.activate_video_ending", icon: "video" }, { id: "audio_start", common: true, name: "actions.activate_audio_starting", icon: "music" }, diff --git a/src/frontend/components/drawer/pages/Actions.svelte b/src/frontend/components/drawer/pages/Actions.svelte index 0787b2cb6..11a536cf2 100644 --- a/src/frontend/components/drawer/pages/Actions.svelte +++ b/src/frontend/components/drawer/pages/Actions.svelte @@ -132,7 +132,7 @@ {#if action.customActivation || action.startupEnabled} <span> {#key action.customActivation} - <T id={customActionActivations.find((a) => a.id === action.customActivation)?.name || "actions.custom_activation"} /> + {translateText(customActionActivations.find((a) => a.id === action.customActivation)?.name || "actions.custom_activation")} {/key} </span> {/if} diff --git a/src/frontend/components/helpers/output.ts b/src/frontend/components/helpers/output.ts index b5c5c4775..efcd69253 100644 --- a/src/frontend/components/helpers/output.ts +++ b/src/frontend/components/helpers/output.ts @@ -304,7 +304,9 @@ function changeOutputBackground(data, { output, id, mute, videoOutputId }) { if (!data.muted && muteAudio && isVideo) fadeoutAllPlayingAudio() else fadeinAllPlayingAudio() - if (isVideo) videoStarting() + const type = data.muted && data.loop ? "background" : !data.muted && !data.loop ? "foreground" : null + + if (isVideo) videoStarting(type) else if (previousWasVideo) videoEnding() } @@ -323,8 +325,11 @@ function videoEnding() { customActionActivation("video_end") }) } -function videoStarting() { +function videoStarting(type: "foreground" | "background" | null) { customActionActivation("video_start") + + if (type === "foreground") customActionActivation("video_start_foreground") + else if (type === "background") customActionActivation("video_start_background") } export function startCamera(cam: API_camera) { From 41ce6407653f6890ecdf8afbc7fcba10fbafc298 Mon Sep 17 00:00:00 2001 From: Kristoffer <kristoffervassbo@gmail.com> Date: Wed, 17 Jun 2026 11:59:07 +0200 Subject: [PATCH 15/32] Tweaks --- public/lang/en.json | 1 + src/frontend/IPC/responsesMain.ts | 17 +++++++++++++---- src/frontend/components/settings/Screens.svelte | 4 ++++ .../components/settings/tabs/Outputs.svelte | 3 ++- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/public/lang/en.json b/public/lang/en.json index 4dc298334..937108a60 100644 --- a/public/lang/en.json +++ b/public/lang/en.json @@ -1368,6 +1368,7 @@ "output_resolution_ratio": "Use output resolution aspect ratio", "font_size_ratio": "Font size ratio", "cropping": "Cropping", + "cropped": "Cropped", "frame_rate": "Frame rate", "device": "Device", "display_mode": "Display mode", diff --git a/src/frontend/IPC/responsesMain.ts b/src/frontend/IPC/responsesMain.ts index 43b94977f..d1bdd9f83 100644 --- a/src/frontend/IPC/responsesMain.ts +++ b/src/frontend/IPC/responsesMain.ts @@ -12,7 +12,7 @@ import { _getVariableValue, getDynamicValue } from "../components/edit/scripts/i import { clone, keysToID } from "../components/helpers/array" import { addDrawerFolder } from "../components/helpers/dropActions" import { history } from "../components/helpers/history" -import { captureCanvas, setMediaTracks } from "../components/helpers/media" +import { captureCanvas, getExtension, getFileName, removeExtension, setMediaTracks } from "../components/helpers/media" import { getActiveOutputs } from "../components/helpers/output" import { loadShows, saveTextCache } from "../components/helpers/setShow" import { checkName, getGlobalGroup, getLabelId } from "../components/helpers/show" @@ -77,6 +77,7 @@ import { theme, themes, timers, + recentFiles, undoHistory, usageLog, variables, @@ -489,9 +490,17 @@ export const mainResponses: MainResponses = { const receiveFilePathIMPORT = { // Media pdf: () => { - // convert to images directly - drag and drop to keep as PDF - ;(mainData as string[]).forEach((path) => sendMain(Main.PDF_TO_IMAGE, { filePath: path })) - // addToProject("pdf", mainData as string[]) + const paths = mainData as string[] + paths.forEach((path) => sendMain(Main.PDF_TO_IMAGE, { filePath: path })) + + // remove any PDFs with the same name from the Recommended project items list + const importedNames = paths.map((p) => removeExtension(getFileName(p)).toLowerCase()) + recentFiles.update((a) => { + const toClear = a.all.filter((p) => getExtension(p) === "pdf" && importedNames.includes(removeExtension(getFileName(p)).toLowerCase())) + if (toClear.length) a.cleared = [...a.cleared, ...toClear] + return a + }) + updateRecentlyAddedFiles() }, powerkey: () => addToProject("ppt", mainData as string[]) } diff --git a/src/frontend/components/settings/Screens.svelte b/src/frontend/components/settings/Screens.svelte index db2d1a95a..a1a87e5be 100644 --- a/src/frontend/components/settings/Screens.svelte +++ b/src/frontend/components/settings/Screens.svelte @@ -200,7 +200,11 @@ return `-webkit-mask-image: linear-gradient(${blending.rotate ?? 90}deg, rgba(0, 0, 0, ${opacity}) 0%, rgb(0, 0, 0) ${blending.left}%, rgb(0, 0, 0) ${100 - blending.right}%, rgba(0, 0, 0, ${opacity}) 100%);` } + $: isCropped = (cropping.left || 0) + (cropping.right || 0) + (cropping.top || 0) + (cropping.bottom || 0) > 0 + $: isEdgeBlending = blending.left || blending.right + let showMore = false + $: showMore = isCropped || isEdgeBlending </script> {#if editCropping} diff --git a/src/frontend/components/settings/tabs/Outputs.svelte b/src/frontend/components/settings/tabs/Outputs.svelte index 652b9a5a0..4455302c1 100644 --- a/src/frontend/components/settings/tabs/Outputs.svelte +++ b/src/frontend/components/settings/tabs/Outputs.svelte @@ -328,7 +328,8 @@ return device.data?.supportsInternalKeying || device.data?.supportsExternalKeying || false } - $: outputLabel = currentOutput?.blackmagicData?.displayMode || `${currentOutput?.bounds?.width || 1920}x${currentOutput?.bounds?.height || 1080}` + $: isCropped = currentOutput?.cropping && (currentOutput.cropping.left || 0) + (currentOutput.cropping.right || 0) + (currentOutput.cropping.top || 0) + (currentOutput.cropping.bottom || 0) > 0 + $: outputLabel = (currentOutput?.blackmagicData?.displayMode || `${currentOutput?.bounds?.width || 1920}x${currentOutput?.bounds?.height || 1080}`) + (isCropped ? ` - settings.cropped` : "") let ndiMenuOpened = false let bmdMenuOpened = false From b46e09e39ae01e1e03834f354eba2b0861148b39 Mon Sep 17 00:00:00 2001 From: Kristoffer <kristoffervassbo@gmail.com> Date: Wed, 17 Jun 2026 12:08:52 +0200 Subject: [PATCH 16/32] Fixed project media export not working with auto located media files --- src/frontend/components/export/project.ts | 24 ++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/frontend/components/export/project.ts b/src/frontend/components/export/project.ts index f9723d37e..8fcfec58f 100644 --- a/src/frontend/components/export/project.ts +++ b/src/frontend/components/export/project.ts @@ -12,6 +12,7 @@ import { clone } from "../helpers/array" import { loadShows } from "../helpers/setShow" import { formatToFileName } from "../helpers/show" import { _show } from "../helpers/shows" +import { getMedia } from "../helpers/media" export async function exportProject(project: Project, projectId: string, savePath?: string) { if (!project) return @@ -111,19 +112,23 @@ export async function exportProject(project: Project, projectId: string, savePat if (Object.keys(actions).length) projectData.actions = actions const includeMediaFiles = get(special).projectIncludeMedia ?? true if (includeMediaFiles) { - projectData.files = files - + const resolvedFiles: string[] = [] const mediaData: any = {} - files.forEach((path) => { - if (!get(media)[path]) return - const data = clone(get(media)[path]) + for (const path of files) { + const mediaObj = await getMedia(path) + const resolvedPath = mediaObj?.path || path + resolvedFiles.push(resolvedPath) - // delete data.info - delete data.creationTime // no need to transport this + const data = mediaObj?.data || clone(get(media)[path]) + if (data) { + // delete data.info + delete data.creationTime // no need to transport this + mediaData[resolvedPath] = data + } + } - mediaData[path] = data - }) + projectData.files = resolvedFiles if (Object.keys(mediaData).length) projectData.media = mediaData } @@ -175,3 +180,4 @@ export async function exportProject(project: Project, projectId: string, savePat // let base64 = await toDataURL(showRef.id) // media[showRef.id] = base64 } + From 3c9c84795012e66cbfe4cc8ca7cf1de994cc9028 Mon Sep 17 00:00:00 2001 From: Kristoffer <kristoffervassbo@gmail.com> Date: Wed, 17 Jun 2026 12:23:22 +0200 Subject: [PATCH 17/32] Tweak --- src/frontend/components/export/projectLink.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/frontend/components/export/projectLink.ts b/src/frontend/components/export/projectLink.ts index d189dacea..dc812e9ae 100644 --- a/src/frontend/components/export/projectLink.ts +++ b/src/frontend/components/export/projectLink.ts @@ -1,6 +1,6 @@ import { get } from "svelte/store" import type { Project } from "../../../types/Projects" -import { overlays, showsCache } from "../../stores" +import { overlays, playerVideos, showsCache } from "../../stores" import { newToast } from "../../utils/common" import { loadShows } from "../helpers/setShow" import { _show } from "../helpers/shows" @@ -70,6 +70,15 @@ export async function exportProjectAsData(project: Project, projectId: string): } as LinkOverlayItem } + if (type === "player") { + const data = get(playerVideos)[item.id] + + return { + name: data?.name || item.name, + type + } as LinkGenericItem + } + return { // id: item.id, name: item.name, From 0fc558738c3674def42ec2a05383805f9d95995a Mon Sep 17 00:00:00 2001 From: Kristoffer <kristoffervassbo@gmail.com> Date: Wed, 17 Jun 2026 13:12:57 +0200 Subject: [PATCH 18/32] Fixed slides sometimes getting out of sync when changing rapidly --- src/frontend/components/helpers/OutputHelper.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/frontend/components/helpers/OutputHelper.ts b/src/frontend/components/helpers/OutputHelper.ts index 3ef071316..949d82480 100644 --- a/src/frontend/components/helpers/OutputHelper.ts +++ b/src/frontend/components/helpers/OutputHelper.ts @@ -243,6 +243,7 @@ export class OutputHelper { // store previous in case it has been cleared after being played (e.g. when a playing video has finished) private static previousOutputted: OutData | null = null + private static pendingSlides: Record<string, OutSlide> = {} private static isOutputted(outputId: string, item: ShowRef, next: boolean, options: Options) { const out = this.getOut(outputId) @@ -447,7 +448,11 @@ export class OutputHelper { private static getOut(outputId: string) { const currentOutput = get(outputs)[outputId] || {} - let out = currentOutput.out || {} + let out = currentOutput.out ? { ...currentOutput.out } : {} + + if (this.pendingSlides[outputId]) { + out.slide = this.pendingSlides[outputId] + } // restore cleared slide position (only if active show is the same as outputted) const clearedSlide = get(outputSlideCache)[outputId] @@ -468,8 +473,15 @@ export class OutputHelper { const layoutData = layout[data.index ?? -1]?.data checkActionTrigger(layoutData, data.index) + + this.pendingSlides[outputId] = data + // allow custom actions to trigger first setTimeout(() => { + if (this.pendingSlides[outputId] === data) { + delete this.pendingSlides[outputId] + } + setOutput("slide", data, false, outputId) updateOut(data.id, data.index!, layout, slideLayers, outputId) }) From d8c01e05d795da71994982d997f3c975f5bf9e0b Mon Sep 17 00:00:00 2001 From: Kristoffer <kristoffervassbo@gmail.com> Date: Wed, 17 Jun 2026 14:38:25 +0200 Subject: [PATCH 19/32] Slide loading tweaks --- src/frontend/components/show/Slides.svelte | 4 ++- src/frontend/components/slide/Textbox.svelte | 26 ++++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/frontend/components/show/Slides.svelte b/src/frontend/components/show/Slides.svelte index 29e8db1ba..5c8b3898d 100644 --- a/src/frontend/components/show/Slides.svelte +++ b/src/frontend/components/show/Slides.svelte @@ -423,7 +423,9 @@ timeout = setTimeout(next, 10) function next() { - lazyLoader += $focusMode ? 20 : 4 + // start small (e.g. 4) and double each step up to a max batch size of 32 (or 128 in focusMode) + const currentBatch = lazyLoader === 0 ? 4 : Math.min($focusMode ? 128 : 32, lazyLoader * 2) + lazyLoader += currentBatch clearTimeout(timeout as NodeJS.Timeout) timeout = null startLazyLoader() diff --git a/src/frontend/components/slide/Textbox.svelte b/src/frontend/components/slide/Textbox.svelte index 6796bf88b..07424c370 100644 --- a/src/frontend/components/slide/Textbox.svelte +++ b/src/frontend/components/slide/Textbox.svelte @@ -649,21 +649,26 @@ if (isStage || itemIndex < 0 || $currentWindow || ref.showId === "temp") return if (ref.type === "overlay") { + const currentOverlays = $overlays + if (!currentOverlays[ref.id]?.items?.[itemIndex] || currentOverlays[ref.id].items[itemIndex].autoFontSize === fontSize) return + overlays.update((a) => { - if (!a[ref.id]?.items?.[itemIndex]) return a a[ref.id].items[itemIndex].autoFontSize = fontSize return a }) } else if (ref.type === "template") { + const currentTemplates = $templates + if (!currentTemplates[ref.id]?.items?.[itemIndex] || currentTemplates[ref.id].items[itemIndex].autoFontSize === fontSize) return + templates.update((a) => { - if (!a[ref.id]?.items?.[itemIndex]) return a a[ref.id].items[itemIndex].autoFontSize = fontSize return a }) } else if (ref.showId) { - showsCache.update((a) => { - if (!a[ref.showId!]?.slides?.[ref.id]?.items?.[itemIndex]) return a + const currentShows = $showsCache + if (!currentShows[ref.showId]?.slides?.[ref.id]?.items?.[itemIndex] || currentShows[ref.showId].slides[ref.id].items[itemIndex].autoFontSize === fontSize) return + showsCache.update((a) => { a[ref.showId!].slides[ref.id].items[itemIndex].autoFontSize = fontSize return a }) @@ -674,21 +679,26 @@ if (isStage || itemIndex < 0 || $currentWindow || ref.showId === "temp") return if (ref.type === "overlay") { + const currentOverlays = $overlays + if (!currentOverlays[ref.id]?.items?.[itemIndex] || currentOverlays[ref.id].items[itemIndex].previewAutoFontSize === fontSize) return + overlays.update((a) => { - if (!a[ref.id]?.items?.[itemIndex]) return a a[ref.id].items[itemIndex].previewAutoFontSize = fontSize return a }) } else if (ref.type === "template") { + const currentTemplates = $templates + if (!currentTemplates[ref.id]?.items?.[itemIndex] || currentTemplates[ref.id].items[itemIndex].previewAutoFontSize === fontSize) return + templates.update((a) => { - if (!a[ref.id]?.items?.[itemIndex]) return a a[ref.id].items[itemIndex].previewAutoFontSize = fontSize return a }) } else if (ref.showId) { - showsCache.update((a) => { - if (!a[ref.showId!]?.slides?.[ref.id]?.items?.[itemIndex]) return a + const currentShows = $showsCache + if (!currentShows[ref.showId]?.slides?.[ref.id]?.items?.[itemIndex] || currentShows[ref.showId].slides[ref.id].items[itemIndex].previewAutoFontSize === fontSize) return + showsCache.update((a) => { a[ref.showId!].slides[ref.id].items[itemIndex].previewAutoFontSize = fontSize return a }) From 15bcbf4ffc862292ed4daa204a18f23caf554b7e Mon Sep 17 00:00:00 2001 From: Kristoffer <kristoffervassbo@gmail.com> Date: Wed, 17 Jun 2026 15:25:02 +0200 Subject: [PATCH 20/32] Changed protected cleanup to last accessed --- src/electron/data/protected.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/electron/data/protected.ts b/src/electron/data/protected.ts index 8aae69047..e6068fd3b 100644 --- a/src/electron/data/protected.ts +++ b/src/electron/data/protected.ts @@ -36,7 +36,7 @@ export async function cleanupProtectedCache() { const filePath = path.join(dir, name) try { const stats = await fs.promises.stat(filePath) - if (now - stats.mtimeMs > PROTECTED_CACHE_MAX_AGE_MS) { + if (now - (stats.atimeMs || stats.mtimeMs) > PROTECTED_CACHE_MAX_AGE_MS) { await fs.promises.unlink(filePath) } } catch { From e62a4f429d49dd2e695db4012396081eaeaeedee Mon Sep 17 00:00:00 2001 From: Kristoffer <kristoffervassbo@gmail.com> Date: Wed, 17 Jun 2026 16:02:19 +0200 Subject: [PATCH 21/32] Fixed minimize timer --- .../components/output/preview/SpotifyController.svelte | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/frontend/components/output/preview/SpotifyController.svelte b/src/frontend/components/output/preview/SpotifyController.svelte index 23e694d1f..04551d064 100644 --- a/src/frontend/components/output/preview/SpotifyController.svelte +++ b/src/frontend/components/output/preview/SpotifyController.svelte @@ -48,18 +48,22 @@ if (nextPlaying || nextFading) { clearTimeout(pauseTimer) + pauseTimer = null isMinimized = false } else if ($spotifyState) { - clearTimeout(pauseTimer) if (isFirstRun || isFading) { + clearTimeout(pauseTimer) + pauseTimer = null isMinimized = true - } else if (isPlaying) { + } else if (isPlaying && !pauseTimer) { pauseTimer = setTimeout(() => { isMinimized = true + pauseTimer = null }, 1500) } } else if (!$spotifyState) { clearTimeout(pauseTimer) + pauseTimer = null isMinimized = false } From 2e6bbbd6def4c0e9a1df33c0b8b92ebeca9ca71f Mon Sep 17 00:00:00 2001 From: John Cameron Furey <174623577+jcfurey@users.noreply.github.com> Date: Wed, 17 Jun 2026 09:14:42 -0500 Subject: [PATCH 22/32] Security & dependency hardening: clear npm advisories + commit lockfile (#3384) --- .gitattributes | 2 + .github/workflows/ci.yml | 40 + .github/workflows/playwright.yml | 2 +- config/testing/start.test.ts | 71 +- package-lock.json | 1531 ++-------------------- package.json | 13 +- src/electron/IPC/main.ts | 27 +- src/electron/data/thumbnails.ts | 4 +- src/electron/data/zip.ts | 14 +- src/electron/servers.ts | 105 +- src/electron/utils/files.ts | 6 +- src/frontend/components/helpers/media.ts | 5 +- src/frontend/converters/csv.ts | 3 +- src/frontend/utils/save.ts | 4 +- src/frontend/utils/sendData.ts | 11 +- src/types/IPC/Main.ts | 2 +- 16 files changed, 357 insertions(+), 1483 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/workflows/ci.yml diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..6eaa0f52d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Collapse the generated lockfile in diffs. +package-lock.json linguist-generated=true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..b16818027 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI + +# Manual activation for now (run from the Actions tab); can be wired to pull_request later. +on: + workflow_dispatch: + +jobs: + checks: + name: Format & type-check + runs-on: ubuntu-latest + steps: + - name: Check out Git repository + uses: actions/checkout@v4 + + - name: Install Node.js and NPM + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install libraries + run: | + sudo apt-get update + sudo apt-get install -y libfontconfig1-dev uuid-dev libltc-dev --fix-missing + + - name: Install dependencies + run: npm ci + + # Formatting and svelte type-check currently have a pre-existing backlog, so they are + # reported but non-blocking for now. Flip continue-on-error off once the backlog is cleared. + - name: Check formatting + run: npm run test:format + continue-on-error: true + + - name: Type-check (svelte) + run: npm run test:svelte + continue-on-error: true + + # Blocking gate: a broken build fails this run. + - name: Build + run: npm run build diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 906df06f9..033517992 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 - name: Install libraries run: sudo apt-get install libfontconfig1-dev - name: Install dependencies diff --git a/config/testing/start.test.ts b/config/testing/start.test.ts index ad2206258..c4e164c86 100644 --- a/config/testing/start.test.ts +++ b/config/testing/start.test.ts @@ -38,8 +38,14 @@ test("Launch electron app", async () => { }) console.log(appPath) - // Get the first window that the app opens, wait if necessary. - const window = await electronApp.firstWindow() + // The app opens a small loading/splash window first, then the main window. + // firstWindow() can return the splash, so explicitly pick the main window (it loads index.html). + let window = electronApp.windows().find((w) => w.url().includes("index.html")) + for (let i = 0; i < 20 && !window; i++) { + await delay(500) + window = electronApp.windows().find((w) => w.url().includes("index.html")) + } + if (!window) window = await electronApp.firstWindow() // Direct Electron console to Node terminal. window.on("console", console.log) @@ -50,30 +56,39 @@ test("Launch electron app", async () => { // Capture a screenshot. // await window.screenshot({ path: "intro.png" }) - // Initial setup - // Set language to English - await window.locator(".main .dropdownElem").getByRole("button").click({ timeout: 5 * timeoutMs }) - await window.locator(".main .dropdownElem .dropdown #id_English").click({ timeout: timeoutMs }) - // This triggers the Electron open dialog, mocked above - await window.locator(".main .showElem").getByRole("button").click() - await window.getByText("Get Started!").click({ timeout: timeoutMs }) - // This depends on whether there is existing shows to be loaded. - // As the existing show folder is also mocked to be a tmp folder, - // this is not expected. - // await window.getByTestId("alert.ack.check").click({ timeout: timeoutMs }) + // Wait for the app UI to be interactive: either the first-run setup popup or the main top bar. + await window.locator(".popup button.start, .top").first().waitFor({ timeout: 10 * timeoutMs }) + + // First-run setup popup (Initialize.svelte) — only shown when the app isn't initialized yet. + // It can be absent if a previous run already initialized the user data, so guard it. + const setupStart = window.locator(".popup button.start") + let didSetup = false + if ((await setupStart.count()) > 0) { + const setupPopup = window.locator(".popup") + + // Set language to English (it is the default, but select it explicitly so the English text selectors below stay stable) + await setupPopup.locator(".dropdown-trigger").first().click({ timeout: 5 * timeoutMs }) + await setupPopup.locator("li[role=option]").filter({ hasText: "English" }).first().click({ timeout: timeoutMs }) - // Give time to save initial state - // await delay(4_000) + // Set the data location via the folder picker; this triggers the Electron open dialog, mocked above + await setupPopup.locator(".button-trigger").first().click({ timeout: timeoutMs }) + + // Finish setup ("Get started!") + await setupStart.click({ timeout: timeoutMs }) + didSetup = true + } - // skip onboarding flow - await window.locator("#guideButtons").getByText("Skip").click({ timeout: timeoutMs }) + // skip the onboarding guide (it opens right after a fresh setup; its overlay otherwise intercepts clicks) + const skipGuide = window.locator("#guideButtons").getByText("Skip") + if (didSetup) await skipGuide.waitFor({ timeout: 5 * timeoutMs }) + if ((await skipGuide.count()) > 0) await skipGuide.click({ timeout: timeoutMs }) // Create a new project, then try creating a new show under the project - await window.locator("#leftPanel").getByText("New project").click({ timeout: timeoutMs }) + await window.getByText("New project").first().click({ timeout: timeoutMs }) await window.getByText("New show").first().click({ timeout: timeoutMs }) - // Expect the pop up to be visible - await expect(window.getByText("Name")).toBeVisible({ timeout: timeoutMs }) + // Expect the create-show popup to be visible (the name input is part of it) + await expect(window.locator("#name")).toBeVisible({ timeout: timeoutMs }) // Fill name of show await window.locator("#name").fill("New Test Show", { timeout: timeoutMs }) @@ -91,19 +106,17 @@ test("Launch electron app", async () => { // Click new show await window.getByTestId("create.show.popup.new.show").click({ timeout: timeoutMs }) - // Try changing group for Chorus - await window.locator("#group").getByTitle("Chorus").click({ timeout: timeoutMs }) + // Try changing group for Chorus (group names render as text in the #group list) + await window.locator("#group").getByText("Chorus").first().click({ timeout: timeoutMs }) //await window.getByText("Change group").hover({ timeout: timeoutMs }) - await window.locator("#group").getByText("Verse").click({ timeout: 5 * timeoutMs }) + await window.locator("#group").getByText("Verse").first().click({ timeout: 5 * timeoutMs }) // Verify the group changing was successful - await expect(window.locator("#group").getByTitle("Verse")).toBeVisible({ timeout: timeoutMs }) + await expect(window.locator("#group").getByText("Verse").first()).toBeVisible({ timeout: timeoutMs }) - // Manual save! - await window.locator(".top").getByText("FreeShow").click({ button: "right", timeout: timeoutMs }) - await window.getByText("Save", { exact: true }).click({ timeout: timeoutMs }) - // await window.keyboard.press("Control+S") - // await window.keyboard.press("Meta+S") + // Manual save via keyboard shortcut (Ctrl+S) — robust across menu changes; the app also auto-saves + await window.keyboard.press("Escape") + await window.keyboard.press("Control+s") await delay(5_000) } catch (ex) { console.log("Taking screenshot") diff --git a/package-lock.json b/package-lock.json index 51e3bec61..eabd7b02a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,7 @@ "@playwright/test": "^1.56.1", "@rollup/plugin-commonjs": "^28.0.6", "@rollup/plugin-node-resolve": "^15.3.1", - "@rollup/plugin-terser": "^0.4.4", + "@rollup/plugin-terser": "^1.0.0", "@rollup/plugin-typescript": "^12.1.4", "@soundtouchjs/audio-worklet": "^1.0.10", "@sveltejs/vite-plugin-svelte": "^2.5.3", @@ -78,7 +78,7 @@ "eslint-plugin-jsdoc": "^39.9.1", "eslint-plugin-prefer-arrow": "^1.2.3", "eslint-plugin-svelte3": "^4.0.0", - "npm-run-all": "^4.1.5", + "npm-run-all2": "^9.0.1", "pdfjs-dist": "^5.4.296", "playwright": "^1.56.1", "prettier": "^3.6.2", @@ -98,7 +98,7 @@ "svelte-check": "^2.10.3", "svelte-inspector": "github:vassbo/svelte-inspector#78307db", "svelte-preprocess": "^4.10.7", - "tmp": "^0.2.5", + "tmp": "^0.2.7", "tslib": "^2.8.1", "typescript": "^4.9.5", "vite": "^4.5.14", @@ -106,6 +106,9 @@ "xml2js": "^0.6.2", "youtube-player": "^5.6.0" }, + "engines": { + "node": ">=22.12.0" + }, "optionalDependencies": { "libspotifyctl": "^0.3.4" } @@ -2500,16 +2503,18 @@ } }, "node_modules/@rollup/plugin-terser": { - "version": "0.4.4", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-1.0.0.tgz", + "integrity": "sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==", "dev": true, "license": "MIT", "dependencies": { - "serialize-javascript": "^6.0.1", + "serialize-javascript": "^7.0.3", "smob": "^1.0.0", "terser": "^5.17.4" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "rollup": "^2.0.0||^3.0.0||^4.0.0" @@ -4345,49 +4350,10 @@ "version": "2.0.1", "license": "Python-2.0" }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-filter": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", - "integrity": "sha512-VW0FpCIhjZdarWjIz8Vpva7U95fl2Jn+b+mmFFMLn8PIVscOQcAgEznwUzTEuUHuqZqIxwzRlcaN/urTFFQoiw==", - "dev": true, - "license": "MIT" - }, "node_modules/array-flatten": { "version": "1.1.1", "license": "MIT" }, - "node_modules/array-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.1.tgz", - "integrity": "sha512-sxHIeJTGEsRC8/hYkZzdJNNPZ41EXHVys7pqMw1iwE/Kx8/hto0UbDuGQsSJ0ujPovj9qUZl6EOY/EiZ2g3d9Q==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-reduce": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", - "integrity": "sha512-8jR+StqaC636u7h3ye1co3lQRefgVVUQUhuAmRbDqIMeR2yuXzRvkCNQiQ5J/wbREmoBLNtp13dhaaVpZQDRUw==", - "dev": true, - "license": "MIT" - }, "node_modules/array-union": { "version": "2.1.0", "dev": true, @@ -4396,26 +4362,6 @@ "node": ">=8" } }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", @@ -4460,14 +4406,6 @@ "node": ">=0.12.0" } }, - "node_modules/async-function": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -4507,20 +4445,6 @@ "node": ">=0.10.0" } }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/axios": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", @@ -5075,23 +4999,6 @@ "@keyv/serialize": "^1.1.0" } }, - "node_modules/call-bind": { - "version": "1.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "license": "MIT", @@ -5776,54 +5683,6 @@ "node": ">=4" } }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/dayjs": { "version": "1.11.13", "dev": true, @@ -5936,6 +5795,7 @@ "version": "1.1.4", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -5952,6 +5812,7 @@ "version": "1.2.1", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -6641,73 +6502,6 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/es-abstract": { - "version": "1.24.0", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "license": "MIT", @@ -6752,22 +6546,6 @@ "node": ">= 0.4" } }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es6-error": { "version": "4.1.1", "dev": true, @@ -7609,20 +7387,6 @@ } } }, - "node_modules/for-each": { - "version": "0.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -7743,33 +7507,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/gauge": { "version": "3.0.2", "license": "ISC", @@ -7894,22 +7631,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -8038,6 +7759,7 @@ "version": "1.0.4", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" @@ -8363,17 +8085,6 @@ "node": ">=0.10.0" } }, - "node_modules/has-bigints": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-flag": { "version": "4.0.0", "license": "MIT", @@ -8385,6 +8096,7 @@ "version": "1.0.2", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -8392,20 +8104,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "license": "MIT", @@ -8693,19 +8391,6 @@ "version": "1.3.8", "license": "ISC" }, - "node_modules/internal-slot": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/ip-address": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", @@ -8723,104 +8408,30 @@ "node": ">= 0.10" } }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "dev": true, "license": "MIT" }, - "node_modules/is-async-function": { - "version": "2.1.1", + "node_modules/is-audio-buffer": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", "dev": true, "license": "MIT", "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-audio-buffer": { - "version": "1.1.0", - "license": "MIT" - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" + "binary-extensions": "^2.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-buffer": { "version": "1.1.6", "license": "MIT" }, - "node_modules/is-callable": { - "version": "1.2.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-core-module": { "version": "2.16.1", "dev": true, @@ -8835,37 +8446,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-data-view": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "license": "MIT", @@ -8873,20 +8453,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "license": "MIT", @@ -8894,23 +8460,6 @@ "node": ">=8" } }, - "node_modules/is-generator-function": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-glob": { "version": "4.0.3", "license": "MIT", @@ -8931,33 +8480,11 @@ "node": ">=8" } }, - "node_modules/is-map": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-module": { "version": "1.0.0", "dev": true, "license": "MIT" }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-number": { "version": "7.0.0", "license": "MIT", @@ -8965,21 +8492,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-number-object": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-obj": { "version": "2.0.0", "license": "MIT", @@ -9021,48 +8533,6 @@ "@types/estree": "*" } }, - "node_modules/is-regex": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-stream": { "version": "2.0.1", "license": "MIT", @@ -9073,51 +8543,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-string": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-unicode-supported": { "version": "2.1.0", "license": "MIT", @@ -9128,51 +8553,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "dev": true, - "license": "MIT" - }, "node_modules/isbinaryfile": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", @@ -9284,11 +8664,6 @@ "version": "3.0.1", "license": "MIT" }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "dev": true, @@ -9337,16 +8712,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsonify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", - "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", - "dev": true, - "license": "Public Domain", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/jwa": { "version": "2.0.1", "license": "MIT", @@ -9502,20 +8867,6 @@ } } }, - "node_modules/load-json-file": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/load-script": { "version": "1.0.0", "dev": true, @@ -10161,11 +9512,6 @@ "node": ">= 0.6" } }, - "node_modules/nice-try": { - "version": "1.0.5", - "dev": true, - "license": "MIT" - }, "node_modules/node-abi": { "version": "3.75.0", "license": "MIT", @@ -10357,227 +9703,130 @@ } }, "node_modules/node-machine-id": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", - "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", - "license": "MIT" - }, - "node_modules/nopt": { - "version": "5.0.0", - "license": "ISC", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/hosted-git-info": { - "version": "2.8.9", - "dev": true, - "license": "ISC" - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "6.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-all": { - "version": "4.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "chalk": "^2.4.1", - "cross-spawn": "^6.0.5", - "memorystream": "^0.3.1", - "minimatch": "^3.0.4", - "pidtree": "^0.3.0", - "read-pkg": "^3.0.0", - "shell-quote": "^1.6.1", - "string.prototype.padend": "^3.0.0" - }, - "bin": { - "npm-run-all": "bin/npm-run-all/index.js", - "run-p": "bin/run-p/index.js", - "run-s": "bin/run-s/index.js" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/npm-run-all/node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/npm-run-all/node_modules/color-name": { - "version": "1.1.3", - "dev": true, + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", + "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", "license": "MIT" }, - "node_modules/npm-run-all/node_modules/cross-spawn": { - "version": "6.0.6", - "dev": true, - "license": "MIT", + "node_modules/nopt": { + "version": "5.0.0", + "license": "ISC", "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" }, "engines": { - "node": ">=4.8" + "node": ">=6" } }, - "node_modules/npm-run-all/node_modules/escape-string-regexp": { - "version": "1.0.5", + "node_modules/normalize-path": { + "version": "3.0.0", "dev": true, "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">=0.10.0" } }, - "node_modules/npm-run-all/node_modules/has-flag": { - "version": "3.0.0", + "node_modules/normalize-url": { + "version": "6.1.0", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-run-all/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/npm-normalize-package-bin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-6.0.0.tgz", + "integrity": "sha512-tdt4aFn9QamlhdN3HV2D2ccpBwO5/fyjjbXUxYA6uBjyekMZcZvDq0aSj9t5Jo+tih6AYFnt/cuIRn9013e0Uw==", "dev": true, "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, "engines": { - "node": "*" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" } }, - "node_modules/npm-run-all/node_modules/path-key": { - "version": "2.0.1", + "node_modules/npm-run-all2": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-9.0.2.tgz", + "integrity": "sha512-+dd4SO2jAlLE06OzmJKzIe6QvvjXezcbmobnh8usR0a8BzQCABTdqTXqVPji0ICOhSQpIIrkGd7IzNl5iDaRSA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/semver": { - "version": "5.7.2", - "dev": true, - "license": "ISC", + "dependencies": { + "ansi-styles": "^6.2.1", + "cross-spawn": "^7.0.6", + "memorystream": "^0.3.1", + "picomatch": "^4.0.2", + "pidtree": "^1.0.0", + "read-package-json-fast": "^6.0.0", + "shell-quote": "^1.8.4", + "which": "^7.0.0" + }, "bin": { - "semver": "bin/semver" + "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0", + "npm": ">= 10" } }, - "node_modules/npm-run-all/node_modules/shebang-command": { - "version": "1.2.0", + "node_modules/npm-run-all2/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", - "dependencies": { - "shebang-regex": "^1.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/npm-run-all/node_modules/shebang-regex": { - "version": "1.0.0", + "node_modules/npm-run-all2/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=0.10.0" + "node": ">=20" } }, - "node_modules/npm-run-all/node_modules/supports-color": { - "version": "5.5.0", + "node_modules/npm-run-all2/node_modules/pidtree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-1.0.0.tgz", + "integrity": "sha512-avfAvjB9Dd0wdj3rjJX//yS+G79OO0KrS5pJHFJENjYGX6N4SMgEDBBI/yFy0lloOYSaC6XQxzpOAMPfSYFV/Q==", "dev": true, "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" + "bin": { + "pidtree": "bin/pidtree.js" }, "engines": { - "node": ">=4" + "node": ">=18" } }, - "node_modules/npm-run-all/node_modules/which": { - "version": "1.3.1", + "node_modules/npm-run-all2/node_modules/which": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-7.0.0.tgz", + "integrity": "sha512-RancgH2dmbLdHl6LRhEqvklWMgl/Hdnun0Y90KhBOLkMefg8Qa7/Zel8Sm+8HEcP6DEjzsWzpkuBQEZok58isA==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^2.0.0" + "isexe": "^4.0.0" }, "bin": { - "which": "bin/which" + "node-which": "bin/which.js" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" } }, "node_modules/npm-run-path": { @@ -10645,29 +9894,11 @@ "version": "1.1.1", "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">= 0.4" } }, - "node_modules/object.assign": { - "version": "4.1.7", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/on-finished": { "version": "2.4.1", "license": "MIT", @@ -10724,19 +9955,6 @@ "node": ">= 4.0" } }, - "node_modules/open-in-editor/node_modules/shell-quote": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", - "integrity": "sha512-V0iQEZ/uoem3NmD91rD8XiuozJnq9/ZJnbHVXHnWqP1ucAhS3yJ7sLIIzEi57wFFcK3oi3kFUC46uSyWr35mxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-filter": "~0.0.0", - "array-map": "~0.0.0", - "array-reduce": "~0.0.0", - "jsonify": "~0.0.0" - } - }, "node_modules/opener": { "version": "1.5.2", "dev": true, @@ -10820,22 +10038,6 @@ "ws": "^8.16.0" } }, - "node_modules/own-keys": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/p-cancelable": { "version": "2.1.1", "dev": true, @@ -10910,18 +10112,6 @@ "node": ">=6" } }, - "node_modules/parse-json": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/parse-ms": { "version": "4.0.0", "license": "MIT", @@ -11164,25 +10354,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pidtree": { - "version": "0.3.1", - "dev": true, - "license": "MIT", - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/pify": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/pkg-up": { "version": "3.1.0", "license": "MIT", @@ -11291,14 +10462,6 @@ "node": ">=10.4.0" } }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/postcss": { "version": "8.5.14", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", @@ -11686,14 +10849,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/range-parser": { "version": "1.2.1", "license": "MIT", @@ -11794,28 +10949,28 @@ "read-binary-file-arch": "cli.js" } }, - "node_modules/read-pkg": { - "version": "3.0.0", + "node_modules/read-package-json-fast": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-6.0.0.tgz", + "integrity": "sha512-PNaGjoCnw9DBA2Kl8D+8po957z778q/HOPuY2u3Bkw/JO3eC8MDx7jn/PgMtSgpcBbs+6UOjDbwReGpXmRvs0g==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "json-parse-even-better-errors": "^6.0.0", + "npm-normalize-package-bin": "^6.0.0" }, "engines": { - "node": ">=4" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" } }, - "node_modules/read-pkg/node_modules/path-type": { - "version": "3.0.0", + "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-6.0.0.tgz", + "integrity": "sha512-2/8adwnK1/+Fdjyts4r6wSpfANWw8zdNhU9U/Llk59c6O+DjSisPWPykwoL8gZmocP9Dy64S7oie2g+Mia123A==", "dev": true, "license": "MIT", - "dependencies": { - "pify": "^3.0.0" - }, "engines": { - "node": ">=4" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" } }, "node_modules/readable-stream": { @@ -11904,46 +11059,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -12341,77 +11456,28 @@ "dependencies": { "mri": "^1.1.0" }, - "engines": { - "node": ">=6" - } - }, - "node_modules/safe-array-concat": { - "version": "1.1.3", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "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", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=6" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "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/safer-buffer": { "version": "2.1.2", "license": "MIT" @@ -12571,11 +11637,13 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.2", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", "dev": true, "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" + "engines": { + "node": ">=20.0.0" } }, "node_modules/serve-static": { @@ -12595,49 +11663,6 @@ "version": "2.0.0", "license": "ISC" }, - "node_modules/set-function-length": { - "version": "1.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/setprototypeof": { "version": "1.2.0", "license": "ISC" @@ -13097,15 +12122,6 @@ "dev": true, "license": "MIT" }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, "node_modules/spdx-exceptions": { "version": "2.5.0", "dev": true, @@ -13195,18 +12211,6 @@ "dev": true, "license": "MIT" }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/stream-combiner": { "version": "0.2.2", "license": "MIT", @@ -13250,76 +12254,6 @@ "node": ">=8" } }, - "node_modules/string.prototype.padend": { - "version": "3.1.6", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "license": "MIT", @@ -13344,14 +12278,6 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/strip-final-newline": { "version": "4.0.0", "license": "MIT", @@ -14048,7 +12974,9 @@ } }, "node_modules/tmp": { - "version": "0.2.5", + "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" @@ -14197,76 +13125,6 @@ "node": ">= 0.6" } }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/typescript": { "version": "4.9.5", "dev": true, @@ -14289,23 +13147,6 @@ "node": ">=8" } }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/undici": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", @@ -14420,15 +13261,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, "node_modules/vary": { "version": "1.1.2", "license": "MIT", @@ -15681,87 +14513,6 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.19", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", diff --git a/package.json b/package.json index 5a1374a17..f96964f1c 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,13 @@ "repository": "git@github.com:ChurchApps/FreeShow.git", "author": "ChurchApps <support@livecs.org>", "license": "GPL-3.0", + "engines": { + "node": ">=22.12.0" + }, + "overrides": { + "shell-quote": "^1.8.4", + "tmp": "^0.2.7" + }, "scripts": { "postinstall": "electron-builder install-app-deps", "---DEVELOPMENT---": "", @@ -59,7 +66,7 @@ "@playwright/test": "^1.56.1", "@rollup/plugin-commonjs": "^28.0.6", "@rollup/plugin-node-resolve": "^15.3.1", - "@rollup/plugin-terser": "^0.4.4", + "@rollup/plugin-terser": "^1.0.0", "@rollup/plugin-typescript": "^12.1.4", "@soundtouchjs/audio-worklet": "^1.0.10", "@sveltejs/vite-plugin-svelte": "^2.5.3", @@ -88,7 +95,7 @@ "eslint-plugin-jsdoc": "^39.9.1", "eslint-plugin-prefer-arrow": "^1.2.3", "eslint-plugin-svelte3": "^4.0.0", - "npm-run-all": "^4.1.5", + "npm-run-all2": "^9.0.1", "pdfjs-dist": "^5.4.296", "playwright": "^1.56.1", "prettier": "^3.6.2", @@ -108,7 +115,7 @@ "svelte-check": "^2.10.3", "svelte-inspector": "github:vassbo/svelte-inspector#78307db", "svelte-preprocess": "^4.10.7", - "tmp": "^0.2.5", + "tmp": "^0.2.7", "tslib": "^2.8.1", "typescript": "^4.9.5", "vite": "^4.5.14", diff --git a/src/electron/IPC/main.ts b/src/electron/IPC/main.ts index 0b9a8b307..4dcfebd1c 100644 --- a/src/electron/IPC/main.ts +++ b/src/electron/IPC/main.ts @@ -38,21 +38,32 @@ export async function receiveMain(e: Electron.IpcMainEvent, msg: MainReceiveValu } const currentlyAwaiting: string[] = [] +// single shared MAIN listener that routes each reply to its awaiting request by listenerId +const pendingToMain = new Map<string, (msg: ToMainReceiveValue) => void>() +let toMainDispatcherRegistered = false +function ensureToMainDispatcher() { + if (toMainDispatcherRegistered) return + toMainDispatcherRegistered = true + ipcMain.on(MAIN, (_e: IpcMainEvent, msg: ToMainReceiveValue, listenId: string) => { + if (!listenId) return + const resolver = pendingToMain.get(listenId) + if (resolver) resolver(msg) + }) +} + // @ts-ignore export async function requestToMain<ID extends ToMain, R = Awaited<ToMainReturnPayloads[ID]>>(id: ID, value: ToMainSendValue<ID>, callback?: (data: R | null) => void, timeoutMs = 15000) { const listenerId = id + uid(5) currentlyAwaiting.push(listenerId) - - sendToMain(id, value, listenerId) + ensureToMainDispatcher() // LISTENER const waitingTimeout = timeoutMs let timeout: NodeJS.Timeout | null = null let settled = false - let receive: null | ((_e: IpcMainEvent, msg: ToMainReceiveValue, listenId: string) => void) = null const cleanup = () => { if (timeout) clearTimeout(timeout) - if (receive) ipcMain.removeListener(MAIN, receive) + pendingToMain.delete(listenerId) const waitIndex = currentlyAwaiting.indexOf(listenerId) if (waitIndex > -1) currentlyAwaiting.splice(waitIndex, 1) @@ -68,16 +79,16 @@ export async function requestToMain<ID extends ToMain, R = Awaited<ToMainReturnP resolve(null) }, waitingTimeout) - receive = (_e: IpcMainEvent, msg: ToMainReceiveValue, listenId: string) => { + pendingToMain.set(listenerId, (msg: ToMainReceiveValue) => { if (settled) return - if (msg.channel !== id || listenId !== listenerId) return + if (msg.channel !== id) return settled = true cleanup() resolve(msg.data as R) - } + }) - ipcMain.on(MAIN, receive) + sendToMain(id, value, listenerId) }) if (callback) callback(returnData) diff --git a/src/electron/data/thumbnails.ts b/src/electron/data/thumbnails.ts index 67a3f865e..c9a2857ed 100644 --- a/src/electron/data/thumbnails.ts +++ b/src/electron/data/thumbnails.ts @@ -65,7 +65,6 @@ export async function getThumbnail(data: { input: string; size: number }) { // source file is newer than thumbnail, delete old thumbnail deleteFile(outputPath) } else { - currentlyGenerating.delete(mediaId) return finish(outputPath) } } @@ -77,6 +76,9 @@ export async function getThumbnail(data: { input: string; size: number }) { return finish(outputPath) function finish(output: string) { + // release the lock if it's still held (cache hit, or generation that timed out without clearing it) + if (currentlyGenerating.has(mediaId)) currentlyGenerating.delete(mediaId) + if (failedPaths.includes(mediaId)) { failedPaths.splice(failedPaths.indexOf(mediaId), 1) // allow retrying return { ...data, output: "" } diff --git a/src/electron/data/zip.ts b/src/electron/data/zip.ts index e12ec9d01..1fd819e8f 100644 --- a/src/electron/data/zip.ts +++ b/src/electron/data/zip.ts @@ -132,10 +132,22 @@ export async function decompressZipStream(file: string, asBuffer = false, option }) } +// neutralise path-traversal/absolute segments so a crafted archive can't write outside the extraction directory (zip-slip) +// legitimate nested names (e.g. "ppt/media/img.png") are left unchanged +function sanitizeZipPath(name: string): string { + return name + .replace(/\\/g, "/") + .split("/") + .filter((segment) => segment && segment !== "." && segment !== "..") + .join("/") +} + function processEntry(entry: yauzl.Entry, zipfile: yauzl.ZipFile, data: { content: Buffer | string; name: string; extension: string }[], asBuffer: boolean, options: DecompressStreamOptions | undefined) { const name = entry.fileName + const safeName = sanitizeZipPath(name) const extension = getExtension(name) - const outputPath = options?.getOutputPath?.(name) + // pass the sanitized name to callers that build a destination path, so "../" entries can't escape the target folder + const outputPath = options?.getOutputPath?.(safeName) zipfile.openReadStream(entry, (err, readStream) => { if (err || !readStream) { diff --git a/src/electron/servers.ts b/src/electron/servers.ts index 60b716c1f..7b7dd82e7 100644 --- a/src/electron/servers.ts +++ b/src/electron/servers.ts @@ -36,31 +36,61 @@ createServers() function createServers() { const serverList = Object.keys(serverPorts) as ServerName[] serverList.forEach((id) => { - const app = express() - const server = http.createServer(app) + createServerInstance(id) + // the app -> client IPC bridge is registered once and always emits via the current io instance + registerIpcBridge(id) + }) +} - if (id === "STAGE") { - // app.get('/show/:showId/:slideId', handleShowSlideHtmlRequest); - // Serve media files - // app.get('/media/:token', handleMediaRequest); - } +// (re)create a fresh express app + http server + socket.io instance. +// a closed http.Server cannot be re-listened, so this is called again on every (re)start. +function createServerInstance(id: ServerName) { + const app = express() + const server = http.createServer(app) + + if (id === "STAGE") { + // app.get('/show/:showId/:slideId', handleShowSlideHtmlRequest); + // Serve media files + // app.get('/media/:token', handleMediaRequest); + } + + // The join import from 'path' is still needed for this part + app.get("/", (_req, res: Response) => res.sendFile(join(__dirname, id.toLowerCase(), "index.html"))) + // The join import from 'path' is still needed for this part + app.use(express.static(join(__dirname, id.toLowerCase()))) + + const io = new Server(server) + + servers[id] = { + ...servers[id], + port: serverPorts[id], + server, + io, + max: servers[id]?.max ?? 10, + connections: {}, // always start with a clean connection map + data: servers[id]?.data ?? {} + } + ioServers[id] = io - // The join import from 'path' is still needed for this part - app.get("/", (_req, res: Response) => res.sendFile(join(__dirname, id.toLowerCase(), "index.html"))) - // The join import from 'path' is still needed for this part - app.use(express.static(join(__dirname, id.toLowerCase()))) - - servers[id] = { - ...servers[id], - port: serverPorts[id], - server, - io: new Server(server), - max: 10, - connections: {}, - data: {} + // RECEIVE CONNECTION FROM CLIENT + io.on("connection", (socket) => { + if (Object.keys(servers[id]!.connections).length >= servers[id]!.max) { + io.emit(id, { channel: "ERROR", id: "overLimit", data: servers[id]!.max }) + socket.disconnect() + return } - createBridge(id, servers[id]!) + initialize(id, socket) + }) +} + +function registerIpcBridge(id: ServerName) { + // SEND DATA FROM APP TO CLIENT (uses the current io instance via ioServers, so it survives server recreation) + ipcMain.on(id, (_e: IpcMainEvent, msg: Message) => { + const io = ioServers[id] + if (!io) return + if (msg?.id) io.to(msg.id).emit(id, msg) + else io.emit(id, msg) }) } @@ -73,6 +103,9 @@ export function startServers({ ports, max, disabled, data }: MainSendPayloads[Ma serverList.forEach((id: ServerName) => { if (!servers[id] || disabled[id.toLowerCase()] !== false) return + // recreate a fresh, listenable instance (the previous http.Server may have been closed) + createServerInstance(id) + servers[id]!.max = max if (ports[id.toLowerCase()]) servers[id]!.port = ports[id.toLowerCase()] @@ -80,7 +113,10 @@ export function startServers({ ports, max, disabled, data }: MainSendPayloads[Ma servers[id]!.server.listen(servers[id]!.port, onStarted) servers[id]!.server.once("error", (err: Error) => { - if ((err as any).code === "EADDRINUSE") servers[id]!.server.close() + if ((err as any).code === "EADDRINUSE") { + servers[id]!.server.close() + console.error(`${id} server port ${servers[id]!.port} already in use`) + } }) function onStarted() { @@ -108,27 +144,10 @@ export function closeServers() { const serverList = Object.keys(servers) as ServerName[] serverList.forEach((id: ServerName) => { if (!servers[id]?.server) return - servers[id]!.server.close() - }) -} - -function createBridge(id: ServerName, server: ServerValues) { - // RECEIVE CONNECTION FROM CLIENT - server.io.on("connection", (socket) => { - if (Object.keys(server.connections).length >= server.max) { - server.io.emit(id, { channel: "ERROR", id: "overLimit", data: server.max }) - socket.disconnect() - return - } - - initialize(id, socket) - }) - - // SEND DATA FROM APP TO CLIENT - ioServers[id] = server.io - ipcMain.on(id, (_e: IpcMainEvent, msg: Message) => { - if (msg?.id) server.io.to(msg.id).emit(id, msg) - else server.io.emit(id, msg) + // close socket.io (disconnects clients and closes the underlying http server) + servers[id]!.io.close() + // clear connection state so reconnect counts / max limit don't drift across restarts + servers[id]!.connections = {} }) } diff --git a/src/electron/utils/files.ts b/src/electron/utils/files.ts index 75444a9ca..d58db0602 100644 --- a/src/electron/utils/files.ts +++ b/src/electron/utils/files.ts @@ -604,15 +604,17 @@ export function getFileInfo(filePath: string) { } // READ EXIF -export function readExifData({ id }: { id: string }): Promise<{ id: string; exif: ExifData }> { +export function readExifData({ id }: { id: string }): Promise<{ id: string; exif: ExifData | undefined }> { return new Promise((resolve) => { try { new ExifImage({ image: id }, (err, exifData) => { actionComplete(err, "Error getting EXIF data") - if (!err) resolve({ id, exif: exifData }) + // always settle the promise, otherwise an awaiting IPC request hangs until it times out + resolve({ id, exif: err ? undefined : exifData }) }) } catch (err) { actionComplete(err as Error, "Error loading EXIF image") + resolve({ id, exif: undefined }) } }) } diff --git a/src/frontend/components/helpers/media.ts b/src/frontend/components/helpers/media.ts index a14b0d916..c758cdb77 100644 --- a/src/frontend/components/helpers/media.ts +++ b/src/frontend/components/helpers/media.ts @@ -78,7 +78,10 @@ export function encodeFilePath(path: string): string { if (path.startsWith("file://")) path = path.replace("file://", "") try { if (path.match(/%[0-9a-fA-F]{2}/)) path = decodeURIComponent(path) - } catch (e) {} + } catch (e) { + // malformed percent-encoding: keep the original path + console.debug("Could not decode media path:", path, e) + } // encode each path segment except for drive letter (e.g. C:) to avoid issues with ":" in file names on Windows const encoded = splitPath(path).map((seg, i) => (i === 0 && /^[a-zA-Z]:$/.test(seg) ? seg : encodeURIComponent(seg))) diff --git a/src/frontend/converters/csv.ts b/src/frontend/converters/csv.ts index 5e0c63c50..833663139 100644 --- a/src/frontend/converters/csv.ts +++ b/src/frontend/converters/csv.ts @@ -23,7 +23,8 @@ export function convertCSV(data: any) { const content: string = file.content const slides: Slide[] = [] - content.split("\n").forEach(createSlide) + // normalize CRLF/CR line endings so no stray "\r" is left in the last field of each row + content.replace(/\r\n?/g, "\n").split("\n").forEach(createSlide) function createSlide(csvLine: string) { const items: Item[] = [] diff --git a/src/frontend/utils/save.ts b/src/frontend/utils/save.ts index 6aeb21431..6486ad2b0 100644 --- a/src/frontend/utils/save.ts +++ b/src/frontend/utils/save.ts @@ -356,7 +356,9 @@ export function unsavedUpdater() { store = customSavedListener[id](clone(store)) try { cachedValues[id] = JSON.stringify(store) - } catch {} + } catch (e) { + console.debug("Could not cache custom listener value for:", id, e) + } } }) diff --git a/src/frontend/utils/sendData.ts b/src/frontend/utils/sendData.ts index a96ec688d..005b493cb 100644 --- a/src/frontend/utils/sendData.ts +++ b/src/frontend/utils/sendData.ts @@ -3,7 +3,7 @@ import { CONTROLLER, REMOTE, STAGE } from "../../types/Channels" import type { ClientMessage, Clients } from "../../types/Socket" import { API_ACTIONS } from "../components/actions/api" import { checkWindowCapture } from "../components/helpers/output" -import { connections, shows } from "../stores" +import { connections, remotePassword, shows } from "../stores" import { isMainWindow } from "./common" import { receiveCONTROLLER } from "./controllerTalk" import { receiveREMOTE } from "./remoteTalk" @@ -68,6 +68,15 @@ export async function sendData(id: Clients, msg: ClientMessage, check = false) { let connectionId = msg.id if (!connectionId) connectionId = Object.keys(get(connections)[id] || {})[0] + // when a REMOTE password is set, only honor control/API messages from connections that have authenticated + if (id === REMOTE && get(remotePassword).length) { + const PRE_AUTH_CHANNELS = ["PASSWORD", "ACCESS"] + if (!PRE_AUTH_CHANNELS.includes(channel)) { + const entered = get(connections).REMOTE?.[connectionId || ""]?.entered + if (!entered) return + } + } + if (channel === "API") { if (!msg.data) msg.data = {} const apiId = msg.api || "" diff --git a/src/types/IPC/Main.ts b/src/types/IPC/Main.ts index ef726573c..2044be192 100644 --- a/src/types/IPC/Main.ts +++ b/src/types/IPC/Main.ts @@ -307,7 +307,7 @@ export interface MainReturnPayloads { [Main.DOES_MEDIA_EXIST]: Promise<{ path: string; exists: boolean; creationTime?: number }> [Main.GET_THUMBNAIL]: Promise<{ output: string; input: string; size: number }> // [Main.PDF_TO_IMAGE]: Promise<string[]> - [Main.READ_EXIF]: Promise<{ id: string; exif: ExifData }> + [Main.READ_EXIF]: Promise<{ id: string; exif: ExifData | undefined }> [Main.MEDIA_CODEC]: Promise<{ path: string; codecs: string[]; mimeType: string; mimeCodec: string }> [Main.MEDIA_TRACKS]: Promise<{ path: string; tracks: Subtitle[] }> [Main.MEDIA_IS_DOWNLOADED]: Promise<{ path: string; buffer: Buffer | null; protectedUrl?: string | null; isDownloading?: boolean } | null> From b7c095f84f36d1835ccc157b685ac968bdd8ebdc Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Wed, 10 Jun 2026 03:18:19 +0000 Subject: [PATCH 23/32] feat: migrate to Svelte 5 + Vite 8 + TypeScript 5 (compatibility mode) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Toolchain: - svelte 3 -> 5, vite 4 -> 8, @sveltejs/vite-plugin-svelte 2 -> 7, svelte-check 2 -> 4, typescript 4.9 -> 5.9, @tsconfig/svelte 2 -> 5. - Replace svelte-preprocess with vitePreprocess (svelte.config + both vite configs). - Drop svelte-inspector fork + rollup-plugin-svelte (Svelte-3-only, used only by the unused rollup.config.mjs). Code changes (Svelte 5 / Vite 8 / TS 5 compatibility): - Component instantiation: new App({target}) -> mount(App, {target}) (frontend + 5 server entries). - Top.svelte: <script type="ts"> -> <script lang="ts"> (vitePreprocess needs lang). - Connection.svelte: rename {@const connections} (Svelte 5 disallows shadowing a store name in a store subscription). - Server type-only imports marked (Rolldown rejects type-as-value): Writable, ReceiverKey, Deep*/Inferred/Nested, AutosizeTypes, StageItem/StageLayout. - bonjour.ts: bonjour-service 1.4 exports Service as a value -> InstanceType<typeof Service> (TS 5). - Raise the E2E test timeout (the flow is slightly slower under Svelte 5). Status: full production build passes (frontend + servers + electron); the app launches and the E2E user flow completes (verified via screenshot). KNOWN REMAINING: the electron process does not exit cleanly after the test (worker-teardown hang) — needs investigation; svelte-check (v4) reports ~584 issues (mostly Svelte 5 deprecations/strictness) for a follow-up cleanup; full feature QA pending. --- config/building/vite.config.servers.mjs | 13 ++++------- config/testing/playwright.config.ts | 2 ++ package.json | 23 ++++++++----------- src/electron/data/bonjour.ts | 4 ++-- src/frontend/components/main/Top.svelte | 2 +- .../settings/tabs/Connection.svelte | 6 ++--- src/frontend/main.ts | 5 ++-- src/server/cam/main.ts | 3 ++- src/server/controller/main.ts | 3 ++- src/server/output_stream/main.ts | 3 ++- .../remote/components/show/Textbox.svelte | 2 +- src/server/remote/main.ts | 3 ++- src/server/remote/util/socket.ts | 2 +- src/server/remote/util/stores.ts | 4 ++-- src/server/stage/main.ts | 3 ++- src/server/stage/util/itemHelpers.ts | 2 +- src/server/stage/util/stores.ts | 4 ++-- svelte.config.mjs | 10 ++------ vite.config.mjs | 19 +++++++-------- 19 files changed, 52 insertions(+), 61 deletions(-) diff --git a/config/building/vite.config.servers.mjs b/config/building/vite.config.servers.mjs index 535b31e09..77770aadc 100644 --- a/config/building/vite.config.servers.mjs +++ b/config/building/vite.config.servers.mjs @@ -1,6 +1,5 @@ import { defineConfig } from 'vite' -import { svelte } from '@sveltejs/vite-plugin-svelte' -import sveltePreprocess from 'svelte-preprocess' +import { svelte, vitePreprocess } from '@sveltejs/vite-plugin-svelte' import { resolve } from 'path' import { copyFileSync, mkdirSync, existsSync, readFileSync, createReadStream } from 'fs' @@ -128,17 +127,13 @@ function copyServerFilesPlugin(serverId) { export default defineConfig({ plugins: [ svelte({ - preprocess: sveltePreprocess({ - typescript: serverConfig.typescript ? { - tsconfigFile: `./config/typescript/tsconfig.server${production ? '.prod' : ''}.json`, - } : false, - }), + preprocess: vitePreprocess(), compilerOptions: { dev: !production, }, onwarn: (warning, handler) => { - // disable A11y warnings - if (warning.code.startsWith('a11y-')) return + // disable A11y warnings (Svelte 5 renamed codes from a11y-* to a11y_*) + if (warning.code.startsWith('a11y-') || warning.code.startsWith('a11y_')) return handler(warning) }, }), diff --git a/config/testing/playwright.config.ts b/config/testing/playwright.config.ts index dbe73119e..b7067f94b 100644 --- a/config/testing/playwright.config.ts +++ b/config/testing/playwright.config.ts @@ -1,6 +1,8 @@ import { defineConfig } from "@playwright/test" export default defineConfig({ + // the single E2E walks the whole app with several built-in delays; give it headroom + timeout: 90_000, // 'github' for GitHub Actions CI to generate annotations, plus a concise 'dot' // default 'line' when running locally reporter: [[process.env.CI ? "html" : "line", { outputFolder: "test-output/playwright-report" }]], diff --git a/package.json b/package.json index f96964f1c..68019dc97 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freeshow", - "version": "1.6.2-beta.2", + "version": "1.6.2-beta.1", "private": true, "main": "build/electron/index.js", "description": "Show song lyrics and more for free!", @@ -48,8 +48,7 @@ "pack:arm64": "electron-builder --config config/building/electron-builder-lnxarm.yaml --dir", "replace-electron": "node config/building/electron-replace-lnxarm.js", "---CHECKING---": "", - "test": "npm-run-all -s test:unit test:playwright test:format test:svelte", - "test:unit": "vitest run --config config/testing/vitest.config.ts", + "test": "npm-run-all -s test:playwright test:format test:svelte", "test:playwright": "npx playwright test --config config/testing/playwright.config.ts", "test:format": "npx prettier --config config/formatting/.prettierrc.yaml --check src scripts", "test:svelte": "svelte-check", @@ -69,9 +68,9 @@ "@rollup/plugin-terser": "^1.0.0", "@rollup/plugin-typescript": "^12.1.4", "@soundtouchjs/audio-worklet": "^1.0.10", - "@sveltejs/vite-plugin-svelte": "^2.5.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", "@tanstack/pacer": "^0.14.0", - "@tsconfig/svelte": "^2.0.1", + "@tsconfig/svelte": "^5.0.0", "@types/better-sqlite3": "^7.6.13", "@types/ebml": "^3.0.5", "@types/exif": "^0.6.5", @@ -106,20 +105,18 @@ "rollup-plugin-css-only": "^4.5.2", "rollup-plugin-livereload": "^2.0.5", "rollup-plugin-serve": "^3.0.0", - "rollup-plugin-svelte": "^7.2.2", "socket.io-client": "^4.8.1", "stylelint": "^16.23.1", "stylelint-config-html": "^1.1.0", "stylelint-use-logical": "^2.1.2", - "svelte": "^3.59.2", - "svelte-check": "^2.10.3", - "svelte-inspector": "github:vassbo/svelte-inspector#78307db", - "svelte-preprocess": "^4.10.7", + "svelte": "^5.56.3", + "svelte-check": "^4.6.0", + "svelte-eslint-parser": "^1.8.0", "tmp": "^0.2.7", "tslib": "^2.8.1", - "typescript": "^4.9.5", - "vite": "^4.5.14", - "vitest": "^2.1.9", + "typescript": "^5.9.3", + "typescript-eslint": "^8.61.0", + "vite": "^8.0.16", "xml2js": "^0.6.2", "youtube-player": "^5.6.0" }, diff --git a/src/electron/data/bonjour.ts b/src/electron/data/bonjour.ts index 0525c2222..d36da161a 100644 --- a/src/electron/data/bonjour.ts +++ b/src/electron/data/bonjour.ts @@ -1,4 +1,4 @@ -import Bonjour, { type Service } from "bonjour-service" +import Bonjour, { Service } from "bonjour-service" import { type ChildProcess, spawn } from "child_process" import crypto from "crypto" import os from "os" @@ -19,7 +19,7 @@ const bonjour = isMac const ips = getLocalIPs() const instanceID = crypto.randomBytes(3).toString("hex") -const publishedServices: Partial<Record<string, Service>> = {} +const publishedServices: Partial<Record<string, InstanceType<typeof Service>>> = {} const dnsSdProcesses: Partial<Record<string, ChildProcess>> = {} // check for existing freeshow services before publishing diff --git a/src/frontend/components/main/Top.svelte b/src/frontend/components/main/Top.svelte index 27e1fb059..8ca1e49ee 100644 --- a/src/frontend/components/main/Top.svelte +++ b/src/frontend/components/main/Top.svelte @@ -1,4 +1,4 @@ -<script type="ts"> +<script lang="ts"> import { slide } from "svelte/transition" import { activeEdit, activePage, activeProfile, activeProject, activeShow, cloudUsers, dictionary, drawSettings, drawTool, os, outputDisplay, outputs, paintCache, profiles, saved, settingsTab, shows } from "../../stores" import { getCloudUsers } from "../../utils/cloudSync" diff --git a/src/frontend/components/settings/tabs/Connection.svelte b/src/frontend/components/settings/tabs/Connection.svelte index 8194e8042..50c34d7c5 100644 --- a/src/frontend/components/settings/tabs/Connection.svelte +++ b/src/frontend/components/settings/tabs/Connection.svelte @@ -171,7 +171,7 @@ {#each servers as server} {@const disabled = server.id === "companion" ? $companion?.enabled !== true : server.enabledByDefault ? $disabledServers[server.id] === true : $disabledServers[server.id] !== false} - {@const connections = Object.keys($connections[server.id.toUpperCase()] || {})?.length || 0} + {@const connectionCount = Object.keys($connections[server.id.toUpperCase()] || {})?.length || 0} <InputRow> <MaterialButton @@ -190,8 +190,8 @@ {#if server.id === "companion"} <span style="opacity: 0.5;font-size: 0.7em;margin-left: 5px;">WebSocket/REST/OSC/Companion</span> {/if} - {#if connections} - <span style="opacity: 0.5;font-size: 0.7em;margin-left: 5px;">{connections}</span> + {#if connectionCount} + <span style="opacity: 0.5;font-size: 0.7em;margin-left: 5px;">{connectionCount}</span> {/if} </span> diff --git a/src/frontend/main.ts b/src/frontend/main.ts index 93b395d9e..143b5136b 100644 --- a/src/frontend/main.ts +++ b/src/frontend/main.ts @@ -2,7 +2,7 @@ // Svelte app entry point import * as Sentry from "@sentry/electron/renderer" -import "svelte" +import { mount } from "svelte" import App from "./App.svelte" import { ERROR_FILTER } from "./utils/common" @@ -17,6 +17,7 @@ Sentry.init({ } }) -const app = new App({ target: document.body }) +// Svelte 5: components are instantiated with mount() instead of `new App()` +const app = mount(App, { target: document.body }) export default app diff --git a/src/server/cam/main.ts b/src/server/cam/main.ts index af573bbba..215416ea3 100644 --- a/src/server/cam/main.ts +++ b/src/server/cam/main.ts @@ -1,6 +1,7 @@ +import { mount } from "svelte" import App from "./App.svelte" -const app = new App({ +const app = mount(App, { target: document.body, props: {} }) diff --git a/src/server/controller/main.ts b/src/server/controller/main.ts index af573bbba..215416ea3 100644 --- a/src/server/controller/main.ts +++ b/src/server/controller/main.ts @@ -1,6 +1,7 @@ +import { mount } from "svelte" import App from "./App.svelte" -const app = new App({ +const app = mount(App, { target: document.body, props: {} }) diff --git a/src/server/output_stream/main.ts b/src/server/output_stream/main.ts index af573bbba..215416ea3 100644 --- a/src/server/output_stream/main.ts +++ b/src/server/output_stream/main.ts @@ -1,6 +1,7 @@ +import { mount } from "svelte" import App from "./App.svelte" -const app = new App({ +const app = mount(App, { target: document.body, props: {} }) diff --git a/src/server/remote/components/show/Textbox.svelte b/src/server/remote/components/show/Textbox.svelte index c6ef4eb3e..d0558fdbf 100644 --- a/src/server/remote/components/show/Textbox.svelte +++ b/src/server/remote/components/show/Textbox.svelte @@ -1,6 +1,6 @@ <script lang="ts"> import type { Item } from "../../../../types/Show" - import autosize, { AutosizeTypes } from "../../../common/util/autosize" + import autosize, { type AutosizeTypes } from "../../../common/util/autosize" import { createVirtualBreaks } from "../../../common/util/show" import { getStyles } from "../../../common/util/style" import { mediaCache } from "../../util/stores" diff --git a/src/server/remote/main.ts b/src/server/remote/main.ts index af573bbba..215416ea3 100644 --- a/src/server/remote/main.ts +++ b/src/server/remote/main.ts @@ -1,6 +1,7 @@ +import { mount } from "svelte" import App from "./App.svelte" -const app = new App({ +const app = mount(App, { target: document.body, props: {} }) diff --git a/src/server/remote/util/socket.ts b/src/server/remote/util/socket.ts index e70f6d70e..275e4189d 100644 --- a/src/server/remote/util/socket.ts +++ b/src/server/remote/util/socket.ts @@ -1,6 +1,6 @@ import { io } from "socket.io-client" import { _get, _update } from "./stores" -import { receiver, ReceiverKey } from "./receiver" +import { receiver, type ReceiverKey } from "./receiver" const socket = io() let id: string = "" diff --git a/src/server/remote/util/stores.ts b/src/server/remote/util/stores.ts index b3be2db9c..7079e6982 100644 --- a/src/server/remote/util/stores.ts +++ b/src/server/remote/util/stores.ts @@ -1,11 +1,11 @@ -import { get, Writable, writable } from "svelte/store" +import { get, type Writable, writable } from "svelte/store" import type { Bible } from "../../../types/Bible" import type { OutData } from "../../../types/Output" import type { Dictionary } from "../../../types/Settings" import type { Overlays, Show, ShowList } from "../../../types/Show" import type { BibleCategories } from "../../../types/Tabs" import { clone } from "../../common/util/helpers" -import { __update, DeepKey, DeepNested, Inferred, Nested } from "../../common/util/stores" +import { __update, type DeepKey, type DeepNested, type Inferred, type Nested } from "../../common/util/stores" import type { Project, ProjectShowRef } from "./../../../types/Projects" import { DEFAULT_DICTIONARY } from "./dictionary" diff --git a/src/server/stage/main.ts b/src/server/stage/main.ts index af573bbba..215416ea3 100644 --- a/src/server/stage/main.ts +++ b/src/server/stage/main.ts @@ -1,6 +1,7 @@ +import { mount } from "svelte" import App from "./App.svelte" -const app = new App({ +const app = mount(App, { target: document.body, props: {} }) diff --git a/src/server/stage/util/itemHelpers.ts b/src/server/stage/util/itemHelpers.ts index 080e531d5..c002bfe7f 100644 --- a/src/server/stage/util/itemHelpers.ts +++ b/src/server/stage/util/itemHelpers.ts @@ -1,7 +1,7 @@ import { get } from "svelte/store" import type { Variable } from "../../../types/Main" import type { Condition, ConditionValue, Item, LayoutRef } from "../../../types/Show" -import { StageItem, StageLayout } from "../../../types/Stage" +import type { StageItem, StageLayout } from "../../../types/Stage" import { keysToID, sortByName } from "../../common/util/helpers" import { getCurrentTimerValue } from "../../common/util/time" import { getDynamicValue, getLayoutRef, replaceDynamicValues } from "../helpers/show" diff --git a/src/server/stage/util/stores.ts b/src/server/stage/util/stores.ts index f78905feb..7168f4163 100644 --- a/src/server/stage/util/stores.ts +++ b/src/server/stage/util/stores.ts @@ -1,10 +1,10 @@ -import { get, Writable, writable } from "svelte/store" +import { get, type Writable, writable } from "svelte/store" import type { Output } from "../../../types/Output" import type { Dictionary } from "../../../types/Settings" import type { Overlays, Shows, TrimmedShow } from "../../../types/Show" import type { StageLayout } from "../../../types/Stage" import { clone } from "../../common/util/helpers" -import { __update, DeepKey, DeepNested, Inferred, Nested } from "../../common/util/stores" +import { __update, type DeepKey, type DeepNested, type Inferred, type Nested } from "../../common/util/stores" import { DEFAULT_DICTIONARY } from "./dictionary" export let dictionary: Writable<Dictionary> = writable(clone(DEFAULT_DICTIONARY)) diff --git a/svelte.config.mjs b/svelte.config.mjs index b3ba7f34d..67207a2de 100644 --- a/svelte.config.mjs +++ b/svelte.config.mjs @@ -1,11 +1,5 @@ -import sveltePreprocess from "svelte-preprocess" - -const production = process.env.NODE_ENV === "production" +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte" export default { - preprocess: sveltePreprocess({ - typescript: { - tsconfigFile: `./config/typescript/tsconfig.svelte${production ? ".prod" : ""}.json` - } - }) + preprocess: vitePreprocess() } diff --git a/vite.config.mjs b/vite.config.mjs index fb8c90da8..c6566b5eb 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -1,23 +1,18 @@ import { defineConfig } from "vite" -import { svelte } from "@sveltejs/vite-plugin-svelte" -import sveltePreprocess from "svelte-preprocess" +import { svelte, vitePreprocess } from "@sveltejs/vite-plugin-svelte" const production = process.env.NODE_ENV === "production" export default defineConfig({ plugins: [ svelte({ - preprocess: sveltePreprocess({ - typescript: { - tsconfigFile: "config/typescript/tsconfig.svelte.json" - } - }), + preprocess: vitePreprocess(), compilerOptions: { dev: !production }, onwarn: (warning, handler) => { - // disable A11y warnings - if (warning.code.startsWith("a11y-")) return + // disable A11y warnings (Svelte 5 renamed codes from a11y-* to a11y_*) + if (warning.code.startsWith("a11y-") || warning.code.startsWith("a11y_")) return handler(warning) } }) @@ -37,10 +32,12 @@ export default defineConfig({ rollupOptions: { output: { assetFileNames: (assetInfo) => { - if (assetInfo.name.endsWith(".css")) { + // Vite 8 / Rollup 4: prefer names[] (the singular .name is deprecated) + const name = assetInfo.names?.[0] ?? assetInfo.name ?? "" + if (name.endsWith(".css")) { return "bundle.css" } - return assetInfo.name + return name } } } From 9627180f7ed02b809af81b5ad4be6c818292da2e Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Wed, 10 Jun 2026 03:36:06 +0000 Subject: [PATCH 24/32] fix(svelte5): preserve pre-migration type strictness (disable strict umbrella) @tsconfig/svelte v5 turns on "strict" (incl. noImplicitAny), which the project intentionally kept off (it opts into only strictNullChecks/strictFunctionTypes/ noImplicitThis). Restoring strict:false drops svelte-check from 584 -> 78 errors (below the pre-migration ~186 baseline). Type-check only; the esbuild-based build is unaffected. --- src/frontend/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/tsconfig.json b/src/frontend/tsconfig.json index fb3d059f1..398d2cca3 100644 --- a/src/frontend/tsconfig.json +++ b/src/frontend/tsconfig.json @@ -5,7 +5,8 @@ "sourceMap": true, "target": "ESNext", "stripInternal": true, - // "strict": true, + // @tsconfig/svelte v5 enables "strict" (incl. noImplicitAny); the project intentionally opts into only the flags below, so disable the umbrella and keep the explicit opt-ins + "strict": false, // "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, From 5f096511f047fc632ac07915552dd80322ee5fdd Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Tue, 16 Jun 2026 13:33:43 +0000 Subject: [PATCH 25/32] build(svelte5): reconcile deps for standalone migration off dev MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-point the migration at the current dev baseline instead of the #3384/#3385 stack: bump svelte 5 / vite 8 / typescript 5 / svelte-check 4 only, keeping dev's electron 37, vitest, version, and all other deps untouched (no security or ESLint-9 changes — those stay their own PRs). Drop eslint-plugin-svelte3 (Svelte-3-only, hard-incompatible with Svelte 5) and its lint:svelte step + config; .svelte files stay covered by svelte-check, prettier, and stylelint. A modern svelte ESLint setup belongs with the separate ESLint 9 modernization, not this framework bump. Regenerated package-lock.json. https://claude.ai/code/session_01GnucxHgJuqNxYHbeHonfRm --- config/linting/eslint.svelte.js | 45 --------------------------------- package.json | 14 +++++----- 2 files changed, 6 insertions(+), 53 deletions(-) delete mode 100644 config/linting/eslint.svelte.js diff --git a/config/linting/eslint.svelte.js b/config/linting/eslint.svelte.js deleted file mode 100644 index 07fdf6ee2..000000000 --- a/config/linting/eslint.svelte.js +++ /dev/null @@ -1,45 +0,0 @@ -module.exports = { - env: { - browser: true, - es2021: true, - node: true, - }, - plugins: ["svelte3", "@typescript-eslint", "jsdoc", "prefer-arrow"], - overrides: [ - { - files: ["*.svelte"], - processor: "svelte3/svelte3", - }, - ], - extends: ["plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended-requiring-type-checking", "prettier"], - parser: "@typescript-eslint/parser", - parserOptions: { - project: "config/typescript/tsconfig.svelte.json", - ecmaVersion: 2021, - sourceType: "module", - extraFileExtensions: [".svelte"], - }, - settings: { - "svelte3/typescript": () => require("typescript"), - }, - rules: { - "@typescript-eslint/no-floating-promises": "off", - "@typescript-eslint/no-misused-promises": "off", - "@typescript-eslint/no-implied-eval": "off", - "@typescript-eslint/no-unused-vars": ["error", { varsIgnorePattern: "^_", argsIgnorePattern: "^_" }], - "--TEMPORARILY-DISABLED": "off", - "@typescript-eslint/restrict-plus-operands": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/naming-convention": "off", - "@typescript-eslint/restrict-template-expressions": "off", - "@typescript-eslint/no-unsafe-member-access": "off", - "@typescript-eslint/no-unsafe-assignment": "off", - "@typescript-eslint/no-unsafe-argument": "off", - "@typescript-eslint/no-unsafe-return": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unsafe-call": "off", - "@typescript-eslint/ban-ts-comment": "off", - // disable a11y warnings (not working) - // "svelte3/valid-compile": ["error", { ignoreWarnings: true }], - }, -}; // always check js config files before executing!! diff --git a/package.json b/package.json index 68019dc97..022133b47 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freeshow", - "version": "1.6.2-beta.1", + "version": "1.6.2-beta.2", "private": true, "main": "build/electron/index.js", "description": "Show song lyrics and more for free!", @@ -48,15 +48,15 @@ "pack:arm64": "electron-builder --config config/building/electron-builder-lnxarm.yaml --dir", "replace-electron": "node config/building/electron-replace-lnxarm.js", "---CHECKING---": "", - "test": "npm-run-all -s test:playwright test:format test:svelte", + "test": "npm-run-all -s test:unit test:playwright test:format test:svelte", + "test:unit": "vitest run --config config/testing/vitest.config.ts", "test:playwright": "npx playwright test --config config/testing/playwright.config.ts", "test:format": "npx prettier --config config/formatting/.prettierrc.yaml --check src scripts", "test:svelte": "svelte-check", "format:prettier": "npx prettier --config config/formatting/.prettierrc.yaml --write src scripts", - "lint": "npm-run-all -s lint:electron lint:frontend lint:svelte lint:styles", + "lint": "npm-run-all -s lint:electron lint:frontend lint:styles", "lint:electron": "eslint -c config/linting/eslint.electron.json --ext .js,.ts src/electron --fix", "lint:frontend": "eslint -c config/linting/eslint.frontend.json --ext .js,.ts src/frontend --fix", - "lint:svelte": "eslint -c config/linting/eslint.svelte.js --ext .svelte src/frontend --fix", "lint:styles": "npx stylelint 'src/frontend/**/*.{css,scss,svelte}' --config config/linting/.stylelintrc.json" }, "devDependencies": { @@ -93,7 +93,6 @@ "eslint-config-prettier": "^8.10.2", "eslint-plugin-jsdoc": "^39.9.1", "eslint-plugin-prefer-arrow": "^1.2.3", - "eslint-plugin-svelte3": "^4.0.0", "npm-run-all2": "^9.0.1", "pdfjs-dist": "^5.4.296", "playwright": "^1.56.1", @@ -111,12 +110,11 @@ "stylelint-use-logical": "^2.1.2", "svelte": "^5.56.3", "svelte-check": "^4.6.0", - "svelte-eslint-parser": "^1.8.0", - "tmp": "^0.2.7", + "tmp": "^0.2.5", "tslib": "^2.8.1", "typescript": "^5.9.3", - "typescript-eslint": "^8.61.0", "vite": "^8.0.16", + "vitest": "^2.1.9", "xml2js": "^0.6.2", "youtube-player": "^5.6.0" }, From e502591a119c1f93a12ffe45ecd323f7f321e824 Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Tue, 16 Jun 2026 13:33:43 +0000 Subject: [PATCH 26/32] fix(svelte5): build on Vite 8 (type-only imports) + restore |global transitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vite 8's bundler (rolldown) requires type-only imports to use `import type` (Svelte's isolatedModules). Dev code added since the original migration used value imports for types (Show, OutData, Item, ContextMenuItem, ProjectShowRef, DropAreas, …) — convert them (inline `type` where the module also exports values, e.g. svelte/store get + Unsubscriber, contextMenus). Also restore the output transitions broken by Svelte 4's local-by-default change: add |global to OutputTransition (in/out), the song-attribution text in Output.svelte, and ListView, plus a monotonic transitionId key in SlideContent so the outgoing slide's text isn't orphaned during the |global outro (the "snap" blocker, ref #1512). npm run build passes. svelte-check reports 71 deferred type errors (compat mode preserved — same backlog the original migration documented; not in CI). https://claude.ai/code/session_01GnucxHgJuqNxYHbeHonfRm --- src/frontend/audio/audioAnalyser.ts | 2 +- src/frontend/components/actions/ChooseEmitter.svelte | 2 +- src/frontend/components/context/ContextChild.svelte | 2 +- src/frontend/components/context/ContextItem.svelte | 2 +- src/frontend/components/edit/tools/SlideFilters.svelte | 2 +- src/frontend/components/export/projectLink.ts | 2 +- .../main/popups/DeleteDuplicatedShows.svelte | 2 +- src/frontend/components/main/popups/Import.svelte | 2 +- .../components/main/popups/export/Export.svelte | 2 +- .../components/main/popups/export/PdfExport.svelte | 2 +- src/frontend/components/output/Output.svelte | 4 ++-- .../components/output/effects/EffectOutput.svelte | 2 +- .../components/output/layers/SlideContent.svelte | 10 +++++++++- .../output/transitions/OutputTransition.svelte | 2 +- src/frontend/components/settings/tabs/Outputs.svelte | 2 +- src/frontend/components/settings/tabs/Profiles.svelte | 2 +- src/frontend/components/show/ProjectContentList.svelte | 2 +- src/frontend/components/show/focus/FocusItem.svelte | 2 +- src/frontend/components/show/media/MediaPreview.svelte | 2 +- .../components/show/ppt/PowerPointPreview.svelte | 2 +- src/frontend/components/slide/views/IconItem.svelte | 2 +- src/frontend/components/slide/views/ListView.svelte | 2 +- src/frontend/components/stage/tools/Items.svelte | 2 +- src/frontend/components/system/DropArea.svelte | 2 +- src/frontend/components/timeline/TimelineActions.ts | 2 +- src/frontend/components/timeline/TimelinePlayback.ts | 4 ++-- src/frontend/utils/searchFast.ts | 2 +- 27 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/frontend/audio/audioAnalyser.ts b/src/frontend/audio/audioAnalyser.ts index 960552a60..dc0201801 100644 --- a/src/frontend/audio/audioAnalyser.ts +++ b/src/frontend/audio/audioAnalyser.ts @@ -5,7 +5,7 @@ import { audioEffects, disabledServers, media, outputs, playingAudio, playingVid import { isOutputWindow } from "../utils/common" import { send } from "../utils/request" import { AudioAnalyserMerger } from "./audioAnalyserMerger" -import { AudioMultichannel, MultichannelInfo } from "./audioMultichannel" +import { AudioMultichannel, type MultichannelInfo } from "./audioMultichannel" import { AudioPlayer } from "./audioPlayer" import { AudioProcessor, PitchShiftNode } from "./audioProcessor" import { initializeCompressor } from "./effects/audioCompressor" diff --git a/src/frontend/components/actions/ChooseEmitter.svelte b/src/frontend/components/actions/ChooseEmitter.svelte index c55fe2fe6..75865b52f 100644 --- a/src/frontend/components/actions/ChooseEmitter.svelte +++ b/src/frontend/components/actions/ChooseEmitter.svelte @@ -11,7 +11,7 @@ import MaterialButton from "../inputs/MaterialButton.svelte" import MaterialDropdown from "../inputs/MaterialDropdown.svelte" import MaterialTextInput from "../inputs/MaterialTextInput.svelte" - import { API_emitter } from "./api" + import type { API_emitter } from "./api" import { formatData } from "./emitters" import MidiValues from "./MidiValues.svelte" diff --git a/src/frontend/components/context/ContextChild.svelte b/src/frontend/components/context/ContextChild.svelte index 32c712ebc..f00f6f68a 100644 --- a/src/frontend/components/context/ContextChild.svelte +++ b/src/frontend/components/context/ContextChild.svelte @@ -3,7 +3,7 @@ import Icon from "../helpers/Icon.svelte" import T from "../helpers/T.svelte" import ContextItem from "./ContextItem.svelte" - import { ContextMenuItem, contextMenuItems } from "./contextMenus" + import { type ContextMenuItem, contextMenuItems } from "./contextMenus" import { loadItems } from "./loadItems" export let contextElem: HTMLDivElement | null = null diff --git a/src/frontend/components/context/ContextItem.svelte b/src/frontend/components/context/ContextItem.svelte index 331cd9906..6c5ab25e9 100644 --- a/src/frontend/components/context/ContextItem.svelte +++ b/src/frontend/components/context/ContextItem.svelte @@ -9,7 +9,7 @@ import { getLayoutRef } from "../helpers/show" import { _show } from "../helpers/shows" import T from "../helpers/T.svelte" - import { ContextMenuItem, contextMenuItems } from "./contextMenus" + import { type ContextMenuItem, contextMenuItems } from "./contextMenus" import { menuClick } from "./menuClick" export let contextElem: HTMLDivElement | null = null diff --git a/src/frontend/components/edit/tools/SlideFilters.svelte b/src/frontend/components/edit/tools/SlideFilters.svelte index 50b4d8ec1..b9990837b 100644 --- a/src/frontend/components/edit/tools/SlideFilters.svelte +++ b/src/frontend/components/edit/tools/SlideFilters.svelte @@ -4,7 +4,7 @@ import { history } from "../../helpers/history" import { getLayoutRef } from "../../helpers/show" import { addFilterString } from "../scripts/textStyle" - import { EditInput2 } from "../values/boxes" + import type { EditInput2 } from "../values/boxes" import { slideFilterSections } from "../values/filters" import EditValues from "./EditValues.svelte" diff --git a/src/frontend/components/export/projectLink.ts b/src/frontend/components/export/projectLink.ts index dc812e9ae..93b02fe18 100644 --- a/src/frontend/components/export/projectLink.ts +++ b/src/frontend/components/export/projectLink.ts @@ -4,7 +4,7 @@ import { overlays, playerVideos, showsCache } from "../../stores" import { newToast } from "../../utils/common" import { loadShows } from "../helpers/setShow" import { _show } from "../helpers/shows" -import { LinkGenericItem, LinkOverlayItem, LinkShowItem, LinkSlide, LinkSlideGenericItem, LinkSlideItems, LinkSlideLine, LinkSlideMediaItem, LinkSlideTextItem, ProjectLink } from "./ProjectLinkTypes" +import type { LinkGenericItem, LinkOverlayItem, LinkShowItem, LinkSlide, LinkSlideGenericItem, LinkSlideItems, LinkSlideLine, LinkSlideMediaItem, LinkSlideTextItem, ProjectLink } from "./ProjectLinkTypes" export async function exportProjectAsData(project: Project, projectId: string): Promise<ProjectLink | null> { if (!project) return null diff --git a/src/frontend/components/main/popups/DeleteDuplicatedShows.svelte b/src/frontend/components/main/popups/DeleteDuplicatedShows.svelte index fa1abdd67..612f810b7 100644 --- a/src/frontend/components/main/popups/DeleteDuplicatedShows.svelte +++ b/src/frontend/components/main/popups/DeleteDuplicatedShows.svelte @@ -1,5 +1,5 @@ <script lang="ts"> - import { Show } from "../../../../types/Show" + import type { Show } from "../../../../types/Show" import { activePopup, popupData, shows, showsCache } from "../../../stores" import { getSlideText } from "../../edit/scripts/textStyle" import { history } from "../../helpers/history" diff --git a/src/frontend/components/main/popups/Import.svelte b/src/frontend/components/main/popups/Import.svelte index 2a2d7eabc..f774ebb2e 100644 --- a/src/frontend/components/main/popups/Import.svelte +++ b/src/frontend/components/main/popups/Import.svelte @@ -1,7 +1,7 @@ <script lang="ts"> import { tick } from "svelte" import { Main } from "../../../../types/IPC/Main" - import { Popups } from "../../../../types/Main" + import type { Popups } from "../../../../types/Main" import { importFromClipboard } from "../../../converters/importHelpers" import { sendMain } from "../../../IPC/main" import { activePopup, alertMessage, popupData } from "../../../stores" diff --git a/src/frontend/components/main/popups/export/Export.svelte b/src/frontend/components/main/popups/export/Export.svelte index b2e39f00d..9a4807b1d 100644 --- a/src/frontend/components/main/popups/export/Export.svelte +++ b/src/frontend/components/main/popups/export/Export.svelte @@ -2,7 +2,7 @@ import { EXPORT } from "../../../../../types/Channels" import { Main } from "../../../../../types/IPC/Main" import type { Project } from "../../../../../types/Projects" - import { Show } from "../../../../../types/Show" + import type { Show } from "../../../../../types/Show" import { sendMain } from "../../../../IPC/main" import { activePopup, activeProject, projects, shows, showsCache, special } from "../../../../stores" import { wait } from "../../../../utils/common" diff --git a/src/frontend/components/main/popups/export/PdfExport.svelte b/src/frontend/components/main/popups/export/PdfExport.svelte index 51b6ed22d..11e525af9 100644 --- a/src/frontend/components/main/popups/export/PdfExport.svelte +++ b/src/frontend/components/main/popups/export/PdfExport.svelte @@ -1,5 +1,5 @@ <script lang="ts"> - import { Show } from "../../../../../types/Show" + import type { Show } from "../../../../../types/Show" import { translateText } from "../../../../utils/language" import Pdf from "../../../export/Pdf.svelte" import T from "../../../helpers/T.svelte" diff --git a/src/frontend/components/output/Output.svelte b/src/frontend/components/output/Output.svelte index 63f2f1cf7..5e39cd8c0 100644 --- a/src/frontend/components/output/Output.svelte +++ b/src/frontend/components/output/Output.svelte @@ -3,7 +3,7 @@ <script lang="ts"> import { onDestroy } from "svelte" import { uid } from "uid" - import { OutData } from "../../../types/Output" + import type { OutData } from "../../../types/Output" import type { Styles } from "../../../types/Settings" import type { AnimationData, Item, LayoutRef, OutBackground, OutSlide, Slide, SlideData, Template, Overlays as TOverlays } from "../../../types/Show" import { allOutputs, colorbars, currentWindow, drawSettings, drawTool, effects, media, outputs, overlays, showsCache, styles, templates, transitionData } from "../../stores" @@ -394,7 +394,7 @@ {#if mirror} <p class="attributionString">{actualSlide.attributionString.slice(0, 135)}</p> {:else} - <p class="attributionString" transition:custom={transitions.text}>{actualSlide.attributionString.slice(0, 135)}</p> + <p class="attributionString" transition:custom|global={transitions.text}>{actualSlide.attributionString.slice(0, 135)}</p> {/if} {/if} diff --git a/src/frontend/components/output/effects/EffectOutput.svelte b/src/frontend/components/output/effects/EffectOutput.svelte index be996f092..c86235e50 100644 --- a/src/frontend/components/output/effects/EffectOutput.svelte +++ b/src/frontend/components/output/effects/EffectOutput.svelte @@ -1,5 +1,5 @@ <script lang="ts"> - import { Transition } from "../../../../types/Show" + import type { Transition } from "../../../../types/Show" import { effects } from "../../../stores" import OutputTransition from "../transitions/OutputTransition.svelte" import Effect from "./Effect.svelte" diff --git a/src/frontend/components/output/layers/SlideContent.svelte b/src/frontend/components/output/layers/SlideContent.svelte index cfcf75833..f1baec650 100644 --- a/src/frontend/components/output/layers/SlideContent.svelte +++ b/src/frontend/components/output/layers/SlideContent.svelte @@ -48,6 +48,11 @@ let currentItems: Item[] = [] let current: any = {} let show = false + // Monotonic id for the {#key} below. Keying on the boolean `show` makes the outgoing slide + // (key=true) and the incoming slide (key=true again) share an identity, so Svelte can't keep + // both alive during a |global transition — the previous slide's text gets left on screen. + // A unique id per reveal gives each transition its own identity so the outro cleans up. + let transitionId = 0 // Track items that are unchanged between slides and have no transition (to avoid redraw flicker) let persistentItems: Item[] = [] @@ -287,6 +292,9 @@ // wait until half transition duration of previous items have passed as it looks better visually timeout = setTimeout(() => { if (gen !== updateGeneration) return + // bump before revealing so the incoming instance gets a fresh key, distinct + // from the outgoing instance that is still completing its |global outro + transitionId++ show = true // wait for between to set in transition @@ -416,7 +424,7 @@ /> {:else} <!-- Transitioning item: render with animation wrapper inside {#key} --> - {#key show} + {#key transitionId} {#if show} <SlideItemTransition {preview} {transitionEnabled} {transitioningBetween} globalTransition={transition} currentSlide={current.currentSlide} {item} outSlide={current.outSlide} lines={current.lines} currentStyle={current.currentStyle} let:customSlide let:customItem let:customLines let:customOut let:transition> <Textbox diff --git a/src/frontend/components/output/transitions/OutputTransition.svelte b/src/frontend/components/output/transitions/OutputTransition.svelte index 2556d4c0a..f4e87da73 100644 --- a/src/frontend/components/output/transitions/OutputTransition.svelte +++ b/src/frontend/components/output/transitions/OutputTransition.svelte @@ -15,7 +15,7 @@ <slot /> </div> {:else} - <div class="transitioner" in:custom={inTransition || transition || {}} out:custom={outTransition || transition || {}} on:outrostart> + <div class="transitioner" in:custom|global={inTransition || transition || {}} out:custom|global={outTransition || transition || {}} on:outrostart> <slot /> </div> {/if} diff --git a/src/frontend/components/settings/tabs/Outputs.svelte b/src/frontend/components/settings/tabs/Outputs.svelte index 4455302c1..226bc4e06 100644 --- a/src/frontend/components/settings/tabs/Outputs.svelte +++ b/src/frontend/components/settings/tabs/Outputs.svelte @@ -2,7 +2,7 @@ import { onDestroy } from "svelte" import { uid } from "uid" import { BLACKMAGIC, NDI, OUTPUT } from "../../../../types/Channels" - import { Option } from "../../../../types/Main" + import type { Option } from "../../../../types/Main" import type { Output } from "../../../../types/Output" import { AudioAnalyser } from "../../../audio/audioAnalyser" import { activePage, activePopup, activeStage, activeStyle, alertMessage, currentOutputSettings, ndiData, os, outputDisplay, outputs, saved, settingsTab, stageShows, styles, toggleOutputEnabled } from "../../../stores" diff --git a/src/frontend/components/settings/tabs/Profiles.svelte b/src/frontend/components/settings/tabs/Profiles.svelte index 4f7b5b091..d65c1e6a0 100644 --- a/src/frontend/components/settings/tabs/Profiles.svelte +++ b/src/frontend/components/settings/tabs/Profiles.svelte @@ -1,6 +1,6 @@ <script lang="ts"> import type { AccessType, Profile } from "../../../../types/Main" - import { SettingsTabs } from "../../../../types/Tabs" + import type { SettingsTabs } from "../../../../types/Tabs" import { actions, actionTags, activeProfile, categories, folders, overlayCategories, profiles, selectedProfile, special, stageShows, templateCategories, variableTags, timerTags } from "../../../stores" import { newToast } from "../../../utils/common" import { translateText } from "../../../utils/language" diff --git a/src/frontend/components/show/ProjectContentList.svelte b/src/frontend/components/show/ProjectContentList.svelte index abf2b1810..92a21bfe0 100644 --- a/src/frontend/components/show/ProjectContentList.svelte +++ b/src/frontend/components/show/ProjectContentList.svelte @@ -2,7 +2,7 @@ import { createEventDispatcher, onDestroy, onMount } from "svelte" import { fade } from "svelte/transition" import type { ProjectShowRef, Tree } from "../../../types/Projects" - import { ShowType } from "../../../types/Show" + import type { ShowType } from "../../../types/Show" import { addProjectItem, addToProject, updateRecentlyAddedFiles } from "../../converters/project" import { actions, activeFocus, activePopup, activeProject, activeShow, contextActive, drawer, drawerTabsData, editingProjectTemplate, focusMode, fullColors, playerVideos, popupData, projects, projectTemplates, projectView, recentFiles, selected, shows, special } from "../../stores" import { triggerFunction } from "../../utils/common" diff --git a/src/frontend/components/show/focus/FocusItem.svelte b/src/frontend/components/show/focus/FocusItem.svelte index 872ac233a..9e31020c4 100644 --- a/src/frontend/components/show/focus/FocusItem.svelte +++ b/src/frontend/components/show/focus/FocusItem.svelte @@ -1,5 +1,5 @@ <script lang="ts"> - import { ProjectShowRef } from "../../../../types/Projects" + import type { ProjectShowRef } from "../../../../types/Projects" import { outputs, showsCache } from "../../../stores" import { getActiveOutputs } from "../../helpers/output" import AudioPreview from "../AudioPreview.svelte" diff --git a/src/frontend/components/show/media/MediaPreview.svelte b/src/frontend/components/show/media/MediaPreview.svelte index 14e090457..c3f70bb62 100644 --- a/src/frontend/components/show/media/MediaPreview.svelte +++ b/src/frontend/components/show/media/MediaPreview.svelte @@ -1,6 +1,6 @@ <script lang="ts"> import type { MediaStyle } from "../../../../types/Main" - import { ProjectShowRef } from "../../../../types/Projects" + import type { ProjectShowRef } from "../../../../types/Projects" import { activeShow, media, outLocked, outputs, styles } from "../../../stores" import { translateText } from "../../../utils/language" import Image from "../../drawer/media/Image.svelte" diff --git a/src/frontend/components/show/ppt/PowerPointPreview.svelte b/src/frontend/components/show/ppt/PowerPointPreview.svelte index 2d3dc85c6..310676f96 100644 --- a/src/frontend/components/show/ppt/PowerPointPreview.svelte +++ b/src/frontend/components/show/ppt/PowerPointPreview.svelte @@ -1,7 +1,7 @@ <script lang="ts"> import { onDestroy, onMount } from "svelte" import { Main } from "../../../../types/IPC/Main" - import { ShowRef } from "../../../../types/Projects" + import type { ShowRef } from "../../../../types/Projects" import { requestMain, sendMain } from "../../../IPC/main" import { activeShow, os, outLocked, outputs, presentationApps, presentationData, special } from "../../../stores" import { translateText } from "../../../utils/language" diff --git a/src/frontend/components/slide/views/IconItem.svelte b/src/frontend/components/slide/views/IconItem.svelte index 293a49f38..35c4ca348 100644 --- a/src/frontend/components/slide/views/IconItem.svelte +++ b/src/frontend/components/slide/views/IconItem.svelte @@ -1,5 +1,5 @@ <script lang="ts"> - import { Item } from "../../../../types/Show" + import type { Item } from "../../../../types/Show" import Icon from "../../helpers/Icon.svelte" export let item: Item diff --git a/src/frontend/components/slide/views/ListView.svelte b/src/frontend/components/slide/views/ListView.svelte index a0d5fc9ff..3bd631601 100644 --- a/src/frontend/components/slide/views/ListView.svelte +++ b/src/frontend/components/slide/views/ListView.svelte @@ -37,7 +37,7 @@ <span>{@html item.text}</span> </div> {:else} - <div class="center" transition:custom={transition}> + <div class="center" transition:custom|global={transition}> <span>{@html item.text}</span> </div> {/if} diff --git a/src/frontend/components/stage/tools/Items.svelte b/src/frontend/components/stage/tools/Items.svelte index ee02f91e3..72b2235e8 100644 --- a/src/frontend/components/stage/tools/Items.svelte +++ b/src/frontend/components/stage/tools/Items.svelte @@ -1,6 +1,6 @@ <script lang="ts"> import { uid } from "uid" - import { Item } from "../../../../types/Show" + import type { Item } from "../../../../types/Show" import type { StageItem } from "../../../../types/Stage" import { activeStage, dictionary, labelsDisabled, selected, stageShows, timers } from "../../../stores" import { translateText } from "../../../utils/language" diff --git a/src/frontend/components/system/DropArea.svelte b/src/frontend/components/system/DropArea.svelte index be218888f..00176e009 100644 --- a/src/frontend/components/system/DropArea.svelte +++ b/src/frontend/components/system/DropArea.svelte @@ -1,7 +1,7 @@ <script lang="ts"> import { selected } from "../../stores" import { mediaExtensions } from "../../values/extensions" - import { DropAreas, ondrop, validateDrop } from "../helpers/drop" + import { type DropAreas, ondrop, validateDrop } from "../helpers/drop" import { deselect } from "../helpers/select" import T from "../helpers/T.svelte" diff --git a/src/frontend/components/timeline/TimelineActions.ts b/src/frontend/components/timeline/TimelineActions.ts index 3a2b7e39c..eb29cb2b4 100644 --- a/src/frontend/components/timeline/TimelineActions.ts +++ b/src/frontend/components/timeline/TimelineActions.ts @@ -1,4 +1,4 @@ -import { get, Unsubscriber } from "svelte/store" +import { get, type Unsubscriber } from "svelte/store" import { uid } from "uid" import type { TimelineAction } from "../../../types/Show" import { activeEdit, activeProject, activeShow, projects, selected, shows, showsCache } from "../../stores" diff --git a/src/frontend/components/timeline/TimelinePlayback.ts b/src/frontend/components/timeline/TimelinePlayback.ts index 743493341..008e2dc22 100644 --- a/src/frontend/components/timeline/TimelinePlayback.ts +++ b/src/frontend/components/timeline/TimelinePlayback.ts @@ -1,4 +1,4 @@ -import { get, Unsubscriber } from "svelte/store" +import { get, type Unsubscriber } from "svelte/store" import { Main } from "../../../types/IPC/Main" import type { TimelineAction } from "../../../types/Show" import { sendMain } from "../../IPC/main" @@ -13,7 +13,7 @@ import { loadShows } from "../helpers/setShow" import { _show } from "../helpers/shows" import { ShowTimeline } from "./ShowTimeline" import { SlideTimeline } from "./SlideTimeline" -import { TimelineType } from "./TimelineActions" +import type { TimelineType } from "./TimelineActions" import { startListeningLTC, stopListeningLTC } from "./timecode" import { getProjectShowDurations } from "./timeline" diff --git a/src/frontend/utils/searchFast.ts b/src/frontend/utils/searchFast.ts index 4bd63d6ad..b996459a9 100644 --- a/src/frontend/utils/searchFast.ts +++ b/src/frontend/utils/searchFast.ts @@ -1,5 +1,5 @@ import { get } from "svelte/store" -import { ShowList } from "../../types/Show" +import type { ShowList } from "../../types/Show" import { categories, drawerTabsData, textCache } from "../stores" import { formatSearch, tokenize } from "./search" import { sortObjectNumbers } from "../components/helpers/array" From 84030aa5d79d3c6192ec79a511e2f16063efbfc0 Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Tue, 16 Jun 2026 23:47:02 +0000 Subject: [PATCH 27/32] =?UTF-8?q?fix(svelte5):=20bump=20vitest=202?= =?UTF-8?q?=E2=86=924=20so=20`npm=20ci`=20installs=20on=20vite=208?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vite 8 requires esbuild ^0.27 || ^0.28, but vitest@2.1.9 pulls vite@5 → esbuild@0.21.5, which npm hoists and then can't satisfy vite 8 — so `npm ci` (what upstream CI runs) failed to install even though `npm install` and the build tolerated it. vitest@4 peer-supports vite ^8, removing the second vite lineage. Regenerated a clean, ci-consistent lockfile. Verified: `npm ci` exit 0, `npm run build` 0 errors, unit tests pass on vitest 4, svelte-check 71 (compat mode, unchanged). https://claude.ai/code/session_01GnucxHgJuqNxYHbeHonfRm --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 022133b47..6dce462b7 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "tslib": "^2.8.1", "typescript": "^5.9.3", "vite": "^8.0.16", - "vitest": "^2.1.9", + "vitest": "^4.1.9", "xml2js": "^0.6.2", "youtube-player": "^5.6.0" }, From 172eac43c28408145c433c98ac35357b343aca65 Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Wed, 17 Jun 2026 00:07:55 +0000 Subject: [PATCH 28/32] fix(svelte5): resolve remote logo via asset import (Vite 8 iife import.meta) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The remote companion built `freeshowLogo` with `new URL("…webp", import.meta.url)`. Vite 8 (rolldown) replaces `import.meta` with {} in iife output (Rollup/Vite 4 polyfilled it), so the base compiled to the string "undefined" and `new URL(dataURI, "undefined")` throws "Invalid URL" at runtime — breaking the RemoteShow login screen, not just the logo. Import the asset instead; Vite inlines the 378-byte webp as a data URI with no import.meta. Added a `*.webp` ambient module declaration so svelte-check stays at 71. Verified: 0 EMPTY_IMPORT_META from our code, build 0 errors, svelte-check 71, npm ci passes. https://claude.ai/code/session_01GnucxHgJuqNxYHbeHonfRm --- src/server/remote/components/Auth.svelte | 6 ++++-- src/server/remote/global.d.ts | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/server/remote/components/Auth.svelte b/src/server/remote/components/Auth.svelte index c831df08b..26b1e3fb0 100644 --- a/src/server/remote/components/Auth.svelte +++ b/src/server/remote/components/Auth.svelte @@ -3,8 +3,10 @@ import { translate } from "../util/helpers" import { send } from "../util/socket" import { _get, _update, dictionary, password } from "../util/stores" - - const freeshowLogo = new URL("../../../../public/import-logos/freeshow.webp", import.meta.url).href + // import the asset directly so Vite resolves it (small → inlined data URI); avoids + // `import.meta.url`, which Vite 8's iife companion build replaces with {} and would + // throw "Invalid URL" at runtime. + import freeshowLogo from "../../../../public/import-logos/freeshow.webp" function submit() { const password = _get("password").stored diff --git a/src/server/remote/global.d.ts b/src/server/remote/global.d.ts index 1a25456a2..3e90da8a8 100644 --- a/src/server/remote/global.d.ts +++ b/src/server/remote/global.d.ts @@ -1 +1,7 @@ /// <reference types="svelte" /> + +// Vite resolves asset imports (small files inline as a data URI) to a URL string. +declare module "*.webp" { + const url: string + export default url +} From a5f2efc87ad1a4364f403812f1ba09378ad2ab40 Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Wed, 17 Jun 2026 14:54:08 +0000 Subject: [PATCH 29/32] build(svelte5): rebase onto dev (post-#3384), regenerate lockfile, address review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reconstructed on current dev so the PR is just the framework migration: - regenerate package-lock.json against dev's lockfile; only the svelte/vite/ vitest/ts subtrees move, every unrelated dep stays at dev's pin - package.json: tmp ^0.2.7 (match dev's override) — was pulled to 0.2.5 during reconcile - config/building/vite.config.servers.mjs: drop the legacy a11y- check, keep only a11y_ Dropped per @vassbo's review (not re-applied during reconstruction): the ~200 self-closing conversions (kept />), BUILDING.md / ACCESSIBILITY.md / Dockerfile / .dockerignore, the a11y code pass, and the Node<26 pin/.nvmrc. eslint.svelte.js stays removed (Svelte-3 eslint-plugin-svelte3 blocks install on Svelte 5). The |global transition fix + SlideContent transitionId keying are preserved. npm ci, npm run build, and unit tests (37, vitest 4) all pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GnucxHgJuqNxYHbeHonfRm --- config/building/vite.config.servers.mjs | 4 +- package-lock.json | 3927 +++++++++-------------- package.json | 2 +- 3 files changed, 1507 insertions(+), 2426 deletions(-) diff --git a/config/building/vite.config.servers.mjs b/config/building/vite.config.servers.mjs index 77770aadc..36f4acce8 100644 --- a/config/building/vite.config.servers.mjs +++ b/config/building/vite.config.servers.mjs @@ -132,8 +132,8 @@ export default defineConfig({ dev: !production, }, onwarn: (warning, handler) => { - // disable A11y warnings (Svelte 5 renamed codes from a11y-* to a11y_*) - if (warning.code.startsWith('a11y-') || warning.code.startsWith('a11y_')) return + // disable A11y warnings + if (warning.code.startsWith('a11y_')) return handler(warning) }, }), diff --git a/package-lock.json b/package-lock.json index eabd7b02a..54c4f59c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,9 +52,9 @@ "@rollup/plugin-terser": "^1.0.0", "@rollup/plugin-typescript": "^12.1.4", "@soundtouchjs/audio-worklet": "^1.0.10", - "@sveltejs/vite-plugin-svelte": "^2.5.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", "@tanstack/pacer": "^0.14.0", - "@tsconfig/svelte": "^2.0.1", + "@tsconfig/svelte": "^5.0.0", "@types/better-sqlite3": "^7.6.13", "@types/ebml": "^3.0.5", "@types/exif": "^0.6.5", @@ -77,7 +77,6 @@ "eslint-config-prettier": "^8.10.2", "eslint-plugin-jsdoc": "^39.9.1", "eslint-plugin-prefer-arrow": "^1.2.3", - "eslint-plugin-svelte3": "^4.0.0", "npm-run-all2": "^9.0.1", "pdfjs-dist": "^5.4.296", "playwright": "^1.56.1", @@ -89,20 +88,17 @@ "rollup-plugin-css-only": "^4.5.2", "rollup-plugin-livereload": "^2.0.5", "rollup-plugin-serve": "^3.0.0", - "rollup-plugin-svelte": "^7.2.2", "socket.io-client": "^4.8.1", "stylelint": "^16.23.1", "stylelint-config-html": "^1.1.0", "stylelint-use-logical": "^2.1.2", - "svelte": "^3.59.2", - "svelte-check": "^2.10.3", - "svelte-inspector": "github:vassbo/svelte-inspector#78307db", - "svelte-preprocess": "^4.10.7", + "svelte": "^5.56.3", + "svelte-check": "^4.6.0", "tmp": "^0.2.7", "tslib": "^2.8.1", - "typescript": "^4.9.5", - "vite": "^4.5.14", - "vitest": "^2.1.9", + "typescript": "^5.9.3", + "vite": "^8.0.16", + "vitest": "^4.1.9", "xml2js": "^0.6.2", "youtube-player": "^5.6.0" }, @@ -663,9 +659,9 @@ } }, "node_modules/@electron/windows-sign/node_modules/fs-extra": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", - "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", "dev": true, "license": "MIT", "optional": true, @@ -679,763 +675,419 @@ "node": ">=14.14" } }, - "node_modules/@es-joy/jsdoccomment": { - "version": "0.36.1", + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "comment-parser": "1.3.1", - "esquery": "^1.4.0", - "jsdoc-type-pratt-parser": "~3.1.0" - }, - "engines": { - "node": "^14 || ^16 || ^17 || ^18 || ^19" + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "cpu": [ - "arm" - ], + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "cpu": [ - "arm64" - ], + "node_modules/@es-joy/jsdoccomment": { + "version": "0.36.1", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "comment-parser": "1.3.1", + "esquery": "^1.4.0", + "jsdoc-type-pratt-parser": "~3.1.0" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || ^17 || ^18 || ^19" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "cpu": [ - "x64" - ], + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "cpu": [ - "arm64" - ], + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": ">=12" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "cpu": [ - "x64" - ], + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "cpu": [ - "arm64" - ], + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">=12" + "node": "*" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "cpu": [ - "x64" - ], + "node_modules/@eslint/js": { + "version": "8.57.1", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@googleapis/drive": { + "version": "8.16.0", + "license": "Apache-2.0", + "dependencies": { + "googleapis-common": "^7.0.0" + }, "engines": { - "node": ">=12" + "node": ">=12.0.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "cpu": [ - "arm64" - ], + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, "engines": { - "node": ">=12" + "node": ">=10.10.0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "cpu": [ - "ia32" - ], + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">=12" + "node": "*" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "cpu": [ - "loong64" - ], + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", "engines": { - "node": ">=12" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "cpu": [ - "mips64el" - ], + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, "engines": { "node": ">=12" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "cpu": [ - "ppc64" - ], + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "cpu": [ - "riscv64" - ], + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "cpu": [ - "s390x" - ], + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "cpu": [ - "x64" - ], + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "ansi-regex": "^6.0.1" + }, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "cpu": [ - "x64" - ], + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "cpu": [ - "x64" - ], + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, "engines": { - "node": ">=12" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "cpu": [ - "x64" - ], + "node_modules/@isaacs/fs-minipass/node_modules/minipass": { + "version": "7.1.2", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], + "license": "ISC", "engines": { - "node": ">=12" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "cpu": [ - "arm64" - ], + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "cpu": [ - "ia32" - ], + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">=12" + "node": ">=6.0.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", "dev": true, "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } + "license": "MIT" }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/@keyv/serialize": { + "version": "1.1.0", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } + "license": "MIT" }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "dev": true, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "license": "MIT" + }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=8" } }, - "node_modules/@googleapis/drive": { - "version": "8.16.0", + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], "license": "Apache-2.0", "dependencies": { - "googleapis-common": "^7.0.0" + "cross-spawn": "^7.0.1" }, "engines": { - "node": ">=12.0.0" + "node": ">= 12.13.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" }, "engines": { - "node": ">=10.10.0" + "node": ">= 10.0.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/@mapbox/node-pre-gyp": { + "version": "2.0.0", "dev": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@isaacs/fs-minipass/node_modules/minipass": { - "version": "7.1.2", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.30", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@keyv/serialize": { - "version": "1.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "license": "MIT" - }, - "node_modules/@lukeed/csprng": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@malept/cross-spawn-promise": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", - "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/malept" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" - } - ], - "license": "Apache-2.0", - "dependencies": { - "cross-spawn": "^7.0.1" - }, - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/@malept/flatpak-bundler": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", - "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "fs-extra": "^9.0.0", - "lodash": "^4.17.15", - "tmp-promise": "^3.0.2" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "2.0.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "consola": "^3.2.3", - "detect-libc": "^2.0.0", - "https-proxy-agent": "^7.0.5", - "node-fetch": "^2.6.7", - "nopt": "^8.0.0", - "semver": "^7.5.3", - "tar": "^7.4.0" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" + "consola": "^3.2.3", + "detect-libc": "^2.0.0", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^2.6.7", + "nopt": "^8.0.0", + "semver": "^7.5.3", + "tar": "^7.4.0" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" }, "engines": { "node": ">=18" @@ -1793,8 +1445,27 @@ "url": "https://github.com/sponsors/Brooooooklyn" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -2365,6 +2036,16 @@ "@opentelemetry/api": "^1.1.0" } }, + "node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2454,6 +2135,270 @@ "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", "license": "BSD-3-Clause" }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/plugin-commonjs": { "version": "28.0.6", "dev": true, @@ -3180,41 +3125,51 @@ "dev": true, "license": "LGPL-2.1" }, - "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "2.5.3", + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.10.tgz", + "integrity": "sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==", "dev": true, "license": "MIT", - "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^1.0.4", - "debug": "^4.3.4", - "deepmerge": "^4.3.1", - "kleur": "^4.1.5", - "magic-string": "^0.30.3", - "svelte-hmr": "^0.15.3", - "vitefu": "^0.2.4" - }, - "engines": { - "node": "^14.18.0 || >= 16" - }, "peerDependencies": { - "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0-next.0", - "vite": "^4.0.0" + "acorn": "^8.9.0" } }, - "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "1.0.4", + "node_modules/@sveltejs/load-config": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@sveltejs/load-config/-/load-config-0.1.1.tgz", + "integrity": "sha512-BXXm+VOH/9X4N7Dd1iZ2MqA1h7M+9i2noI8QYuLDY8QcN2WHYn7D/VK/+IJNfcAmRw7ACNJ538UT9GXIhnBTiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.1.2.tgz", + "integrity": "sha512-DrUBA2UXRfDmUX/ZTiEopd3X40yavsJF1FX2RygcuIScHL7o5YX1fMvoYnDhjeJQC4weCOklirpNWlcb2NiSeA==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.3.4" + "deepmerge": "^4.3.1", + "magic-string": "^0.30.21", + "obug": "^2.1.0", + "vitefu": "^1.1.2" }, "engines": { - "node": "^14.18.0 || >= 16" + "node": "^20.19 || ^22.12 || >=24" }, "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^2.2.0", - "svelte": "^3.54.0 || ^4.0.0", - "vite": "^4.0.0" + "svelte": "^5.46.4", + "vite": "^8.0.0-beta.7 || ^8.0.0" } }, "node_modules/@szmarczak/http-timer": { @@ -3257,10 +3212,23 @@ "license": "MIT" }, "node_modules/@tsconfig/svelte": { - "version": "2.0.1", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.8.tgz", + "integrity": "sha512-UkNnw1/oFEfecR8ypyHIQuWYdkPvHiwcQ78sh+ymIiYoF+uc5H1UBetbjyqT+vgGJ3qQN6nhucJviX6HesWtKQ==", "dev": true, "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/better-sqlite3": { "version": "7.6.13", "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", @@ -3291,11 +3259,22 @@ "@types/responselike": "^1.0.0" } }, - "node_modules/@types/connect": { - "version": "3.4.38", + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "license": "MIT", + "dependencies": { + "@types/node": "*" } }, "node_modules/@types/cors": { @@ -3315,6 +3294,13 @@ "@types/ms": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ebml": { "version": "3.0.5", "dev": true, @@ -3472,11 +3458,6 @@ "xmlbuilder": ">=11.0.1" } }, - "node_modules/@types/pug": { - "version": "2.0.10", - "dev": true, - "license": "MIT" - }, "node_modules/@types/qs": { "version": "6.14.0", "dev": true, @@ -3500,14 +3481,6 @@ "@types/node": "*" } }, - "node_modules/@types/sass": { - "version": "1.43.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/semver": { "version": "7.7.0", "dev": true, @@ -3546,6 +3519,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/verror": { "version": "1.10.11", "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", @@ -3657,6 +3637,20 @@ } } }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/scope-manager": { "version": "5.62.0", "dev": true, @@ -3673,6 +3667,20 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/type-utils": { "version": "5.62.0", "dev": true, @@ -3700,11 +3708,15 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.62.0", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.1.tgz", + "integrity": "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -3737,6 +3749,20 @@ } } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/utils": { "version": "5.62.0", "dev": true, @@ -3762,6 +3788,20 @@ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/visitor-keys": { "version": "5.62.0", "dev": true, @@ -3778,6 +3818,20 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "dev": true, @@ -3793,86 +3847,123 @@ } }, "node_modules/@vitest/expect": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", - "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz", + "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", + "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.9", - "@vitest/utils": "2.1.9", - "chai": "^5.1.2", - "tinyrainbow": "^1.2.0" + "@vitest/spy": "4.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" }, "funding": { "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", - "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", + "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", - "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz", + "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.9", - "pathe": "^1.1.2" + "@vitest/utils": "4.1.9", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", - "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz", + "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.9", - "magic-string": "^0.30.12", - "pathe": "^1.1.2" + "@vitest/pretty-format": "4.1.9", + "@vitest/utils": "4.1.9", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", - "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz", + "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", "dev": true, "license": "MIT", - "dependencies": { - "tinyspy": "^3.0.2" - }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", - "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz", + "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.9", - "loupe": "^3.1.2", - "tinyrainbow": "^1.2.0" + "@vitest/pretty-format": "4.1.9", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -4350,6 +4441,16 @@ "version": "2.0.1", "license": "Python-2.0" }, + "node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "license": "MIT" @@ -4456,6 +4557,16 @@ "proxy-from-env": "^2.1.0" } }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/balanced-match": { "version": "2.0.0", "dev": true, @@ -4788,16 +4899,6 @@ "node": ">= 0.8" } }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/cacache": { "version": "19.0.1", "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", @@ -5033,18 +5134,11 @@ } }, "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, "engines": { "node": ">=18" } @@ -5063,16 +5157,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/check-error": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, "node_modules/chokidar": { "version": "3.6.0", "dev": true, @@ -5152,89 +5236,6 @@ "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", "license": "MIT" }, - "node_modules/clap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", - "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^1.1.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clap/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clap/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clap/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clap/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/clap/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clap/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -5315,6 +5316,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "license": "MIT", @@ -5358,16 +5369,6 @@ "node": ">= 0.8" } }, - "node_modules/command-join": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/command-join/-/command-join-2.0.0.tgz", - "integrity": "sha512-3wRmSkL303bNoiJeNbGfyAi3dx8ZohEce158hUCoxtay8F7bYthV/5paxpIc3PqVp35VFwqp75GpkVi+XINfKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/commander": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", @@ -5476,6 +5477,13 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cookie": { "version": "0.7.1", "license": "MIT", @@ -5739,16 +5747,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -5853,14 +5851,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/detect-indent": { - "version": "6.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/detect-libc": { "version": "2.0.4", "license": "Apache-2.0", @@ -5874,6 +5864,13 @@ "license": "MIT", "optional": true }, + "node_modules/devalue": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", + "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", + "dev": true, + "license": "MIT" + }, "node_modules/dir-compare": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", @@ -6517,9 +6514,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", "dev": true, "license": "MIT" }, @@ -6552,49 +6549,6 @@ "license": "MIT", "optional": true }, - "node_modules/es6-promise": { - "version": "3.3.1", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -6713,15 +6667,6 @@ "eslint": ">=2.0.0" } }, - "node_modules/eslint-plugin-svelte3": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=8.0.0", - "svelte": "^3.2.0" - } - }, "node_modules/eslint-scope": { "version": "5.1.1", "dev": true, @@ -6781,6 +6726,13 @@ "node": "*" } }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, + "license": "MIT" + }, "node_modules/espree": { "version": "9.6.1", "dev": true, @@ -6816,6 +6768,24 @@ "node": ">=4.0" } }, + "node_modules/esrap": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.11.tgz", + "integrity": "sha512-gPdx+I+BjYEinNMQaBXFjbaJVyoPMU4ZODg5mE+M4DqVG9VusAVHHjcBX+zqyITlI0DIARwDMMzZwAWj36dRoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "peerDependencies": { + "@typescript-eslint/types": "^8.2.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/types": { + "optional": true + } + } + }, "node_modules/esrecurse": { "version": "4.3.0", "dev": true, @@ -8062,29 +8032,6 @@ "node": ">=14.0.0" } }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/has-flag": { "version": "4.0.0", "license": "MIT", @@ -8188,6 +8135,8 @@ }, "node_modules/htmlparser2": { "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", @@ -8623,6 +8572,8 @@ }, "node_modules/js-tokens": { "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", "dev": true, "license": "MIT", "peer": true @@ -8755,14 +8706,6 @@ "node": ">=0.10.0" } }, - "node_modules/kleur": { - "version": "4.1.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/known-css-properties": { "version": "0.37.0", "dev": true, @@ -8820,6 +8763,267 @@ "node": ">=18" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "dev": true, @@ -8872,6 +9076,13 @@ "dev": true, "license": "MIT" }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "dev": true, @@ -8945,13 +9156,6 @@ "version": "5.3.2", "license": "Apache-2.0" }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lowercase-keys": { "version": "2.0.0", "dev": true, @@ -8985,7 +9189,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.18", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9214,14 +9420,6 @@ "node": ">=4" } }, - "node_modules/min-indent": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/minimatch": { "version": "10.2.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", @@ -9468,7 +9666,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.11", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -9899,6 +10099,20 @@ "node": ">= 0.4" } }, + "node_modules/obug": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", + "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "license": "MIT", @@ -9936,25 +10150,6 @@ "node": ">=6" } }, - "node_modules/open-in-editor": { - "name": "@generalov/open-in-editor", - "version": "2.3.0", - "resolved": "git+ssh://git@github.com/generalov/open-in-editor.git#ce1e8cb2fef76593379737077f61b9d89f39e96c", - "dev": true, - "license": "MIT", - "dependencies": { - "clap": "^1.1.3", - "command-join": "2.0.0", - "os-homedir": "~1.0.2", - "shell-quote": "1.6.1" - }, - "bin": { - "oe": "bin/oe" - }, - "engines": { - "node": ">= 4.0" - } - }, "node_modules/opener": { "version": "1.5.2", "dev": true, @@ -10021,16 +10216,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/osc-js": { "version": "2.4.1", "license": "MIT", @@ -10220,22 +10405,12 @@ } }, "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, "node_modules/pause-stream": { "version": "0.0.11", "license": [ @@ -10463,9 +10638,9 @@ } }, "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -10483,7 +10658,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -10492,7 +10667,9 @@ } }, "node_modules/postcss-html": { - "version": "1.8.0", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-1.8.1.tgz", + "integrity": "sha512-OLF6P7qctfAWayOhLpcVnTGqVeJzu2W3WpIYelfz2+JV5oGxfkcEvweN9U4XpeqE0P98dcD9ssusGwlF0TK0uQ==", "dev": true, "license": "MIT", "peer": true, @@ -10513,6 +10690,8 @@ }, "node_modules/postcss-safe-parser": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", + "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", "dev": true, "license": "MIT", "peer": true, @@ -11138,14 +11317,6 @@ "node": ">=4" } }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/responselike": { "version": "2.0.1", "dev": true, @@ -11224,6 +11395,40 @@ "node": ">=8.0" } }, + "node_modules/rolldown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, "node_modules/rollup": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", @@ -11387,47 +11592,6 @@ "node": ">=16" } }, - "node_modules/rollup-plugin-svelte": { - "version": "7.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^4.1.0", - "resolve.exports": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "rollup": ">=2.0.0", - "svelte": ">=3.5.0" - } - }, - "node_modules/rollup-plugin-svelte/node_modules/@rollup/pluginutils": { - "version": "4.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "estree-walker": "^2.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/rollup-plugin-svelte/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "funding": [ @@ -11486,39 +11650,6 @@ "version": "2.0.1", "license": "MIT" }, - "node_modules/sander": { - "version": "0.5.1", - "dev": true, - "license": "MIT", - "dependencies": { - "es6-promise": "^3.1.2", - "graceful-fs": "^4.1.3", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.2" - } - }, - "node_modules/sander/node_modules/mkdirp": { - "version": "0.5.6", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/sander/node_modules/rimraf": { - "version": "2.7.1", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/sanitize-filename": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", @@ -12078,20 +12209,6 @@ "node": ">= 14" } }, - "node_modules/sorcery": { - "version": "0.10.0", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "^0.2.5", - "minimist": "^1.2.0", - "sander": "^0.5.0", - "sourcemap-codec": "^1.3.0" - }, - "bin": { - "sorcery": "bin/index.js" - } - }, "node_modules/source-map": { "version": "0.6.1", "dev": true, @@ -12117,11 +12234,6 @@ "source-map": "^0.6.0" } }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "dev": true, - "license": "MIT" - }, "node_modules/spdx-exceptions": { "version": "2.5.0", "dev": true, @@ -12205,9 +12317,9 @@ } }, "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", "dev": true, "license": "MIT" }, @@ -12288,17 +12400,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-indent": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "dev": true, @@ -12539,125 +12640,96 @@ } }, "node_modules/svelte": { - "version": "3.59.2", + "version": "5.56.3", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.3.tgz", + "integrity": "sha512-w7JvrM5IFl5cmfbY0TLik9o7mjRUJmRMhOR51tBPu708Gr/MjbGs7VnJnr/B0CaXeI4vtnOh7RKxDr0cwhMdDA==", "dev": true, "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.10", + "@types/estree": "^1.0.5", + "@types/trusted-types": "^2.0.7", + "acorn": "^8.12.1", + "aria-query": "5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.8.1", + "esm-env": "^1.2.1", + "esrap": "^2.2.11", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, "engines": { - "node": ">= 8" + "node": ">=18" } }, "node_modules/svelte-check": { - "version": "2.10.3", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.6.0.tgz", + "integrity": "sha512-KhVnDFDSid57mmZtHz8gfW8AAGylOZ0vPnOIzVmAL+urzwK8sBYXRss953gD8T0OdgAQ11mdWhE6uadmtOz8TQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.9", - "chokidar": "^3.4.1", - "fast-glob": "^3.2.7", - "import-fresh": "^3.2.1", + "@jridgewell/trace-mapping": "^0.3.25", + "@sveltejs/load-config": "0.1.1", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", "picocolors": "^1.0.0", - "sade": "^1.7.4", - "svelte-preprocess": "^4.0.0", - "typescript": "*" + "sade": "^1.7.4" }, "bin": { "svelte-check": "bin/svelte-check" }, - "peerDependencies": { - "svelte": "^3.24.0" - } - }, - "node_modules/svelte-hmr": { - "version": "0.15.3", - "dev": true, - "license": "ISC", "engines": { - "node": "^12.20 || ^14.13.1 || >= 16" + "node": ">= 18.0.0" }, "peerDependencies": { - "svelte": "^3.19.0 || ^4.0.0" + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" } }, - "node_modules/svelte-inspector": { - "version": "0.0.3", - "resolved": "git+ssh://git@github.com/vassbo/svelte-inspector.git#78307db00df8b5358c62610c0724c72cbd7b1378", + "node_modules/svelte-check/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", "dependencies": { - "open-in-editor": "generalov/open-in-editor#ce1e8cb" + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/svelte-preprocess": { - "version": "4.10.7", + "node_modules/svelte-check/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "dependencies": { - "@types/pug": "^2.0.4", - "@types/sass": "^1.16.0", - "detect-indent": "^6.0.0", - "magic-string": "^0.25.7", - "sorcery": "^0.10.0", - "strip-indent": "^3.0.0" - }, "engines": { - "node": ">= 9.11.2" - }, - "peerDependencies": { - "@babel/core": "^7.10.2", - "coffeescript": "^2.5.1", - "less": "^3.11.3 || ^4.0.0", - "postcss": "^7 || ^8", - "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0", - "pug": "^3.0.0", - "sass": "^1.26.8", - "stylus": "^0.55.0", - "sugarss": "^2.0.0", - "svelte": "^3.23.0", - "typescript": "^3.9.5 || ^4.0.0" + "node": ">= 14.18.0" }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "coffeescript": { - "optional": true - }, - "less": { - "optional": true - }, - "node-sass": { - "optional": true - }, - "postcss": { - "optional": true - }, - "postcss-load-config": { - "optional": true - }, - "pug": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "typescript": { - "optional": true - } + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "node_modules/svelte-preprocess/node_modules/magic-string": { - "version": "0.25.9", + "node_modules/svelte/node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", "dev": true, "license": "MIT", "dependencies": { - "sourcemap-codec": "^1.4.8" + "@types/estree": "^1.0.6" } }, "node_modules/svg-tags": { @@ -12920,21 +12992,24 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -12943,30 +13018,10 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, "node_modules/tinyrainbow": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", - "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", - "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", "engines": { @@ -13126,7 +13181,9 @@ } }, "node_modules/typescript": { - "version": "4.9.5", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -13134,7 +13191,7 @@ "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/uid": { @@ -13173,1229 +13230,196 @@ "node_modules/unique-filename": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", - "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/unique-slug": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", - "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/upath": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", - "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", - "license": "MIT", - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-template": { - "version": "2.0.8", - "license": "BSD" - }, - "node_modules/utf8-byte-length": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", - "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", - "dev": true, - "license": "(WTFPL OR MIT)" - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "license": "MIT" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "9.0.1", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/verror": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", - "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/vite": { - "version": "4.5.14", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.27", - "rollup": "^3.27.1" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "@types/node": ">= 14", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", - "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.7", - "es-module-lexer": "^1.5.4", - "pathe": "^1.1.2", - "vite": "^5.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vite-node/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/vite-node/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/vite-node/node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/rollup": { - "version": "3.30.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.30.0.tgz", - "integrity": "sha512-kQvGasUgN+AlWGliFn2POSajRQEsULVYFGTvOZmK06d7vCD+YhZztt70kGk3qaeAXeWYL5eO7zx+rAubBc55eA==", - "dev": true, - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/vitefu": { - "version": "0.2.5", - "dev": true, - "license": "MIT", - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/vitest": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", - "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "2.1.9", - "@vitest/mocker": "2.1.9", - "@vitest/pretty-format": "^2.1.9", - "@vitest/runner": "2.1.9", - "@vitest/snapshot": "2.1.9", - "@vitest/spy": "2.1.9", - "@vitest/utils": "2.1.9", - "chai": "^5.1.2", - "debug": "^4.3.7", - "expect-type": "^1.1.0", - "magic-string": "^0.30.12", - "pathe": "^1.1.2", - "std-env": "^3.8.0", - "tinybench": "^2.9.0", - "tinyexec": "^0.3.1", - "tinypool": "^1.0.1", - "tinyrainbow": "^1.2.0", - "vite": "^5.0.0", - "vite-node": "2.1.9", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.9", - "@vitest/ui": "2.1.9", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/vitest/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, "engines": { - "node": ">=12" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], + "node_modules/unique-slug": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, "engines": { - "node": ">=12" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/vitest/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, + "node_modules/universalify": { + "version": "2.0.1", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=12" + "node": ">= 10.0.0" } }, - "node_modules/vitest/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/unpipe": { + "version": "1.0.0", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=12" + "node": ">= 0.8" } }, - "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], "engines": { - "node": ">=12" + "node": ">=4", + "yarn": "*" } }, - "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], + "node_modules/uri-js": { + "version": "4.4.1", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" } }, - "node_modules/vitest/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], + "node_modules/url-template": { + "version": "2.0.8", + "license": "BSD" + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], "engines": { - "node": ">=12" + "node": ">= 0.4.0" } }, - "node_modules/vitest/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" + "node_modules/uuid": { + "version": "9.0.1", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" ], - "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/vitest/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, + "node_modules/vary": { + "version": "1.1.2", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">=12" + "node": ">= 0.8" } }, - "node_modules/vitest/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, "engines": { - "node": ">=12" + "node": ">=0.6.0" } }, - "node_modules/vitest/node_modules/@vitest/mocker": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", - "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "node_modules/vite": { + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.9", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.12" + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" }, "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0" + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { - "msw": { + "@types/node": { "optional": true }, - "vite": { + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { "optional": true } } }, - "node_modules/vitest/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/vitest/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/vitest/node_modules/fsevents": { + "node_modules/vite/node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", @@ -14410,63 +13434,113 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/vitest/node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", "dev": true, "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz", + "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.9", + "@vitest/mocker": "4.1.9", + "@vitest/pretty-format": "4.1.9", + "@vitest/runner": "4.1.9", + "@vitest/snapshot": "4.1.9", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" }, "bin": { - "vite": "bin/vite.js" + "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" + "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.9", + "@vitest/browser-preview": "4.1.9", + "@vitest/browser-webdriverio": "4.1.9", + "@vitest/coverage-istanbul": "4.1.9", + "@vitest/coverage-v8": "4.1.9", + "@vitest/ui": "4.1.9", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, "@types/node": { "optional": true }, - "less": { + "@vitest/browser-playwright": { "optional": true }, - "lightningcss": { + "@vitest/browser-preview": { "optional": true }, - "sass": { + "@vitest/browser-webdriverio": { "optional": true }, - "sass-embedded": { + "@vitest/coverage-istanbul": { "optional": true }, - "stylus": { + "@vitest/coverage-v8": { "optional": true }, - "sugarss": { + "@vitest/ui": { "optional": true }, - "terser": { + "happy-dom": { + "optional": true + }, + "jsdom": { "optional": true + }, + "vite": { + "optional": false } } }, @@ -14816,6 +13890,13 @@ "load-script": "^1.0.0", "sister": "^3.0.0" } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "dev": true, + "license": "MIT" } } } diff --git a/package.json b/package.json index 6dce462b7..0d8a75440 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "stylelint-use-logical": "^2.1.2", "svelte": "^5.56.3", "svelte-check": "^4.6.0", - "tmp": "^0.2.5", + "tmp": "^0.2.7", "tslib": "^2.8.1", "typescript": "^5.9.3", "vite": "^8.0.16", From 6b287beca5f399e819d9b348e0ad9979c0a73e14 Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Wed, 17 Jun 2026 15:16:12 +0000 Subject: [PATCH 30/32] fix(svelte5): drop legacy a11y- check in vite.config.mjs Match config/building/vite.config.servers.mjs (already cleaned per review): Svelte 5 only emits a11y_* warning codes, so the a11y- branch is dead. Keeps the two onwarn filters consistent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GnucxHgJuqNxYHbeHonfRm --- vite.config.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vite.config.mjs b/vite.config.mjs index c6566b5eb..dcd9da610 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -11,8 +11,8 @@ export default defineConfig({ dev: !production }, onwarn: (warning, handler) => { - // disable A11y warnings (Svelte 5 renamed codes from a11y-* to a11y_*) - if (warning.code.startsWith("a11y-") || warning.code.startsWith("a11y_")) return + // disable A11y warnings (Svelte 5 renamed the codes to a11y_*) + if (warning.code.startsWith("a11y_")) return handler(warning) } }) From f94218d28ac9a2ce047f023eba1facc3468b0bda Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Wed, 17 Jun 2026 15:35:02 +0000 Subject: [PATCH 31/32] refactor(svelte5): address review nits (comments + dead asset fallback) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - main.ts: drop the redundant mount() comment (the call is self-evident) - SlideContent.svelte: shorten the transitionId/{#key} comment to 2 lines - vite.config.mjs: drop the dead `?? assetInfo.name` fallback — in Rollup 4 PreRenderedAsset.name is the @deprecated alias of names[0] (undefined when names is empty), so `names?.[0] ?? ""` already covers every case Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GnucxHgJuqNxYHbeHonfRm --- src/frontend/components/output/layers/SlideContent.svelte | 6 ++---- src/frontend/main.ts | 1 - vite.config.mjs | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/frontend/components/output/layers/SlideContent.svelte b/src/frontend/components/output/layers/SlideContent.svelte index f1baec650..fcb974af0 100644 --- a/src/frontend/components/output/layers/SlideContent.svelte +++ b/src/frontend/components/output/layers/SlideContent.svelte @@ -48,10 +48,8 @@ let currentItems: Item[] = [] let current: any = {} let show = false - // Monotonic id for the {#key} below. Keying on the boolean `show` makes the outgoing slide - // (key=true) and the incoming slide (key=true again) share an identity, so Svelte can't keep - // both alive during a |global transition — the previous slide's text gets left on screen. - // A unique id per reveal gives each transition its own identity so the outro cleans up. + // Unique id per reveal so each {#key} block gets its own identity; keying on the boolean + // `show` would orphan the outgoing slide's text during the |global outro. let transitionId = 0 // Track items that are unchanged between slides and have no transition (to avoid redraw flicker) diff --git a/src/frontend/main.ts b/src/frontend/main.ts index 143b5136b..9fefeecce 100644 --- a/src/frontend/main.ts +++ b/src/frontend/main.ts @@ -17,7 +17,6 @@ Sentry.init({ } }) -// Svelte 5: components are instantiated with mount() instead of `new App()` const app = mount(App, { target: document.body }) export default app diff --git a/vite.config.mjs b/vite.config.mjs index dcd9da610..d04637795 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -32,8 +32,8 @@ export default defineConfig({ rollupOptions: { output: { assetFileNames: (assetInfo) => { - // Vite 8 / Rollup 4: prefer names[] (the singular .name is deprecated) - const name = assetInfo.names?.[0] ?? assetInfo.name ?? "" + // Vite 8 / Rollup 4: asset name comes from names[] + const name = assetInfo.names?.[0] ?? "" if (name.endsWith(".css")) { return "bundle.css" } From e8252fbf07407bd69819bdf166823db04dbf6193 Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Wed, 17 Jun 2026 16:07:17 +0000 Subject: [PATCH 32/32] refactor(svelte5): shorten the Auth.svelte logo-import comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review — collapse the 3-line explanation to one line; the why (direct import → data URI, avoids import.meta.url blanked in iife) is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GnucxHgJuqNxYHbeHonfRm --- src/server/remote/components/Auth.svelte | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/server/remote/components/Auth.svelte b/src/server/remote/components/Auth.svelte index 26b1e3fb0..9bf07fa81 100644 --- a/src/server/remote/components/Auth.svelte +++ b/src/server/remote/components/Auth.svelte @@ -3,9 +3,7 @@ import { translate } from "../util/helpers" import { send } from "../util/socket" import { _get, _update, dictionary, password } from "../util/stores" - // import the asset directly so Vite resolves it (small → inlined data URI); avoids - // `import.meta.url`, which Vite 8's iife companion build replaces with {} and would - // throw "Invalid URL" at runtime. + // direct import → Vite inlines as a data URI; avoids `import.meta.url` (blanked to {} in the iife companion build) import freeshowLogo from "../../../../public/import-logos/freeshow.webp" function submit() {