From dee294dda589ca9e2eaee8a7e55197635b6323c7 Mon Sep 17 00:00:00 2001 From: Innei Date: Tue, 5 Nov 2024 23:00:34 +0800 Subject: [PATCH] feat(app): support cache limit and clean cache Signed-off-by: Innei --- apps/main/package.json | 1 + apps/main/src/init.ts | 2 + apps/main/src/lib/cleaner.ts | 87 +++++++- apps/main/src/lib/store.ts | 10 + apps/main/src/tipc/app.ts | 25 ++- .../src/modules/settings/settings-glob.ts | 2 +- .../modules/settings/tabs/data-control.tsx | 192 ++++++++++++++++ .../src/modules/settings/tabs/general.tsx | 86 +------ apps/renderer/src/modules/settings/title.tsx | 12 +- apps/renderer/src/modules/settings/utils.ts | 4 +- .../src/pages/settings/(settings)/about.tsx | 2 +- .../src/pages/settings/(settings)/actions.tsx | 2 +- .../pages/settings/(settings)/appearance.tsx | 2 +- .../settings/(settings)/data-control.tsx | 22 ++ .../src/pages/settings/(settings)/feeds.tsx | 2 +- .../src/pages/settings/(settings)/general.tsx | 2 +- .../pages/settings/(settings)/integration.tsx | 2 +- .../pages/settings/(settings)/invitations.tsx | 2 +- .../src/pages/settings/(settings)/list.tsx | 2 +- .../src/pages/settings/(settings)/profile.tsx | 2 +- .../pages/settings/(settings)/shortcuts.tsx | 2 +- changelog/next.md | 3 + forge.config.ts | 2 +- locales/settings/ar-DZ.json | 1 - locales/settings/ar-IQ.json | 1 - locales/settings/ar-KW.json | 1 - locales/settings/ar-MA.json | 1 - locales/settings/ar-SA.json | 1 - locales/settings/ar-TN.json | 1 - locales/settings/de.json | 1 - locales/settings/en.json | 9 +- locales/settings/es.json | 1 - locales/settings/fi.json | 1 - locales/settings/fr.json | 1 - locales/settings/it.json | 1 - locales/settings/ja.json | 1 - locales/settings/ko.json | 1 - locales/settings/pt.json | 1 - locales/settings/ru.json | 1 - locales/settings/tr.json | 1 - locales/settings/zh-CN.json | 1 - locales/settings/zh-HK.json | 1 - locales/settings/zh-TW.json | 1 - package.json | 3 +- packages/components/src/icons/Database.tsx | 12 + packages/components/src/icons/infinify.tsx | 12 + packages/components/src/ui/slider/index.tsx | 20 ++ pnpm-lock.yaml | 209 +++++++++++++++++- 48 files changed, 624 insertions(+), 128 deletions(-) create mode 100644 apps/renderer/src/modules/settings/tabs/data-control.tsx create mode 100644 apps/renderer/src/pages/settings/(settings)/data-control.tsx create mode 100644 packages/components/src/icons/Database.tsx create mode 100644 packages/components/src/icons/infinify.tsx create mode 100644 packages/components/src/ui/slider/index.tsx diff --git a/apps/main/package.json b/apps/main/package.json index 9151b0e4d..d750db27e 100644 --- a/apps/main/package.json +++ b/apps/main/package.json @@ -34,6 +34,7 @@ "electron-log": "5.2.0", "electron-squirrel-startup": "1.0.1", "electron-updater": "^6.3.9", + "fast-folder-size": "2.3.0", "font-list": "1.5.1", "i18next": "^23.16.4", "linkedom": "^0.18.5", diff --git a/apps/main/src/init.ts b/apps/main/src/init.ts index f077c68da..50d9e6a81 100644 --- a/apps/main/src/init.ts +++ b/apps/main/src/init.ts @@ -9,6 +9,7 @@ import { app, nativeTheme, Notification, shell } from "electron" import contextMenu from "electron-context-menu" import { getIconPath } from "./helper" +import { clearCacheCronJob } from "./lib/cleaner" import { t } from "./lib/i18n" import { store } from "./lib/store" import { updateNotificationsToken } from "./lib/user" @@ -59,6 +60,7 @@ export const initializeAppStage1 = () => { registerMenuAndContextMenu() registerPushNotifications() + clearCacheCronJob() } let contextMenuDisposer: () => void diff --git a/apps/main/src/lib/cleaner.ts b/apps/main/src/lib/cleaner.ts index bd7d4883b..c761aa658 100644 --- a/apps/main/src/lib/cleaner.ts +++ b/apps/main/src/lib/cleaner.ts @@ -1,9 +1,20 @@ -import { callWindowExpose } from "@follow/shared/bridge" -import { dialog } from "electron" +import { statSync } from "node:fs" +import fsp from "node:fs/promises" +import { createRequire } from "node:module" +import path from "node:path" +import { promisify } from "node:util" +import { callWindowExpose } from "@follow/shared/bridge" +import { app, dialog } from "electron" + +import { logger } from "~/logger" import { getMainWindow } from "~/window" import { t } from "./i18n" +import { store, StoreKey } from "./store" + +const require = createRequire(import.meta.url) +const fastFolderSize = require("fast-folder-size") as any as typeof import("fast-folder-size") export const clearAllDataAndConfirm = async () => { const win = getMainWindow() @@ -53,3 +64,75 @@ export const clearAllData = async () => { caller.toast.error(`Error resetting app data: ${error.message}`) } } +const fastFolderSizeAsync = promisify(fastFolderSize) +export const getCacheSize = async () => { + const cachePath = path.join(app.getPath("userData"), "cache") + + // Size is in bytes + const sizeInBytes = await fastFolderSizeAsync(cachePath) + return sizeInBytes || 0 +} + +const getCachedFilesRecursive = async (dir: string, result: string[] = []) => { + const files = await fsp.readdir(dir) + + for (const file of files) { + const filePath = path.join(dir, file) + const stat = await fsp.stat(filePath) + if (stat.isDirectory()) { + const files = await getCachedFilesRecursive(filePath) + result.push(...files) + } else { + result.push(filePath) + } + } + return result +} + +let timer: any = null +export const clearCacheCronJob = () => { + if (timer) { + timer = clearInterval(timer) + } + timer = setInterval( + async () => { + const hasLimit = store.get(StoreKey.CacheSizeLimit) + + if (!hasLimit) { + return + } + + const cacheSize = await getCacheSize() + + const limitByteSize = hasLimit * 1024 * 1024 + if (cacheSize > limitByteSize) { + const shouldCleanSize = cacheSize - limitByteSize - 1024 * 1024 * 50 // 50MB + + const cachePath = path.join(app.getPath("userData"), "cache") + const files = await getCachedFilesRecursive(cachePath) + // Sort by last modified + files.sort((a, b) => { + const aStat = statSync(a) + const bStat = statSync(b) + return bStat.mtime.getTime() - aStat.mtime.getTime() + }) + + let cleanedSize = 0 + for (const file of files) { + const fileSize = statSync(file).size + cleanedSize += fileSize + if (cleanedSize >= shouldCleanSize) { + logger.info(`Cleaned ${cleanedSize} bytes cache`) + break + } + } + } + }, + 10 * 60 * 1000, + ) // 10 min + + return () => { + if (!timer) return + timer = clearInterval(timer) + } +} diff --git a/apps/main/src/lib/store.ts b/apps/main/src/lib/store.ts index 2fa22bd30..bdde93ca6 100644 --- a/apps/main/src/lib/store.ts +++ b/apps/main/src/lib/store.ts @@ -15,6 +15,11 @@ const createOrGetDb = () => { } return db } + +export enum StoreKey { + CacheSizeLimit = "cacheSizeLimit", +} + export const store = { get: (key: string) => { const db = createOrGetDb() @@ -26,4 +31,9 @@ export const store = { db.data[key] = value db.write() }, + delete: (key: string) => { + const db = createOrGetDb() + delete db.data[key] + db.write() + }, } diff --git a/apps/main/src/tipc/app.ts b/apps/main/src/tipc/app.ts index 0b09a1d7a..4416ca96d 100644 --- a/apps/main/src/tipc/app.ts +++ b/apps/main/src/tipc/app.ts @@ -8,7 +8,9 @@ import type { BrowserWindow } from "electron" import { app, clipboard, dialog, screen } from "electron" import { registerMenuAndContextMenu } from "~/init" -import { clearAllData } from "~/lib/cleaner" +import { clearAllData, getCacheSize } from "~/lib/cleaner" +import { store, StoreKey } from "~/lib/store" +import { logger } from "~/logger" import { isWindows11 } from "../env" import { downloadFile } from "../lib/download" @@ -267,6 +269,27 @@ ${content} return { success: false, error: errorMessage } } }), + + getCacheSize: t.procedure.action(async () => { + return getCacheSize() + }), + getCacheLimit: t.procedure.action(async () => { + return store.get(StoreKey.CacheSizeLimit) + }), + + clearCache: t.procedure.action(async () => { + const cachePath = path.join(app.getPath("userData"), "cache") + await fsp.rm(cachePath, { recursive: true, force: true }) + }), + + limitCacheSize: t.procedure.input().action(async ({ input }) => { + logger.info("set limitCacheSize", input) + if (input === 0) { + store.delete(StoreKey.CacheSizeLimit) + } else { + store.set(StoreKey.CacheSizeLimit, input) + } + }), } interface Sender extends Electron.WebContents { diff --git a/apps/renderer/src/modules/settings/settings-glob.ts b/apps/renderer/src/modules/settings/settings-glob.ts index e2550a08b..31326bfe5 100644 --- a/apps/renderer/src/modules/settings/settings-glob.ts +++ b/apps/renderer/src/modules/settings/settings-glob.ts @@ -5,7 +5,7 @@ function getSettings() { const settings = [] as { name: I18nKeysForSettings - iconName: string + icon: string | React.ReactNode path: string Component: () => JSX.Element priority: number diff --git a/apps/renderer/src/modules/settings/tabs/data-control.tsx b/apps/renderer/src/modules/settings/tabs/data-control.tsx new file mode 100644 index 000000000..ce21aef61 --- /dev/null +++ b/apps/renderer/src/modules/settings/tabs/data-control.tsx @@ -0,0 +1,192 @@ +import { CarbonInfinitySymbol } from "@follow/components/icons/infinify.jsx" +import { Button } from "@follow/components/ui/button/index.js" +import { Label } from "@follow/components/ui/label/index.jsx" +import { Slider } from "@follow/components/ui/slider/index.js" +import { env } from "@follow/shared/env" +import { useQuery } from "@tanstack/react-query" +import { useEffect } from "react" +import { useTranslation } from "react-i18next" + +import { setGeneralSetting, useGeneralSettingValue } from "~/atoms/settings/general" +import { useModalStack } from "~/components/ui/modal/stacked/hooks" +import { exportDB } from "~/database" +import { initAnalytics } from "~/initialize/analytics" +import { tipcClient } from "~/lib/client" +import { queryClient } from "~/lib/query-client" +import { clearLocalPersistStoreData } from "~/store/utils/clear" + +import { SettingDescription } from "../control" +import { createSetting } from "../helper/builder" +import { SettingItemGroup } from "../section" + +const { defineSettingItem, SettingBuilder } = createSetting( + useGeneralSettingValue, + setGeneralSetting, +) +export const SettingDataControl = () => { + const { t } = useTranslation("settings") + useEffect(() => { + tipcClient?.getLoginItemSettings().then((settings) => { + setGeneralSetting("appLaunchOnStartup", settings.openAtLogin) + }) + }, []) + + const { present } = useModalStack() + + return ( +
+ { + present({ + title: t("general.rebuild_database.title"), + clickOutsideToDismiss: true, + content: () => ( +
+

{t("general.rebuild_database.warning.line1")}

+

{t("general.rebuild_database.warning.line2")}

+
+ +
+
+ ), + }) + }, + description: t("general.rebuild_database.description"), + buttonText: t("general.rebuild_database.button"), + }, + { + label: t("general.export_database.label"), + description: t("general.export_database.description"), + buttonText: t("general.export_database.button"), + action: () => { + exportDB() + }, + }, + { + label: t("general.export.label"), + description: t("general.export.description"), + buttonText: t("general.export.button"), + action: () => { + const link = document.createElement("a") + link.href = `${env.VITE_API_URL}/subscriptions/export` + link.download = "follow.opml" + link.click() + }, + }, + + { + type: "title", + value: t("general.cache"), + }, + AppCacheLimit, + { + label: t("data_control.clean_cache.button"), + description: t("data_control.clean_cache.description"), + buttonText: t("data_control.clean_cache.button"), + action: async () => { + await tipcClient?.clearCache() + queryClient.invalidateQueries({ queryKey: ["app", "cache", "size"] }) + }, + }, + ]} + /> +
+ ) +} +const AppCacheLimit = () => { + const { t } = useTranslation("settings") + const { data: cacheSize, isLoading: isLoadingCacheSize } = useQuery({ + queryKey: ["app", "cache", "size"], + queryFn: async () => { + const byteSize = (await tipcClient?.getCacheSize()) ?? 0 + return Math.round(byteSize / 1024 / 1024) + }, + }) + const { + data: cacheLimit, + isLoading: isLoadingCacheLimit, + refetch: refetchCacheLimit, + } = useQuery({ + queryKey: ["app", "cache", "limit"], + queryFn: async () => { + const size = (await tipcClient?.getCacheLimit()) ?? 0 + return size + }, + }) + + const onChange = (value: number[]) => { + tipcClient?.limitCacheSize(value[0]) + refetchCacheLimit() + } + + if (isLoadingCacheSize || isLoadingCacheLimit) return null + + const InfinitySymbol = + return ( + +
+ + +
+ +
{InfinitySymbol}
+
500M
+
+
+ {t("data_control.app_cache_limit.description")} +
+ ) +} diff --git a/apps/renderer/src/modules/settings/tabs/general.tsx b/apps/renderer/src/modules/settings/tabs/general.tsx index 2e8eb08f7..efe699259 100644 --- a/apps/renderer/src/modules/settings/tabs/general.tsx +++ b/apps/renderer/src/modules/settings/tabs/general.tsx @@ -1,4 +1,3 @@ -import { Button } from "@follow/components/ui/button/index.js" import { LoadingCircle } from "@follow/components/ui/loading/index.jsx" import { Select, @@ -8,7 +7,6 @@ import { SelectValue, } from "@follow/components/ui/select/index.jsx" import { IN_ELECTRON } from "@follow/shared/constants" -import { env } from "@follow/shared/env" import { cn } from "@follow/utils/utils" import { useQuery } from "@tanstack/react-query" import { useAtom } from "jotai" @@ -24,13 +22,9 @@ import { useGeneralSettingSelector, useGeneralSettingValue, } from "~/atoms/settings/general" -import { useModalStack } from "~/components/ui/modal/stacked/hooks" -import { exportDB } from "~/database" import { useProxyValue, useSetProxy } from "~/hooks/biz/useProxySetting" import { fallbackLanguage } from "~/i18n" -import { initAnalytics } from "~/initialize/analytics" import { tipcClient } from "~/lib/client" -import { clearLocalPersistStoreData } from "~/store/utils/clear" import { SettingDescription, SettingInput } from "../control" import { createSetting } from "../helper/builder" @@ -54,8 +48,6 @@ export const SettingGeneral = () => { setGeneralSetting("appLaunchOnStartup", checked) }, []) - const { present } = useModalStack() - return (
{ IN_ELECTRON && VoiceSelector, - // { type: "title", value: "Secure" }, - // defineSettingItem("jumpOutLinkWarn", { - // label: "Warn when opening external links", - // description: "When you open an untrusted external link, you need to make sure that you open the link.", - // }), - { - type: "title", - value: t("general.privacy_data"), - }, - - defineSettingItem("dataPersist", { - label: t("general.data_persist.label"), - description: t("general.data_persist.description"), - }), - - defineSettingItem("sendAnonymousData", { - label: t("general.send_anonymous_data.label"), - description: t("general.send_anonymous_data.description"), - onChange(value) { - setGeneralSetting("sendAnonymousData", value) - if (value) { - initAnalytics() - } else { - window.analytics?.reset() - delete window.analytics - } - }, - }), - { - label: t("general.rebuild_database.label"), - action: () => { - present({ - title: t("general.rebuild_database.title"), - clickOutsideToDismiss: true, - content: () => ( -
-

{t("general.rebuild_database.warning.line1")}

-

{t("general.rebuild_database.warning.line2")}

-
- -
-
- ), - }) - }, - description: t("general.rebuild_database.description"), - buttonText: t("general.rebuild_database.button"), - }, - { - label: t("general.export_database.label"), - description: t("general.export_database.description"), - buttonText: t("general.export_database.button"), - action: () => { - exportDB() - }, - }, - { - label: t("general.export.label"), - description: t("general.export.description"), - buttonText: t("general.export.button"), - action: () => { - const link = document.createElement("a") - link.href = `${env.VITE_API_URL}/subscriptions/export` - link.download = "follow.opml" - link.click() - }, - }, - { type: "title", value: t("general.network"), disabled: !IN_ELECTRON }, IN_ELECTRON && NettingSetting, ]} @@ -189,7 +105,7 @@ export const SettingGeneral = () => { ) } -export const VoiceSelector = () => { +const VoiceSelector = () => { const { t } = useTranslation("settings") const { data } = useQuery({ diff --git a/apps/renderer/src/modules/settings/title.tsx b/apps/renderer/src/modules/settings/title.tsx index 7abe847ee..6dad6a184 100644 --- a/apps/renderer/src/modules/settings/title.tsx +++ b/apps/renderer/src/modules/settings/title.tsx @@ -1,5 +1,6 @@ import { EllipsisHorizontalTextWithTooltip } from "@follow/components/ui/typography/index.js" import { cn } from "@follow/utils/utils" +import { Slot } from "@radix-ui/react-slot" import { useContext } from "react" import { useTranslation } from "react-i18next" import { useLoaderData } from "react-router-dom" @@ -18,7 +19,11 @@ export const SettingsSidebarTitle = ({ path, className }: { path: string; classN return (
- + {typeof tab.icon === "string" ? ( + + ) : ( + {tab.icon} + )} {t(tab.name as any)}
) @@ -33,11 +38,12 @@ export const SettingsTitle = ({ }) => { const { t } = useTranslation("settings") const { - iconName, + icon: iconName, name: title, headerIcon, } = (useLoaderData() || loader?.() || {}) as SettingPageConfig + const usedIcon = headerIcon || iconName const isInSettingIndependentWindow = useContext(IsInSettingIndependentWindowContext) if (!title) { return null @@ -51,7 +57,7 @@ export const SettingsTitle = ({ className, )} > - + {typeof usedIcon === "string" ? : usedIcon} {t(title as any)}
) diff --git a/apps/renderer/src/modules/settings/utils.ts b/apps/renderer/src/modules/settings/utils.ts index c97f2907a..b679888f1 100644 --- a/apps/renderer/src/modules/settings/utils.ts +++ b/apps/renderer/src/modules/settings/utils.ts @@ -10,10 +10,10 @@ export enum DisableWhy { } export interface SettingPageConfig { - iconName: string + icon: string | React.ReactNode name: I18nKeysForSettings priority: number - headerIcon?: string + headerIcon?: string | React.ReactNode hideIf?: (ctx: SettingPageContext) => boolean disableIf?: (ctx: SettingPageContext) => [boolean, DisableWhy] } diff --git a/apps/renderer/src/pages/settings/(settings)/about.tsx b/apps/renderer/src/pages/settings/(settings)/about.tsx index 1f964c7bf..faba9f76f 100644 --- a/apps/renderer/src/pages/settings/(settings)/about.tsx +++ b/apps/renderer/src/pages/settings/(settings)/about.tsx @@ -3,7 +3,7 @@ import { SettingsTitle } from "~/modules/settings/title" import { defineSettingPageData } from "~/modules/settings/utils" export const loader = defineSettingPageData({ - iconName: "i-mgc-information-cute-re", + icon: "i-mgc-information-cute-re", name: "titles.about", priority: 9999, }) diff --git a/apps/renderer/src/pages/settings/(settings)/actions.tsx b/apps/renderer/src/pages/settings/(settings)/actions.tsx index e92968640..14c732e0e 100644 --- a/apps/renderer/src/pages/settings/(settings)/actions.tsx +++ b/apps/renderer/src/pages/settings/(settings)/actions.tsx @@ -8,7 +8,7 @@ const iconName = "i-mgc-magic-2-cute-re" const priority = 1020 export const loader = defineSettingPageData({ - iconName, + icon: iconName, name: "titles.actions", priority, disableIf: (ctx) => [ctx.role === UserRole.Trial, DisableWhy.NotActivation], diff --git a/apps/renderer/src/pages/settings/(settings)/appearance.tsx b/apps/renderer/src/pages/settings/(settings)/appearance.tsx index bdc3fc615..d29265461 100644 --- a/apps/renderer/src/pages/settings/(settings)/appearance.tsx +++ b/apps/renderer/src/pages/settings/(settings)/appearance.tsx @@ -6,7 +6,7 @@ const iconName = "i-mgc-palette-cute-re" const priority = 1010 export const loader = defineSettingPageData({ - iconName, + icon: iconName, name: "titles.appearance", priority, }) diff --git a/apps/renderer/src/pages/settings/(settings)/data-control.tsx b/apps/renderer/src/pages/settings/(settings)/data-control.tsx new file mode 100644 index 000000000..b9f1604ea --- /dev/null +++ b/apps/renderer/src/pages/settings/(settings)/data-control.tsx @@ -0,0 +1,22 @@ +import { MaterialSymbolsDatabaseOutline } from "@follow/components/icons/Database.js" + +import { SettingDataControl } from "~/modules/settings/tabs/data-control" +import { SettingsTitle } from "~/modules/settings/title" +import { defineSettingPageData } from "~/modules/settings/utils" + +const priority = 1025 + +export const loader = defineSettingPageData({ + icon: , + name: "titles.data_control", + priority, +}) + +export function Component() { + return ( + <> + + + + ) +} diff --git a/apps/renderer/src/pages/settings/(settings)/feeds.tsx b/apps/renderer/src/pages/settings/(settings)/feeds.tsx index 5d81f184e..6467cb1c7 100644 --- a/apps/renderer/src/pages/settings/(settings)/feeds.tsx +++ b/apps/renderer/src/pages/settings/(settings)/feeds.tsx @@ -6,7 +6,7 @@ const iconName = "i-mgc-certificate-cute-re" const priority = 1060 export const loader = defineSettingPageData({ - iconName, + icon: iconName, name: "titles.feeds", priority, }) diff --git a/apps/renderer/src/pages/settings/(settings)/general.tsx b/apps/renderer/src/pages/settings/(settings)/general.tsx index 23f2998d3..1540cff38 100644 --- a/apps/renderer/src/pages/settings/(settings)/general.tsx +++ b/apps/renderer/src/pages/settings/(settings)/general.tsx @@ -6,7 +6,7 @@ const iconName = "i-mgc-settings-7-cute-re" const priority = 1000 export const loader = defineSettingPageData({ - iconName, + icon: iconName, name: "titles.general", priority, }) diff --git a/apps/renderer/src/pages/settings/(settings)/integration.tsx b/apps/renderer/src/pages/settings/(settings)/integration.tsx index b90bc1fb2..657612cb3 100644 --- a/apps/renderer/src/pages/settings/(settings)/integration.tsx +++ b/apps/renderer/src/pages/settings/(settings)/integration.tsx @@ -6,7 +6,7 @@ const iconName = "i-mgc-department-cute-re" const priority = 1030 export const loader = defineSettingPageData({ - iconName, + icon: iconName, name: "titles.integration", priority, }) diff --git a/apps/renderer/src/pages/settings/(settings)/invitations.tsx b/apps/renderer/src/pages/settings/(settings)/invitations.tsx index 6d5963f9a..b3c900fc4 100644 --- a/apps/renderer/src/pages/settings/(settings)/invitations.tsx +++ b/apps/renderer/src/pages/settings/(settings)/invitations.tsx @@ -8,7 +8,7 @@ const iconName = "i-mgc-love-cute-re" const priority = 1070 export const loader = defineSettingPageData({ - iconName, + icon: iconName, name: "titles.invitations", priority, disableIf: (ctx) => [ctx.role === UserRole.Trial, DisableWhy.NotActivation], diff --git a/apps/renderer/src/pages/settings/(settings)/list.tsx b/apps/renderer/src/pages/settings/(settings)/list.tsx index a98ab68ed..5c8a42b97 100644 --- a/apps/renderer/src/pages/settings/(settings)/list.tsx +++ b/apps/renderer/src/pages/settings/(settings)/list.tsx @@ -8,7 +8,7 @@ const iconName = "i-mgc-rada-cute-re" const priority = 1050 export const loader = defineSettingPageData({ - iconName, + icon: iconName, name: "titles.lists", priority, disableIf: (ctx) => [ctx.role === UserRole.Trial, DisableWhy.NotActivation], diff --git a/apps/renderer/src/pages/settings/(settings)/profile.tsx b/apps/renderer/src/pages/settings/(settings)/profile.tsx index fa1603f6f..510dfac7c 100644 --- a/apps/renderer/src/pages/settings/(settings)/profile.tsx +++ b/apps/renderer/src/pages/settings/(settings)/profile.tsx @@ -5,7 +5,7 @@ import { defineSettingPageData } from "~/modules/settings/utils" const iconName = "i-mgc-user-setting-cute-re" const priority = 1090 export const loader = defineSettingPageData({ - iconName, + icon: iconName, name: "titles.profile", priority, }) diff --git a/apps/renderer/src/pages/settings/(settings)/shortcuts.tsx b/apps/renderer/src/pages/settings/(settings)/shortcuts.tsx index 41eab9f21..303ba576a 100644 --- a/apps/renderer/src/pages/settings/(settings)/shortcuts.tsx +++ b/apps/renderer/src/pages/settings/(settings)/shortcuts.tsx @@ -10,7 +10,7 @@ const iconName = "i-mgc-hotkey-cute-re" const priority = 1080 export const loader = defineSettingPageData({ - iconName, + icon: iconName, name: "titles.shortcuts", priority, }) diff --git a/changelog/next.md b/changelog/next.md index fe31e67d4..455ac005a 100644 --- a/changelog/next.md +++ b/changelog/next.md @@ -3,7 +3,10 @@ ## New Features - Feed owners can now reset their feeds. +- Now you can export the data from the local database. ## Improvements +- Optimized the Zen mode experience on macOS. + ## Bug Fixes diff --git a/forge.config.ts b/forge.config.ts index 50933abcd..fa8f10115 100644 --- a/forge.config.ts +++ b/forge.config.ts @@ -26,7 +26,7 @@ const ymlMapsMap = { win32: "latest.yml", } -const keepModules = new Set(["font-list", "vscode-languagedetection"]) +const keepModules = new Set(["font-list", "vscode-languagedetection", "fast-folder-size"]) const keepLanguages = new Set(["en", "en_GB", "en-US", "en_US"]) // remove folders & files not to be included in the app diff --git a/locales/settings/ar-DZ.json b/locales/settings/ar-DZ.json index f5d2e7176..83ffc9959 100644 --- a/locales/settings/ar-DZ.json +++ b/locales/settings/ar-DZ.json @@ -86,7 +86,6 @@ "general.mark_as_read.render.label": "تحديد كمقروءة عند العرض", "general.mark_as_read.scroll.description": "تحديد الإدخالات تلقائيًا كمقروءة عند التمرير خارج العرض.", "general.mark_as_read.scroll.label": "تحديد كمقروءة عند التمرير", - "general.privacy_data": "الخصوصية والبيانات", "general.rebuild_database.button": "إعادة بناء", "general.rebuild_database.description": "إذا كنت تواجه مشاكل في العرض، قد تحل إعادة بناء قاعدة البيانات هذه المشاكل.", "general.rebuild_database.label": "إعادة بناء قاعدة البيانات", diff --git a/locales/settings/ar-IQ.json b/locales/settings/ar-IQ.json index 14226a1e2..f0b354616 100644 --- a/locales/settings/ar-IQ.json +++ b/locales/settings/ar-IQ.json @@ -92,7 +92,6 @@ "general.mark_as_read.render.label": "تحديد كمقروءة عند العرض", "general.mark_as_read.scroll.description": "تحديد الإدخالات تلقائيًا كمقروءة عند التمرير خارج العرض.", "general.mark_as_read.scroll.label": "تحديد كمقروءة عند التمرير", - "general.privacy_data": "الخصوصية والبيانات", "general.rebuild_database.button": "إعادة بناء", "general.rebuild_database.description": "إذا كنت تواجه مشاكل في العرض، قد تحل إعادة بناء قاعدة البيانات هذه المشاكل.", "general.rebuild_database.label": "إعادة بناء قاعدة البيانات", diff --git a/locales/settings/ar-KW.json b/locales/settings/ar-KW.json index f37844167..b9e9e2a2a 100644 --- a/locales/settings/ar-KW.json +++ b/locales/settings/ar-KW.json @@ -92,7 +92,6 @@ "general.mark_as_read.render.label": "تحديد كمقروءة عند العرض", "general.mark_as_read.scroll.description": "تحديد المشاركات كمقروءة تلقائيًا عند التمرير خارج العرض.", "general.mark_as_read.scroll.label": "تحديد كمقروءة عند التمرير", - "general.privacy_data": "الخصوصية والبيانات", "general.rebuild_database.button": "إعادة بناء", "general.rebuild_database.description": "إذا كنت تواجه مشاكل في العرض، قد تحل إعادة بناء قاعدة البيانات هذه المشاكل.", "general.rebuild_database.label": "إعادة بناء قاعدة البيانات", diff --git a/locales/settings/ar-MA.json b/locales/settings/ar-MA.json index c15e4203a..02a6f14b1 100644 --- a/locales/settings/ar-MA.json +++ b/locales/settings/ar-MA.json @@ -86,7 +86,6 @@ "general.mark_as_read.render.label": "تحديد كمقروء عند العرض", "general.mark_as_read.scroll.description": "تحديد المدخلات كمقروءة تلقائيًا فاش كتخرج من العرض.", "general.mark_as_read.scroll.label": "تحديد كمقروء عند التمرير", - "general.privacy_data": "الخصوصية والبيانات", "general.rebuild_database.button": "إعادة بناء", "general.rebuild_database.description": "إلى كنتي كتواجه مشاكل فالعرض، إعادة بناء قاعدة البيانات غادي يحلها.", "general.rebuild_database.label": "إعادة بناء قاعدة البيانات", diff --git a/locales/settings/ar-SA.json b/locales/settings/ar-SA.json index 1a3b2c7fa..a8604a075 100644 --- a/locales/settings/ar-SA.json +++ b/locales/settings/ar-SA.json @@ -86,7 +86,6 @@ "general.mark_as_read.render.label": "وضع علامة كمقروء عند العرض", "general.mark_as_read.scroll.description": "وضع علامة على الإدخالات كمقروءة تلقائيًا عند التمرير خارج العرض.", "general.mark_as_read.scroll.label": "وضع علامة كمقروء عند التمرير", - "general.privacy_data": "الخصوصية والبيانات", "general.rebuild_database.button": "إعادة بناء", "general.rebuild_database.description": "إذا كنت تواجه مشكلات في العرض، فقد تحل إعادة بناء قاعدة البيانات هذه المشكلة.", "general.rebuild_database.label": "إعادة بناء قاعدة البيانات", diff --git a/locales/settings/ar-TN.json b/locales/settings/ar-TN.json index c6c50380d..487a0b810 100644 --- a/locales/settings/ar-TN.json +++ b/locales/settings/ar-TN.json @@ -92,7 +92,6 @@ "general.mark_as_read.render.label": "تحديد كمقروء عند العرض", "general.mark_as_read.scroll.description": "تحديد العناصر تلقائيًا كمقروءة عند التمرير خارج العرض.", "general.mark_as_read.scroll.label": "تحديد كمقروء عند التمرير", - "general.privacy_data": "الخصوصية والبيانات", "general.rebuild_database.button": "إعادة بناء", "general.rebuild_database.description": "إذا كنت تواجه مشكلات في العرض، فقد يحل إعادة بناء قاعدة البيانات هذه المشكلات.", "general.rebuild_database.label": "إعادة بناء قاعدة البيانات", diff --git a/locales/settings/de.json b/locales/settings/de.json index d96476bb9..6a2802470 100644 --- a/locales/settings/de.json +++ b/locales/settings/de.json @@ -96,7 +96,6 @@ "general.mark_as_read.scroll.description": "Einträge automatisch als gelesen markieren, wenn sie aus dem Ansichtsbereich gescrollt werden.", "general.mark_as_read.scroll.label": "Als gelesen markieren, wenn gescrollt wird", "general.network": "Netzwerk", - "general.privacy_data": "Datenschutz & Daten", "general.proxy.description": "Proxy für Netzwerkverkehrsrouting einrichten, z.B. socks://proxy.beispiel.de:1080", "general.proxy.label": "Proxy", "general.rebuild_database.button": "Neu aufbauen", diff --git a/locales/settings/en.json b/locales/settings/en.json index 1826f71cd..43d99d764 100644 --- a/locales/settings/en.json +++ b/locales/settings/en.json @@ -95,6 +95,10 @@ "appearance.zen_mode.description": "Zen mode is an undisturbed reading mode that allows you to focus on the content without any interference. Enabling Zen mode will hide the sidebar.", "appearance.zen_mode.label": "Zen mode", "common.give_star": "Love our product? Give us a star on GitHub!", + "data_control.app_cache_limit.description": "The maximum size of the app cache. Once the cache reaches this size, the oldest items will be deleted to free up space.", + "data_control.app_cache_limit.label": "App Cache Limit", + "data_control.clean_cache.button": "Clean Cache", + "data_control.clean_cache.description": "Clean the app cache to free up space.", "feeds.claimTips": "To claim your feeds and receive tips, right-click on the feed in your subscription list and select Claim.", "feeds.noFeeds": "No claimed feeds", "feeds.tableHeaders.entryCount": "Entries", @@ -102,6 +106,8 @@ "feeds.tableHeaders.subscriptionCount": "Subs", "feeds.tableHeaders.tipAmount": "Tips", "general.app": "App", + "general.cache": "Cache", + "general.data": "Data", "general.data_persist.description": "Persist data locally to enable offline access and local search.", "general.data_persist.label": "Persist data for offline usage", "general.export.button": "Export", @@ -121,7 +127,7 @@ "general.mark_as_read.scroll.description": "Automatically mark entries as read when scrolled out of the view.", "general.mark_as_read.scroll.label": "Mark as read when scrolling", "general.network": "Network", - "general.privacy_data": "Privacy & Data", + "general.privacy": "Privacy", "general.proxy.description": "Set proxy for network traffic routing, e.g., socks://proxy.example.com:1080", "general.proxy.label": "Proxy", "general.rebuild_database.button": "Rebuild", @@ -243,6 +249,7 @@ "titles.about": "About", "titles.actions": "Actions", "titles.appearance": "Appearance", + "titles.data_control": "Data Control", "titles.feeds": "Feeds", "titles.general": "General", "titles.integration": "Integration", diff --git a/locales/settings/es.json b/locales/settings/es.json index 707edd892..5584ecf94 100644 --- a/locales/settings/es.json +++ b/locales/settings/es.json @@ -86,7 +86,6 @@ "general.mark_as_read.render.label": "Marcar como leído cuando está en vista", "general.mark_as_read.scroll.description": "Marcar automáticamente las entradas como leídas al desplazarse fuera de la vista.", "general.mark_as_read.scroll.label": "Marcar como leído al desplazarse", - "general.privacy_data": "Privacidad y Datos", "general.rebuild_database.button": "Reconstruir", "general.rebuild_database.description": "Si experimentas problemas de renderizado, reconstruir la base de datos puede solucionarlos.", "general.rebuild_database.label": "Reconstruir Base de Datos", diff --git a/locales/settings/fi.json b/locales/settings/fi.json index 973e05324..bf8661b8a 100644 --- a/locales/settings/fi.json +++ b/locales/settings/fi.json @@ -86,7 +86,6 @@ "general.mark_as_read.render.label": "Merkitse luetuksi, kun näkyvissä", "general.mark_as_read.scroll.description": "Merkitse merkinnät automaattisesti luetuiksi, kun ne vieritetään pois näkymästä.", "general.mark_as_read.scroll.label": "Merkitse luetuksi vierittäessä", - "general.privacy_data": "Yksityisyys ja data", "general.rebuild_database.button": "Rakenna uudelleen", "general.rebuild_database.description": "Jos kohtaat renderöintiongelmia, tietokannan uudelleenrakentaminen saattaa ratkaista ne.", "general.rebuild_database.label": "Rakenna tietokanta uudelleen", diff --git a/locales/settings/fr.json b/locales/settings/fr.json index e7254c1fd..b858bf450 100644 --- a/locales/settings/fr.json +++ b/locales/settings/fr.json @@ -86,7 +86,6 @@ "general.mark_as_read.render.label": "Marquer comme lu lors de l'affichage", "general.mark_as_read.scroll.description": "Marquer automatiquement les entrées comme lues lorsqu'elles sortent de la vue.", "general.mark_as_read.scroll.label": "Marquer comme lu lors du défilement", - "general.privacy_data": "Confidentialité & Données", "general.rebuild_database.button": "Reconstruire", "general.rebuild_database.description": "Si vous rencontrez des problèmes d'affichage, la reconstruction de la base de données peut les résoudre.", "general.rebuild_database.label": "Reconstruire la base de données", diff --git a/locales/settings/it.json b/locales/settings/it.json index 6805dd2b1..cb7f27e95 100644 --- a/locales/settings/it.json +++ b/locales/settings/it.json @@ -86,7 +86,6 @@ "general.mark_as_read.render.label": "Segna come letto quando è in visualizzazione", "general.mark_as_read.scroll.description": "Segna automaticamente le voci come lette quando scorri fuori dalla visualizzazione.", "general.mark_as_read.scroll.label": "Segna come letto durante lo scorrimento", - "general.privacy_data": "Privacy e Dati", "general.rebuild_database.button": "Ricostruisci", "general.rebuild_database.description": "Se riscontri problemi di visualizzazione, la ricostruzione del database potrebbe risolverli.", "general.rebuild_database.label": "Ricostruisci Database", diff --git a/locales/settings/ja.json b/locales/settings/ja.json index f5c1e9d14..494848e80 100644 --- a/locales/settings/ja.json +++ b/locales/settings/ja.json @@ -118,7 +118,6 @@ "general.mark_as_read.scroll.description": "表示からスクロールアウトしたときにエントリを自動的に既読にします。", "general.mark_as_read.scroll.label": "スクロール時に既読にする", "general.network": "ネットワーク", - "general.privacy_data": "プライバシーとデータ", "general.proxy.description": "ネットワークリクエストを代理します。例: socks://proxy.example.com:1080", "general.proxy.label": "プロキシ", "general.rebuild_database.button": "再構築", diff --git a/locales/settings/ko.json b/locales/settings/ko.json index 5c366de4b..950184733 100644 --- a/locales/settings/ko.json +++ b/locales/settings/ko.json @@ -96,7 +96,6 @@ "general.mark_as_read.scroll.description": "스크롤하여 보기에서 벗어날 때 자동으로 항목을 읽음으로 표시합니다.", "general.mark_as_read.scroll.label": "스크롤 시 읽음으로 표시", "general.network": "네트워크", - "general.privacy_data": "개인정보 및 데이터", "general.proxy.description": "네트워크 트래픽 라우팅을 위한 프록시 설정, 예: socks://proxy.example.com:1080", "general.proxy.label": "프록시", "general.rebuild_database.button": "재구축", diff --git a/locales/settings/pt.json b/locales/settings/pt.json index 96eea1c25..da3688c94 100644 --- a/locales/settings/pt.json +++ b/locales/settings/pt.json @@ -86,7 +86,6 @@ "general.mark_as_read.render.label": "Marcar como lido ao visualizar", "general.mark_as_read.scroll.description": "Marcar automaticamente as entradas como lidas quando saem do campo de visão.", "general.mark_as_read.scroll.label": "Marcar como lido ao rolar a página", - "general.privacy_data": "Privacidade & Dados", "general.rebuild_database.button": "Reconstruir", "general.rebuild_database.description": "Se estiver a ter problemas de renderização, reconstruir a base de dados pode resolver.", "general.rebuild_database.label": "Reconstruir Base de Dados", diff --git a/locales/settings/ru.json b/locales/settings/ru.json index c8aa146c9..004b973f9 100644 --- a/locales/settings/ru.json +++ b/locales/settings/ru.json @@ -86,7 +86,6 @@ "general.mark_as_read.render.label": "Отметить как прочитанное при отображении", "general.mark_as_read.scroll.description": "Автоматически отмечать записи как прочитанные при прокрутке.", "general.mark_as_read.scroll.label": "Отметить как прочитанное при прокрутке", - "general.privacy_data": "Конфиденциальность и данные", "general.rebuild_database.button": "Перестроить", "general.rebuild_database.description": "Если у вас возникают проблемы с отображением, перестроение базы данных может их решить.", "general.rebuild_database.label": "Перестроить базу данных", diff --git a/locales/settings/tr.json b/locales/settings/tr.json index 05ebb6ed5..f11e5fb3e 100644 --- a/locales/settings/tr.json +++ b/locales/settings/tr.json @@ -96,7 +96,6 @@ "general.mark_as_read.scroll.description": "Görünümden kaydırıldığında girdileri otomatik olarak okundu olarak işaretle.", "general.mark_as_read.scroll.label": "Kaydırıldığında okundu olarak işaretle", "general.network": "Ağ", - "general.privacy_data": "Gizlilik ve Veri", "general.proxy.description": "Ağ trafiği yönlendirmesi için proxy ayarla, örn., socks://proxy.example.com:1080", "general.proxy.label": "Proxy", "general.rebuild_database.button": "Yeniden Oluştur", diff --git a/locales/settings/zh-CN.json b/locales/settings/zh-CN.json index cfc455e19..77228575c 100644 --- a/locales/settings/zh-CN.json +++ b/locales/settings/zh-CN.json @@ -117,7 +117,6 @@ "general.mark_as_read.scroll.description": "当条目滚动出窗口时自动标记为已读", "general.mark_as_read.scroll.label": "滚动时标记为已读", "general.network": "网络", - "general.privacy_data": "隐私与数据", "general.proxy.description": "代理网络请求,示例:socks://proxy.example.com:1080", "general.proxy.label": "代理", "general.rebuild_database.button": "重建", diff --git a/locales/settings/zh-HK.json b/locales/settings/zh-HK.json index dc5b0d1bc..3c7be4a1b 100644 --- a/locales/settings/zh-HK.json +++ b/locales/settings/zh-HK.json @@ -106,7 +106,6 @@ "general.mark_as_read.scroll.description": "當滾動出視圖時,自動標記條目為已讀。", "general.mark_as_read.scroll.label": "滾動時標記為已讀", "general.network": "網絡", - "general.privacy_data": "隱私與數據", "general.proxy.description": "代理網絡流量,例如:socks://proxy.example.com:1080", "general.proxy.label": "代理", "general.rebuild_database.button": "重建", diff --git a/locales/settings/zh-TW.json b/locales/settings/zh-TW.json index d7a0bfa13..fa8dd1630 100644 --- a/locales/settings/zh-TW.json +++ b/locales/settings/zh-TW.json @@ -106,7 +106,6 @@ "general.mark_as_read.scroll.description": "當條目捲動離開視圖時自動標記為已讀。", "general.mark_as_read.scroll.label": "捲動時標記為已讀", "general.network": "網路", - "general.privacy_data": "隱私與資料", "general.proxy.description": "設定代理伺服器來處理網路流量,例如:socks://proxy.example.com:1080", "general.proxy.label": "代理伺服器", "general.rebuild_database.button": "重置", diff --git a/package.json b/package.json index 5dd297cb5..b719a5869 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,8 @@ "prettier --ignore-unknown --write" ], "locales/**/*.json": [ - "npm run dedupe:locales" + "npm run dedupe:locales", + "git add locales" ] }, "bump": { diff --git a/packages/components/src/icons/Database.tsx b/packages/components/src/icons/Database.tsx new file mode 100644 index 000000000..d1d6835ad --- /dev/null +++ b/packages/components/src/icons/Database.tsx @@ -0,0 +1,12 @@ +import type { SVGProps } from "react" + +export function MaterialSymbolsDatabaseOutline(props: SVGProps) { + return ( + + + + ) +} diff --git a/packages/components/src/icons/infinify.tsx b/packages/components/src/icons/infinify.tsx new file mode 100644 index 000000000..846ef0eb8 --- /dev/null +++ b/packages/components/src/icons/infinify.tsx @@ -0,0 +1,12 @@ +import type { SVGProps } from "react" + +export function CarbonInfinitySymbol(props: SVGProps) { + return ( + + + + ) +} diff --git a/packages/components/src/ui/slider/index.tsx b/packages/components/src/ui/slider/index.tsx new file mode 100644 index 000000000..e01c5e0f7 --- /dev/null +++ b/packages/components/src/ui/slider/index.tsx @@ -0,0 +1,20 @@ +import { cn } from "@follow/utils/utils" +import * as SliderPrimitive from "@radix-ui/react-slider" +import * as React from "react" + +export const Slider = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + + +)) +Slider.displayName = SliderPrimitive.Root.displayName diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5a0d2f0db..a3c611abb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -272,6 +272,9 @@ importers: electron-updater: specifier: ^6.3.9 version: 6.3.9 + fast-folder-size: + specifier: 2.3.0 + version: 2.3.0 font-list: specifier: 1.5.1 version: 1.5.1 @@ -3343,8 +3346,8 @@ packages: '@radix-ui/react-avatar@1.1.1': resolution: {integrity: sha512-eoOtThOmxeoizxpX6RiEsQZ2wj5r4+zoeqAwO0cBaFQGjJwIH3dIX0OCxNrCyrrdxG+vBweMETh3VziQG7c1kw==} peerDependencies: - '@types/react': npm:types-react@19.0.0-rc.1 - '@types/react-dom': npm:types-react-dom@19.0.0-rc.1 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -4859,6 +4862,9 @@ packages: birecord@0.1.1: resolution: {integrity: sha512-VUpsf/qykW0heRlC8LooCq28Kxn3mAqKohhDG/49rrsQ1dT1CXyj/pgXS+5BSRzFTR/3DyIBOqQOrGyZOh71Aw==} + bl@1.2.3: + resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -4936,6 +4942,12 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer-alloc-unsafe@1.1.0: + resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} + + buffer-alloc@1.2.0: + resolution: {integrity: sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==} + buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} @@ -4946,6 +4958,9 @@ packages: resolution: {integrity: sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==} engines: {node: '>=0.4'} + buffer-fill@1.0.0: + resolution: {integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -5542,6 +5557,26 @@ packages: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} + decompress-tar@4.1.1: + resolution: {integrity: sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==} + engines: {node: '>=4'} + + decompress-tarbz2@4.1.1: + resolution: {integrity: sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==} + engines: {node: '>=4'} + + decompress-targz@4.1.1: + resolution: {integrity: sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==} + engines: {node: '>=4'} + + decompress-unzip@4.0.1: + resolution: {integrity: sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==} + engines: {node: '>=4'} + + decompress@4.2.1: + resolution: {integrity: sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==} + engines: {node: '>=4'} + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -6233,6 +6268,10 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-folder-size@2.3.0: + resolution: {integrity: sha512-W3HuQYdZ1GaOAdIqKR/guqxvlHW0f8ZFknYfnsTBuL+9QokVARUBNt/kFE8hkyZd/yQ/mYnKmrtWmFyTLRfX3A==} + hasBin: true + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -6293,6 +6332,18 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + file-type@3.9.0: + resolution: {integrity: sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==} + engines: {node: '>=0.10.0'} + + file-type@5.2.0: + resolution: {integrity: sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==} + engines: {node: '>=4'} + + file-type@6.2.0: + resolution: {integrity: sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==} + engines: {node: '>=4'} + filelist@1.0.4: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} @@ -6499,6 +6550,10 @@ packages: resolution: {integrity: sha512-SCbprXGAPdIhKAXiG+Mk6yeoFH61JlYunqdFQFHDtLjJlDjFf6x07dsS8acO+xWt52jpdVo49AlVDnUVK1sDNw==} engines: {node: '>= 4.0'} + get-stream@2.3.1: + resolution: {integrity: sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==} + engines: {node: '>=0.10.0'} + get-stream@4.1.0: resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} engines: {node: '>=6'} @@ -6974,6 +7029,9 @@ packages: is-my-json-valid@2.20.6: resolution: {integrity: sha512-1JQwulVNjx8UqkPE/bqDaxtH4PXCe/2VRh/y3p99heOV87HG4Id5/VfDswd+YiAfHcRTfDlWgISycnHuhZq1aw==} + is-natural-number@4.0.1: + resolution: {integrity: sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -7451,6 +7509,10 @@ packages: resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} engines: {node: '>=12'} + make-dir@1.3.0: + resolution: {integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==} + engines: {node: '>=4'} + make-fetch-happen@10.2.1: resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -8190,6 +8252,18 @@ packages: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} + pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + + pinkie-promise@2.0.1: + resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==} + engines: {node: '>=0.10.0'} + + pinkie@2.0.4: + resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} + engines: {node: '>=0.10.0'} + pino-abstract-transport@2.0.0: resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} @@ -9059,6 +9133,10 @@ packages: secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + seek-bzip@1.0.6: + resolution: {integrity: sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==} + hasBin: true + selecto@1.26.3: resolution: {integrity: sha512-gZHgqMy5uyB6/2YDjv3Qqaf7bd2hTDOpPdxXlrez4R3/L0GiEWDCFaUfrflomgqdb3SxHF2IXY0Jw0EamZi7cw==} @@ -9315,6 +9393,9 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-dirs@2.1.0: + resolution: {integrity: sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==} + strip-eof@1.0.0: resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} engines: {node: '>=0.10.0'} @@ -9409,6 +9490,10 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + tar-stream@1.6.2: + resolution: {integrity: sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==} + engines: {node: '>= 0.8.0'} + tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} @@ -9442,6 +9527,9 @@ packages: thread-stream@3.1.0: resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tiny-inflate@1.0.3: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} @@ -9488,6 +9576,9 @@ packages: resolution: {integrity: sha512-DbplOfQFkqG5IHcDyyrs/lkvSr3mPUVsFf/RbDppOshs22yTPnSJWEe6FkYd1txAwU/zcnR905ar2fi4kwF29w==} engines: {node: '>=0.12'} + to-buffer@1.1.1: + resolution: {integrity: sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==} + to-data-view@1.1.0: resolution: {integrity: sha512-1eAdufMg6mwgmlojAx3QeMnzB/BTVp7Tbndi3U7ftcT2zCZadjxkkmLmd97zmaxWi+sgGcgWrokmpEoy0Dn0vQ==} @@ -9672,6 +9763,9 @@ packages: uhyphen@0.2.0: resolution: {integrity: sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==} + unbzip2-stream@1.4.3: + resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} + unconfig@0.5.5: resolution: {integrity: sha512-VQZ5PT9HDX+qag0XdgQi8tJepPhXiR/yVOkn707gJDKo31lGjRilPREiQJ9Z6zd/Ugpv6ZvO5VxVIcatldYcNQ==} @@ -14879,6 +14973,11 @@ snapshots: birecord@0.1.1: {} + bl@1.2.3: + dependencies: + readable-stream: 2.3.8 + safe-buffer: 5.2.1 + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -14983,12 +15082,21 @@ snapshots: node-releases: 2.0.18 update-browserslist-db: 1.1.1(browserslist@4.24.0) + buffer-alloc-unsafe@1.1.0: {} + + buffer-alloc@1.2.0: + dependencies: + buffer-alloc-unsafe: 1.1.0 + buffer-fill: 1.0.0 + buffer-crc32@0.2.13: {} buffer-equal-constant-time@1.0.1: {} buffer-equal@1.0.1: {} + buffer-fill@1.0.0: {} + buffer-from@1.1.2: {} buffer-xor@1.0.3: {} @@ -15705,6 +15813,44 @@ snapshots: dependencies: mimic-response: 3.1.0 + decompress-tar@4.1.1: + dependencies: + file-type: 5.2.0 + is-stream: 1.1.0 + tar-stream: 1.6.2 + + decompress-tarbz2@4.1.1: + dependencies: + decompress-tar: 4.1.1 + file-type: 6.2.0 + is-stream: 1.1.0 + seek-bzip: 1.0.6 + unbzip2-stream: 1.4.3 + + decompress-targz@4.1.1: + dependencies: + decompress-tar: 4.1.1 + file-type: 5.2.0 + is-stream: 1.1.0 + + decompress-unzip@4.0.1: + dependencies: + file-type: 3.9.0 + get-stream: 2.3.1 + pify: 2.3.0 + yauzl: 2.10.0 + + decompress@4.2.1: + dependencies: + decompress-tar: 4.1.1 + decompress-tarbz2: 4.1.1 + decompress-targz: 4.1.1 + decompress-unzip: 4.0.1 + graceful-fs: 4.2.11 + make-dir: 1.3.0 + pify: 2.3.0 + strip-dirs: 2.1.0 + deep-eql@5.0.2: {} deep-is@0.1.4: {} @@ -16610,6 +16756,13 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-folder-size@2.3.0: + dependencies: + decompress: 4.2.1 + https-proxy-agent: 7.0.5 + transitivePeerDependencies: + - supports-color + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -16686,6 +16839,12 @@ snapshots: dependencies: flat-cache: 4.0.1 + file-type@3.9.0: {} + + file-type@5.2.0: {} + + file-type@6.2.0: {} + filelist@1.0.4: dependencies: minimatch: 5.1.6 @@ -16944,6 +17103,11 @@ snapshots: transitivePeerDependencies: - supports-color + get-stream@2.3.1: + dependencies: + object-assign: 4.1.1 + pinkie-promise: 2.0.1 + get-stream@4.1.0: dependencies: pump: 3.0.2 @@ -17595,6 +17759,8 @@ snapshots: xtend: 4.0.2 optional: true + is-natural-number@4.0.1: {} + is-number@7.0.0: {} is-obj@2.0.0: {} @@ -18033,6 +18199,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + make-dir@1.3.0: + dependencies: + pify: 3.0.0 + make-fetch-happen@10.2.1: dependencies: agentkeepalive: 4.5.0 @@ -19039,6 +19209,14 @@ snapshots: pify@2.3.0: {} + pify@3.0.0: {} + + pinkie-promise@2.0.1: + dependencies: + pinkie: 2.0.4 + + pinkie@2.0.4: {} + pino-abstract-transport@2.0.0: dependencies: split2: 4.2.0 @@ -19966,6 +20144,10 @@ snapshots: secure-json-parse@2.7.0: {} + seek-bzip@1.0.6: + dependencies: + commander: 2.20.3 + selecto@1.26.3: dependencies: '@daybrush/utils': 1.13.0 @@ -20218,6 +20400,10 @@ snapshots: strip-bom@3.0.0: {} + strip-dirs@2.1.0: + dependencies: + is-natural-number: 4.0.1 + strip-eof@1.0.0: {} strip-final-newline@3.0.0: {} @@ -20332,6 +20518,16 @@ snapshots: transitivePeerDependencies: - ts-node + tar-stream@1.6.2: + dependencies: + bl: 1.2.3 + buffer-alloc: 1.2.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + readable-stream: 2.3.8 + to-buffer: 1.1.1 + xtend: 4.0.2 + tar-stream@2.2.0: dependencies: bl: 4.1.0 @@ -20381,6 +20577,8 @@ snapshots: dependencies: real-require: 0.2.0 + through@2.3.8: {} + tiny-inflate@1.0.3: {} tiny-typed-emitter@2.1.0: {} @@ -20417,6 +20615,8 @@ snapshots: unorm: 1.6.0 optional: true + to-buffer@1.1.1: {} + to-data-view@1.1.0: optional: true @@ -20572,6 +20772,11 @@ snapshots: uhyphen@0.2.0: {} + unbzip2-stream@1.4.3: + dependencies: + buffer: 5.7.1 + through: 2.3.8 + unconfig@0.5.5: dependencies: '@antfu/utils': 0.7.10