commit 6d5265dfe0ff1690fd191deb9a89f8c0548fcf3a Author: Tom Butcher Date: Sun Nov 9 18:02:15 2025 +0000 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0509aab --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local +.dev.vars + +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +.wrangler/ \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..cb29dc9 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "tombutcher-api", + "version": "0.0.0", + "private": true, + "scripts": { + "deploy": "wrangler deploy --env production", + "dev": "wrangler dev --test-scheduled --host 0.0.0.0", + "test": "vitest" + }, + "devDependencies": { + "@cloudflare/vitest-pool-workers": "^0.6.4", + "vitest": "~2.1.9", + "wrangler": "^4.38.0" + }, + "dependencies": { + "@napi-rs/canvas": "^0.1.80", + "@notionhq/client": "^5.1.0", + "blurhash": "^2.0.5", + "dayjs": "^1.11.18", + "jpeg-js": "^0.4.4", + "lodash": "^4.17.21", + "microdiff": "^1.5.0", + "remove-svg-properties": "^0.3.4", + "sharp": "^0.34.3", + "svgson": "^5.3.1", + "upng-js": "^2.1.0" + } +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..a63e2ed --- /dev/null +++ b/src/index.js @@ -0,0 +1,67 @@ +import { handleContactRequest } from "./routes/contact.js"; +import { handleContentRequest } from "./routes/content.js"; +import { handleNotionHook } from "./routes/hooks.js"; +import { globalHeaders } from "./utils/api.js"; + +async function handleRequest(request, env) { + if ( + request.method === "OPTIONS" && + request.url.split("?")[0].endsWith("/contact") + ) { + console.log("Handling contact OPTIONS request..."); + return new Response(null, { + status: 204, // No Content + headers: { + "Access-Control-Allow-Origin": env.CORS_ORIGIN, + "Access-Control-Allow-Methods": "POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type", + }, + }); + } + if ( + request.method === "POST" && + request.url.split("?")[0].endsWith("/contact") + ) { + return await handleContactRequest(request, env); + } + + if ( + request.method === "GET" && + request.url.split("?")[0].endsWith("/content") + ) { + return await handleContentRequest(request, env); + } + + if ( + request.method === "POST" && + request.url.split("?")[0].endsWith("/notionHook") + ) { + return await handleNotionHook(request, env); + } + + // Return 404 if the route is not found + return new Response("Not Found", { status: 404, headers: globalHeaders }); +} + +async function handleScheduledEvent(event, env) { + console.log("Scheduled event:", event.cron); + switch (event.cron) { + case "*/5 * * * *": + await updateAllSmoobuData(env); + break; + case "* * * * *": + await refreshBookingCache(env); + break; + default: + break; + } +} + +export default { + async fetch(request, env) { + return await handleRequest(request, env); + }, + async scheduled(event, env) { + return await handleScheduledEvent(event, env); + }, +}; diff --git a/src/objects/blogs.js b/src/objects/blogs.js new file mode 100644 index 0000000..bc02c62 --- /dev/null +++ b/src/objects/blogs.js @@ -0,0 +1,45 @@ +import { + buildListCache, + queryNotionDataSource, + getNotionPage, +} from "../utils/notion.js"; +import { getBlogs, storeBlogs, transformNotionBlog } from "../utils/blogs.js"; +import { unionBy } from "lodash"; + +export async function importNotionBlogs(env, notionId = null) { + console.log("Importing Blogs from Notion..."); + + let BlogsData = []; + if (notionId !== null) { + BlogsData = [await getNotionPage(notionId)]; + } else { + BlogsData = await queryNotionDataSource(env.BLOGS_DB); + } + + const Blogs = ( + await Promise.all( + BlogsData.map(async (blog) => transformNotionBlog(env, blog)) + ) + ).filter(Boolean); + + // If notionId is not null, use lodash unionBy to replace existing Blogs + if (notionId !== null) { + const cachedBlogs = await getBlogs(env); + const mergedBlogs = unionBy(Blogs, cachedBlogs, "notionId"); + await storeBlogs(env, mergedBlogs); + console.log("Imported Blogs from Notion and merged with cache."); + return mergedBlogs; + } + + await storeBlogs(env, Blogs); + console.log("Imported Blogs from Notion."); + return Blogs; +} + +export async function deleteNotionBlogFromCache(env, notionId) { + const cachedBlogs = await getBlogs(env); + const newCache = cachedBlogs.filter((b) => b.notionId !== notionId); + await storeBlogs(env, newCache); + console.log("Deleted blog from cache."); + return newCache; +} diff --git a/src/objects/companies.js b/src/objects/companies.js new file mode 100644 index 0000000..3712f92 --- /dev/null +++ b/src/objects/companies.js @@ -0,0 +1,62 @@ +import { + queryNotionDataSource, + getNotionPage, + sortDuration, +} from "../utils/notion.js"; +import { + getCompanies, + storeCompanies, + transformNotionCompany, +} from "../utils/companies.js"; +import { unionBy } from "lodash"; +import { getSettings } from "../utils/settings.js"; +import { getPositions } from "../utils/positions.js"; + +export async function importNotionCompanies(env, notionId = null) { + console.log("Importing Companies from Notion..."); + + let CompaniesData = []; + if (notionId !== null) { + CompaniesData = [await getNotionPage(notionId)]; + } else { + CompaniesData = await queryNotionDataSource(env.COMPANIES_DB); + } + + const settingsData = await getSettings(env); + const positionsData = await getPositions(env); + + const Companies = ( + await Promise.all( + CompaniesData.map(async (company) => + transformNotionCompany(env, company, settingsData, positionsData) + ) + ) + ) + .filter(Boolean) + .sort(sortDuration); + + // If notionId is not null, use lodash unionBy to replace existing Companies + if (notionId !== null) { + const cachedCompanies = await getCompanies(env); + const mergedCompanies = unionBy( + Companies, + cachedCompanies, + "notionId" + ).sort(sortDuration); + await storeCompanies(env, mergedCompanies); + console.log("Imported Companies from Notion and merged with cache."); + return mergedCompanies; + } + + await storeCompanies(env, Companies); + console.log("Imported Companies from Notion."); + return Companies; +} + +export async function deleteNotionCompanyFromCache(env, notionId) { + const cachedCompanies = await getCompanies(env); + const newCache = cachedCompanies.filter((c) => c.notionId !== notionId); + await storeCompanies(env, newCache); + console.log("Deleted company from cache."); + return newCache; +} diff --git a/src/objects/cv.js b/src/objects/cv.js new file mode 100644 index 0000000..559f5ce --- /dev/null +++ b/src/objects/cv.js @@ -0,0 +1,43 @@ +import { + buildListCache, + queryNotionDataSource, + getNotionPage, +} from "../utils/notion.js"; +import { getCvs, storeCvs, transformNotionCv } from "../utils/cv.js"; +import { unionBy } from "lodash"; + +export async function importNotionCvs(env, notionId = null) { + console.log("Importing CVs from Notion..."); + + let CvsData = []; + if (notionId !== null) { + CvsData = [await getNotionPage(notionId)]; + } else { + CvsData = await queryNotionDataSource(env.CV_DB); + } + + const Cvs = ( + await Promise.all(CvsData.map(async (cv) => transformNotionCv(env, cv))) + ).filter(Boolean); + + // If notionId is not null, use lodash unionBy to replace existing CVs + if (notionId !== null) { + const cachedCvs = await getCvs(env); + const mergedCvs = unionBy(Cvs, cachedCvs, "notionId"); + await storeCvs(env, mergedCvs); + console.log("Imported CVs from Notion and merged with cache."); + return mergedCvs; + } + + await storeCvs(env, Cvs); + console.log("Imported CVs from Notion."); + return Cvs; +} + +export async function deleteNotionCvFromCache(env, notionId) { + const cachedCvs = await getCvs(env); + const newCache = cachedCvs.filter((c) => c.notionId !== notionId); + await storeCvs(env, newCache); + console.log("Deleted CV from cache."); + return newCache; +} diff --git a/src/objects/files.js b/src/objects/files.js new file mode 100644 index 0000000..51b6c7f --- /dev/null +++ b/src/objects/files.js @@ -0,0 +1,27 @@ +import { getPages } from "../utils/pages.js"; +import { getBlogs } from "../utils/blogs.js"; +import { getProjects } from "../utils/projects.js"; +import { getCompanies } from "../utils/companies.js"; +import { collectFileUrls, updateFiles } from "../utils/fileCache.js"; +import { getCvs } from "../utils/cv.js"; + +export async function importFiles(env) { + console.log("Importing files from Notion..."); + const pages = (await getPages(env)) || []; + const blogs = (await getBlogs(env)) || []; + const projects = (await getProjects(env)) || []; + const companies = (await getCompanies(env)) || []; + const cvs = (await getCvs(env)) || []; + + const fileUrls = collectFileUrls([ + ...pages, + ...blogs, + ...projects, + ...companies, + ...cvs, + ]); + + const updatedFiles = await updateFiles(env, fileUrls); + console.log("Imported files from Notion."); + return updatedFiles; +} diff --git a/src/objects/images.js b/src/objects/images.js new file mode 100644 index 0000000..a0f1955 --- /dev/null +++ b/src/objects/images.js @@ -0,0 +1,26 @@ +import { getPages } from "../utils/pages.js"; +import { getBlogs } from "../utils/blogs.js"; +import { getProjects } from "../utils/projects.js"; +import { getCompanies } from "../utils/companies.js"; +import { collectImageUrls, updateImages } from "../utils/imageCache.js"; + +export async function importImages(env) { + // Fetch caches + console.log("Importing images from Notion..."); + const pages = (await getPages(env)) || []; + const blogs = (await getBlogs(env)) || []; + const projects = (await getProjects(env)) || []; + const companies = (await getCompanies(env)) || []; + + // Collect all image URLs + const imageUrls = collectImageUrls([ + ...pages, + ...blogs, + ...projects, + ...companies, + ]); + + const updatedImages = await updateImages(env, imageUrls); + console.log("Imported images from Notion."); + return updatedImages; +} diff --git a/src/objects/navigation.js b/src/objects/navigation.js new file mode 100644 index 0000000..3b874bb --- /dev/null +++ b/src/objects/navigation.js @@ -0,0 +1,22 @@ +import { queryNotionDataSource } from "../utils/notion.js"; +import { transformNavigation, storeNavigation } from "../utils/navigation.js"; + +export async function importNotionNavigation(env) { + console.log("Importing navigation from Notion..."); + const notionPages = await queryNotionDataSource(env.PAGES_DB); + const notionBlogs = await queryNotionDataSource(env.BLOGS_DB); + // Transform the incoming pages for the given type + const navigationItems = ( + await Promise.all( + notionPages.map(async (item) => transformNavigation(item, "page")), + notionBlogs.map(async (item) => transformNavigation(item, "blog")) + ) + ).filter(Boolean); + + // Store the updated cache + await storeNavigation(env, navigationItems); + + console.log("Navigation imported from Notion."); + + return navigationItems; +} diff --git a/src/objects/pages.js b/src/objects/pages.js new file mode 100644 index 0000000..7b7129d --- /dev/null +++ b/src/objects/pages.js @@ -0,0 +1,53 @@ +import { queryNotionDataSource, getNotionPage } from "../utils/notion.js"; +import { transformPageData, storePages, getPages } from "../utils/pages.js"; +import { getSettings } from "../utils/settings.js"; +import { unionBy } from "lodash"; + +// Fetch Notion pages (raw data) +export async function importNotionPages(env, notionId = null) { + console.log("Importing pages from Notion..."); + const settingsData = await getSettings(env); + let pagesData = []; + if (notionId !== null) { + pagesData = [await getNotionPage(notionId)]; + } else { + pagesData = await queryNotionDataSource(env.PAGES_DB, { + sorts: [{ property: "Order", direction: "ascending" }], + }); + } + + const pages = ( + await Promise.all( + pagesData.map(async (page) => transformPageData(env, page, settingsData)) + ) + ) + .filter(Boolean) + .sort((a, b) => a.order - b.order) + .filter((page) => page.published == true); + + if (notionId !== null) { + const cachedPages = await getPages(env); + console.log("Cached pages:", cachedPages); + console.log("Pages:", pages); + + // Merge: preserve cached order, but replace with new data if available + const mergedPages = unionBy(pages, cachedPages, "notionId") + .sort((a, b) => a.order - b.order) + .filter((page) => page.published == true); + await storePages(env, mergedPages); + console.log("Pages imported from Notion and merged with cache."); + return mergedPages; + } + + await storePages(env, pages); + console.log("Pages imported from Notion."); + return pages; +} + +export async function deleteNotionPageFromCache(env, notionId) { + const cachedPages = await getPages(env); + const newCache = cachedPages.filter((b) => b.notionId !== notionId); + await storePages(env, newCache); + console.log("Deleted page from cache."); + return newCache; +} diff --git a/src/objects/positions.js b/src/objects/positions.js new file mode 100644 index 0000000..941585a --- /dev/null +++ b/src/objects/positions.js @@ -0,0 +1,50 @@ +import { queryNotionDataSource, getNotionPage } from "../utils/notion.js"; +import { + getPositions, + storePositions, + transformNotionPosition, +} from "../utils/positions.js"; +import { unionBy } from "lodash"; +import { getCompanies } from "../utils/companies.js"; + +export async function importNotionPositions(env, notionId = null) { + console.log("Importing Positions from Notion..."); + + let PositionsData = []; + if (notionId !== null) { + PositionsData = [await getNotionPage(notionId)]; + } else { + PositionsData = await queryNotionDataSource(env.POSITIONS_DB); + } + + const companiesData = await getCompanies(env); + + const Positions = ( + await Promise.all( + PositionsData.map(async (position) => + transformNotionPosition(env, position, companiesData) + ) + ) + ).filter(Boolean); + + // If notionId is not null, use lodash unionBy to replace existing Positions + if (notionId !== null) { + const cachedPositions = await getPositions(env); + const mergedPositions = unionBy(Positions, cachedPositions, "notionId"); + await storePositions(env, mergedPositions); + console.log("Imported Positions from Notion and merged with cache."); + return mergedPositions; + } + + await storePositions(env, Positions); + console.log("Imported Positions from Notion."); + return Positions; +} + +export async function deleteNotionPositionFromCache(env, notionId) { + const cachedPositions = await getPositions(env); + const newCache = cachedPositions.filter((p) => p.notionId !== notionId); + await storePositions(env, newCache); + console.log("Deleted position from cache."); + return newCache; +} diff --git a/src/objects/projects.js b/src/objects/projects.js new file mode 100644 index 0000000..5c9cbd2 --- /dev/null +++ b/src/objects/projects.js @@ -0,0 +1,54 @@ +import { + buildListCache, + queryNotionDataSource, + getNotionPage, +} from "../utils/notion.js"; +import { + getProjects, + storeProjects, + transformNotionProject, +} from "../utils/projects.js"; +import { unionBy } from "lodash"; +import { getSettings } from "../utils/settings.js"; + +export async function importNotionProjects(env, notionId = null) { + console.log("Importing Projects from Notion..."); + + let ProjectsData = []; + if (notionId !== null) { + ProjectsData = [await getNotionPage(notionId)]; + } else { + ProjectsData = await queryNotionDataSource(env.PROJECTS_DB); + } + + const settingsData = await getSettings(env); + + const Projects = ( + await Promise.all( + ProjectsData.map(async (project) => + transformNotionProject(env, project, settingsData) + ) + ) + ).filter(Boolean); + + // If notionId is not null, use lodash unionBy to replace existing Projects + if (notionId !== null) { + const cachedProjects = await getProjects(env); + const mergedProjects = unionBy(Projects, cachedProjects, "notionId"); + await storeProjects(env, mergedProjects); + console.log("Imported Projects from Notion and merged with cache."); + return mergedProjects; + } + + await storeProjects(env, Projects); + console.log("Imported Projects from Notion."); + return Projects; +} + +export async function deleteNotionProjectFromCache(env, notionId) { + const cachedProjects = await getProjects(env); + const newCache = cachedProjects.filter((p) => p.notionId !== notionId); + await storeProjects(env, newCache); + console.log("Deleted project from cache."); + return newCache; +} diff --git a/src/objects/settings.js b/src/objects/settings.js new file mode 100644 index 0000000..51dd3dc --- /dev/null +++ b/src/objects/settings.js @@ -0,0 +1,34 @@ +import { queryNotionDataSource } from "../utils/notion.js"; +import { + transformThemes, + transformRedirectsData, + transformGlobalThemesData, + storeSettings, +} from "../utils/settings.js"; + +export async function importNotionSettings(env) { + console.log("Importing settings from Notion..."); + const [globalThemesData, themesData, redirectsData] = await Promise.all([ + queryNotionDataSource(env.GLOBAL_THEMES_DB), + queryNotionDataSource(env.THEMES_DB), + queryNotionDataSource(env.REDIRECTS_DB), + ]); + + console.log("Fetched settings from Notion."); + + const themes = await transformThemes(themesData); + const globalThemes = transformGlobalThemesData(env, globalThemesData, themes); + const redirects = await transformRedirectsData(env, redirectsData); + + await storeSettings(env, { + globalThemes, + themes, + redirects, + }); + + return { + globalThemes, + themes, + redirects, + }; +} diff --git a/src/objects/videos.js b/src/objects/videos.js new file mode 100644 index 0000000..bc87922 --- /dev/null +++ b/src/objects/videos.js @@ -0,0 +1,24 @@ +import { getPages } from "../utils/pages.js"; +import { getBlogs } from "../utils/blogs.js"; +import { getProjects } from "../utils/projects.js"; +import { getCompanies } from "../utils/companies.js"; +import { collectVideoUrls, updateVideos } from "../utils/videoCache.js"; + +export async function importVideos(env) { + console.log("Importing videos from Notion..."); + const pages = (await getPages(env)) || []; + const blogs = (await getBlogs(env)) || []; + const projects = (await getProjects(env)) || []; + const companies = (await getCompanies(env)) || []; + + const videoUrls = collectVideoUrls([ + ...pages, + ...blogs, + ...projects, + ...companies, + ]); + + const updatedVideos = await updateVideos(env, videoUrls); + console.log("Imported videos from Notion."); + return updatedVideos; +} diff --git a/src/routes/blurHash.js b/src/routes/blurHash.js new file mode 100644 index 0000000..c1a803e --- /dev/null +++ b/src/routes/blurHash.js @@ -0,0 +1,24 @@ +export async function handleProcessBlurhash(env) { + try { + const imagesData = await getImages(env); + + if (imagesData) { + console.log("Found cached content!"); + await processBlurhashes(imagesData); + } else { + console.log("No images found in cache!"); + } + } catch (error) { + console.error("Error in handleProcessBlurhash:", error); + return new Response( + JSON.stringify({ + error: "Failed to process blurhash!", + message: error.message, + }), + { + status: 500, + headers: globalHeaders, + } + ); + } +} diff --git a/src/routes/contact.js b/src/routes/contact.js new file mode 100644 index 0000000..a92dede --- /dev/null +++ b/src/routes/contact.js @@ -0,0 +1,85 @@ +import { globalHeaders } from "../utils/api.js"; +import { addToNotionDataSource } from "../utils/notion.js"; +import { env } from "cloudflare:workers"; + +const TURNSTILE_ENABLED = true; + +export async function handleContactRequest(request, env) { + const { name, email, message, token } = await request.json(); + if (!email || !token || !message || !name) { + return new Response( + JSON.stringify({ + message: "Email, message, name and token are required", + code: "missing-fields", + }), + { + status: 400, + headers: globalHeaders, + }, + ); + } + + // Extract the IP address from the request headers + const ip = request.headers.get("CF-Connecting-IP"); + + if (TURNSTILE_ENABLED) { + // Verify the Turnstile token + const verificationResponse = await fetch( + "https://challenges.cloudflare.com/turnstile/v0/siteverify", + { + method: "POST", + headers: globalHeaders, + body: JSON.stringify({ + secret: env.TURNSTILE_AUTH, + response: token, + }), + }, + ); + + const verificationData = await verificationResponse.json(); + if (!verificationData.success) { + const code = verificationData["error-codes"][0]; + var errorMessage = "Captcha error."; + if (code == "timeout-or-duplicate") { + errorMessage = "Captcha expired."; + } + return new Response( + JSON.stringify({ message: errorMessage, code: `captcha-${code}` }), + { + status: 400, + headers: globalHeaders, + }, + ); + } + } + + // Add the email and location to Notion + try { + await addToNotionDataSource( + { + Name: name, + Email: email, + Message: message, + ["IP Address"]: ip, + }, + env.MESSAGES_DB, + ); + return new Response( + JSON.stringify({ + success: true, + message: "Email processed and added to Notion", + }), + { + headers: globalHeaders, + }, + ); + } catch (error) { + return new Response( + JSON.stringify({ message: "Error storing data.", code: "storage-error" }), + { + status: 500, + headers: globalHeaders, + }, + ); + } +} diff --git a/src/routes/content.js b/src/routes/content.js new file mode 100644 index 0000000..d35c03e --- /dev/null +++ b/src/routes/content.js @@ -0,0 +1,92 @@ +import { globalHeaders } from "../utils/api.js"; +import { getCombinedCachedContent } from "../utils/contentCache.js"; +import { importNotionBlogs } from "../objects/blogs.js"; +import { importNotionPages } from "../objects/pages.js"; +import { importNotionSettings } from "../objects/settings.js"; +import { importNotionNavigation } from "../objects/navigation.js"; +import { importNotionPositions } from "../objects/positions.js"; +import { importNotionCompanies } from "../objects/companies.js"; +import { importImages } from "../objects/images.js"; +import { importNotionCvs } from "../objects/cv.js"; +import { importFiles } from "../objects/files.js"; +import { importVideos } from "../objects/videos.js"; + +// Fetch or return cached content +export async function getCachedContent(env) { + // Try to get combined cached content + var cachedContent = await getCombinedCachedContent(env); + + const noBlogs = cachedContent.blogs?.length === 0; + const noPages = cachedContent.pages?.length === 0; + const noSettings = cachedContent.settings == {}; + const noCompanies = cachedContent.companies?.length === 0; + const noCvs = cachedContent.cvs?.length === 0; + const noPositions = cachedContent.positions?.length === 0; + + if (noBlogs || noPages || noSettings || noCompanies || noCvs || noPositions) { + await importNotionNavigation(env); + } + + if (noBlogs) { + cachedContent.blogs = await importNotionBlogs(env); + cachedContent.images = await importImages(env); + cachedContent.files = await importFiles(env); + cachedContent.videos = await importVideos(env); + } + + if (noPages) { + cachedContent.pages = await importNotionPages(env); + cachedContent.images = await importImages(env); + cachedContent.files = await importFiles(env); + cachedContent.videos = await importVideos(env); + } + + if (noSettings) { + cachedContent.settings = await importNotionSettings(env); + } + + if (noCvs) { + cachedContent.cvs = await importNotionCvs(env); + cachedContent.files = await importFiles(env); + } + + if (noCompanies) { + await importNotionPositions(env); + cachedContent.companies = await importNotionCompanies(env); + cachedContent.images = await importImages(env); + cachedContent.files = await importFiles(env); + cachedContent.videos = await importVideos(env); + } + + if (cachedContent) { + return new Response( + JSON.stringify({ + ...cachedContent, + blogs: cachedContent.blogs.filter((blog) => blog.published == true), + }), + { + headers: globalHeaders, + } + ); + } + + return new Response(JSON.stringify({}), { headers: globalHeaders }); +} + +export async function handleContentRequest(request, env) { + try { + return await getCachedContent(env); + } catch (error) { + console.error("Error handling content request:", error); + return new Response( + JSON.stringify({ + error: "Failed to fetch content", + message: error.message, + }), + { + status: 500, + headers: globalHeaders, + } + ); + } +} diff --git a/src/routes/hooks.js b/src/routes/hooks.js new file mode 100644 index 0000000..10908b3 --- /dev/null +++ b/src/routes/hooks.js @@ -0,0 +1,277 @@ +import { importNotionNavigation } from "../objects/navigation"; +import { importNotionSettings } from "../objects/settings"; +import { importNotionPages, deleteNotionPageFromCache } from "../objects/pages"; +import { importNotionBlogs, deleteNotionBlogFromCache } from "../objects/blogs"; +import { globalHeaders } from "../utils/api"; +import { importImages } from "../objects/images"; +import { importFiles } from "../objects/files"; +import { importVideos } from "../objects/videos"; +import { handleBlurhashUpdate } from "../utils/imageCache"; +import { importNotionProjects } from "../objects/projects"; +import { importNotionCompanies } from "../objects/companies"; +import { importNotionPositions } from "../objects/positions"; +import { importNotionCvs } from "../objects/cv"; + +async function updateAllNotionData(env) { + await importNotionNavigation(env); + const settings = await importNotionSettings(env); + const pages = await importNotionPages(env); + const blogs = await importNotionBlogs(env); + const bookings = await importNotionBookings(env); + const projects = await importNotionProjects(env); + + const cvs = await importNotionCvs(env); + return { settings, pages, blogs, bookings, projects, cvs }; +} + +export async function handleNotionHook(request, env) { + try { + const body = await request.json(); + if (body?.verification_token) { + console.log("Verification token received:", body.verification_token); + return new Response( + JSON.stringify({ + status: "gotVerificationToken", + }), + { headers: globalHeaders } + ); + } + console.log("Notion hook received:", body.type); + if ( + body.type == "page.properties_updated" || + body.type == "page.content_updated" || + (body.type == "page.created" && body.data?.parent?.data_source_id) + ) { + const dataSourceId = body.data.parent.data_source_id; + const entityId = body?.entity?.id || null; + console.log("Data source ID:", dataSourceId); + + if (dataSourceId === env.PAGES_DB) { + console.log("Importing pages from Notion..."); + await importNotionNavigation(env); + const pages = await importNotionPages(env, entityId); + const images = await importImages(env); + await handleBlurhashUpdate(request, env); + return new Response( + JSON.stringify({ + status: "OK", + content: { pages, images }, + }), + { + headers: globalHeaders, + } + ); + } + if (dataSourceId === env.BLOGS_DB) { + await importNotionNavigation(env); + const blogs = await importNotionBlogs(env, entityId); + const images = await importImages(env); + const files = await importFiles(env); + const videos = await importVideos(env); + await handleBlurhashUpdate(request, env); + return new Response( + JSON.stringify({ + status: "OK", + content: { blogs, images, files, videos }, + }), + { + headers: globalHeaders, + } + ); + } + if (dataSourceId === env.PROJECTS_DB) { + await importNotionNavigation(env); + const projects = await importNotionProjects(env, entityId); + const images = await importImages(env); + const files = await importFiles(env); + const videos = await importVideos(env); + await handleBlurhashUpdate(request, env); + return new Response( + JSON.stringify({ + status: "OK", + content: { projects, images, files, videos }, + }), + { + headers: globalHeaders, + } + ); + } + if (dataSourceId === env.COMPANIES_DB) { + const positions = await importNotionPositions(env); + const companies = await importNotionCompanies(env, entityId); + const images = await importImages(env); + const files = await importFiles(env); + const videos = await importVideos(env); + await handleBlurhashUpdate(request, env); + return new Response( + JSON.stringify({ + status: "OK", + content: { companies, images, positions, files, videos }, + }), + { + headers: globalHeaders, + } + ); + } + if (dataSourceId === env.POSITIONS_DB) { + const positions = await importNotionPositions(env, entityId); + const companies = await importNotionCompanies(env); + const images = await importImages(env); + await handleBlurhashUpdate(request, env); + const files = await importFiles(env); + const videos = await importVideos(env); + return new Response( + JSON.stringify({ + status: "OK", + content: { positions, companies, images, files, videos }, + }), + { + headers: globalHeaders, + } + ); + } + if (dataSourceId === env.CV_DB) { + const cvs = await importNotionCvs(env, entityId); + const files = await importFiles(env); + return new Response( + JSON.stringify({ status: "OK", content: { cvs, files } }), + { + headers: globalHeaders, + } + ); + } + if (dataSourceId === env.THEMES_DB) { + const settings = await importNotionSettings(env); + return new Response( + JSON.stringify({ status: "OK", content: { settings } }), + { + headers: globalHeaders, + } + ); + } + if (dataSourceId === env.REDIRECTS_DB) { + const redirects = await importNotionSettings(env); + return new Response( + JSON.stringify({ status: "OK", content: { redirects } }), + { + headers: globalHeaders, + } + ); + } + if (dataSourceId === env.BRANDING_DB) { + const branding = await importNotionSettings(env); + return new Response( + JSON.stringify({ status: "OK", content: { branding } }), + { + headers: globalHeaders, + } + ); + } + console.log("Page Blogs with data source:", dataSourceId); + } + if ( + body.type == "page.properties_updated" || + (body.type == "page.deleted" && body.data?.parent?.data_source_id) + ) { + const dataSourceId = body.data.parent.data_source_id; + const entityId = body?.entity?.id || null; + console.log("Data source ID:", dataSourceId); + console.log("Entity ID:", entityId); + + switch (dataSourceId) { + case env.PAGES_DB: + const pages = await deleteNotionPageFromCache(env, entityId); + await importNotionNavigation(env); + return new Response( + JSON.stringify({ status: "OK", content: { pages } }), + { + headers: globalHeaders, + } + ); + case env.BLOGS_DB: + const blogs = await deleteNotionBlogFromCache(env, entityId); + await importNotionNavigation(env); + return new Response( + JSON.stringify({ status: "OK", content: { blogs } }), + { + headers: globalHeaders, + } + ); + case env.COMPANIES_DB: + const companies = await deleteNotionCompanyFromCache(env, entityId); + return new Response( + JSON.stringify({ status: "OK", content: { companies } }), + { + headers: globalHeaders, + } + ); + case env.POSITIONS_DB: + const positions = await deleteNotionPositionFromCache(env, entityId); + await importNotionCompanies(env); + return new Response( + JSON.stringify({ status: "OK", content: { positions } }), + { + headers: globalHeaders, + } + ); + case env.PROJECTS_DB: + const projects = await deleteNotionProjectFromCache(env, entityId); + return new Response( + JSON.stringify({ status: "OK", content: { projects } }), + { + headers: globalHeaders, + } + ); + case env.CV_DB: + const cvs = await deleteNotionCvFromCache(env, entityId); + return new Response( + JSON.stringify({ status: "OK", content: { cvs } }), + { + headers: globalHeaders, + } + ); + case env.THEMES_DB: + const settings = await importNotionSettings(env); + return new Response( + JSON.stringify({ status: "OK", content: { settings } }), + { + headers: globalHeaders, + } + ); + case env.REDIRECTS_DB: + const redirects = await importNotionSettings(env); + return new Response( + JSON.stringify({ status: "OK", content: { redirects } }), + { + headers: globalHeaders, + } + ); + case env.BRANDING_DB: + const branding = await importNotionSettings(env); + return new Response( + JSON.stringify({ status: "OK", content: { branding } }), + { + headers: globalHeaders, + } + ); + } + console.log("Page Blogs with data source:", dataSourceId); + } + + console.log("Unknown hook:", body.type); + + return new Response( + JSON.stringify({ status: "Unknown hook", type: body.type }), + { + headers: globalHeaders, + } + ); + } catch (error) { + throw error; + console.log("No body to parse. Calling both fetch requests..."); + const content = await updateAllNotionData(env); + return new Response(JSON.stringify({ status: "OK", content }), { + headers: globalHeaders, + }); + } +} diff --git a/src/utils/api.js b/src/utils/api.js new file mode 100644 index 0000000..a1cf5a6 --- /dev/null +++ b/src/utils/api.js @@ -0,0 +1,6 @@ +import { env } from "cloudflare:workers"; + +export const globalHeaders = { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": env.CORS_ORIGIN, +}; diff --git a/src/utils/blogs.js b/src/utils/blogs.js new file mode 100644 index 0000000..6976975 --- /dev/null +++ b/src/utils/blogs.js @@ -0,0 +1,203 @@ +import { extractRichText, getPlainText, transformContent } from "./notion.js"; +import { addToNotionDataSource, updateNotionPage } from "./notion.js"; +import _ from "lodash"; +import diff from "microdiff"; +import { getNavigation } from "../utils/navigation.js"; +import dayjs from "dayjs"; + +export async function getBlogs(env, cached = false) { + try { + // If cached=true, try to get from Cloudflare Cache API first + if (cached) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/Blogs` + ); + const cachedResponse = await cache.match(cacheKey); + + if (cachedResponse) { + const cachedData = await cachedResponse.json(); + console.log("Blogs retrieved from Cloudflare cache"); + return cachedData; + } + } catch (cacheError) { + console.log("Cache miss or error, falling back to KV:", cacheError); + } + } + + // Fall back to KV storage + const kvData = await env.CONTENT_KV.get(env.BLOGS_KEY, { + type: "json", + }); + + // If we were trying to use cache but it failed, store the KV data in cache + if (cached && kvData) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/Blogs` + ); + + const response = new Response(JSON.stringify(kvData), { + headers: { + "Content-Type": "application/json", + "Cache-Control": "max-age=60", // 1 minute TTL + ETag: `"Blogs-${Date.now()}"`, // Add ETag for cache validation + }, + }); + + await cache.put(cacheKey, response); + console.log("Blogs stored in Cloudflare cache after KV fallback"); + } catch (cacheError) { + console.warn("Error storing in cache after KV fallback:", cacheError); + } + } + + return kvData || []; + } catch (error) { + console.log("Error fetching Blogs cache:", error); + return null; + } +} + +export async function storeBlogs(env, Blogs) { + try { + // Always store in KV first + await env.CONTENT_KV.put(env.BLOGS_KEY, JSON.stringify(Blogs)); + console.log("Blogs stored in KV."); + + // Purge the Cloudflare Cache API + try { + const cache = caches.default; + const cacheKey = new Request(`https://${env.CACHE_URL || "cache"}/Blogs`); + + await cache.delete(cacheKey); + console.log("Blogs cache purged successfully."); + } catch (cacheError) { + console.warn( + "Error purging Cloudflare cache, but KV was updated successfully:", + cacheError + ); + } + } catch (error) { + console.error("Error storing Blogs cache:", error); + throw error; + } +} + +export function diffBlogs(newList, oldList) { + // Keys to check for changes (set to null to check all keys) + const keysToCheck = [ + "name", + "maxOccupancy", + "bedrooms", + "bathrooms", + "address", + "features", + "minPrice", + "maxPrice", + "timezone", + "doubleBeds", + "singleBeds", + "sofaBeds", + "couches", + "childBeds", + "queenSizeBeds", + "kingSizeBeds", + ]; + + // Helper: index by smoobuId using lodash + const oldById = _.keyBy(oldList || [], "smoobuId"); + const newById = _.keyBy(newList || [], "smoobuId"); + + // toAdd: in newList but not in oldList + const toAdd = newList.filter((p) => !oldById[p.smoobuId]); + + // toDelete: in oldList but not in newList + const toDelete = oldList.filter((p) => !newById[p.smoobuId]); + + // toUpdate: in both, but with different content (using microdiff) + const toUpdate = newList.filter((p) => { + const old = oldById[p.smoobuId]; + if (!old) return false; + + // If syncName is false, exclude name from comparison + const { smoobuId, notionId, name, ...restNew } = p; + const { smoobuId: _, notionId: __, name: oldName, ...restOld } = old; + + // Prepare objects for comparison + let newObj, oldObj; + + if (old.syncName === false) { + // Don't compare names when syncName is false + newObj = restNew; + oldObj = restOld; + } else { + // Include name in comparison when syncName is true or undefined + newObj = { name, ...restNew }; + oldObj = { name: oldName, ...restOld }; + } + + // Use microdiff to get differences + const differences = diff(oldObj, newObj); + + // If no keysToCheck specified, return true if any differences found + if (!keysToCheck) { + return differences.length > 0; + } + + // Filter differences to only include keys we care about + const relevantDifferences = differences.filter((change) => { + // Check if the changed path starts with any of our keysToCheck + const path = change.path.join("."); + return keysToCheck.some((key) => path.startsWith(key)); + }); + + return relevantDifferences.length > 0; + }); + + return { toAdd, toUpdate, toDelete }; +} + +// Transform Notion blog data to desired format +export async function transformNotionBlog(env, notionBlog) { + const navigationItems = await getNavigation(env); + + const properties = notionBlog.properties; + console.log("Properties:", properties); + + const published = properties["Published"].checkbox || false; + + const dateRaw = properties["Date"].date; + const date = dateRaw?.start + ? dayjs(dateRaw.start).format("DD/MM/YY h:mma") + : null; + + // Extract image URLs (handle both external and file images) + const images = (properties["Images"]?.files || []) + .map((f) => f?.external?.url || f?.file?.url) + .filter(Boolean); + + // Extract slug from formula + const slug = properties.Slug?.formula?.string || "unknown"; + + const subTitle = getPlainText(properties["Subtitle"]); + + // Extract name from title + const name = properties.Name?.title?.[0]?.plain_text || "Untitled"; + + // Fetch and transform the actual page content from Notion blocks + const content = await transformContent(env, notionBlog.id, navigationItems); + + return { + notionId: notionBlog.id, + name: name, + subTitle: subTitle, + images: images, + slug: slug, + date: date, + content, + published, + }; +} diff --git a/src/utils/blurHash.js b/src/utils/blurHash.js new file mode 100644 index 0000000..d556661 --- /dev/null +++ b/src/utils/blurHash.js @@ -0,0 +1,110 @@ +import { encode } from "blurhash"; +import JPEG from "jpeg-js"; +import { env } from "cloudflare:workers"; + +// Function to decode JPEG image data using jpeg-js (Cloudflare Workers compatible) +export function decodeJPEG(arrayBuffer) { + try { + const buffer = new Uint8Array(arrayBuffer); + + console.log("Decoding JPG..."); + const jpeg = JPEG.decode(buffer, { useTArray: true }); + console.log("Decoded JPG."); + + if (!jpeg) { + throw new Error("Failed to decode JPEG"); + } + + return { + data: jpeg.data, + width: jpeg.width, + height: jpeg.height, + }; + } catch (error) { + throw new Error(`JPEG decode error: ${error.message}`); + } +} + +// Function to detect image type from URL or content (only JPEG) +export function detectImageType(url) { + const urlLower = url.toLowerCase(); + if (urlLower.includes(".jpg") || urlLower.includes(".jpeg")) { + return "jpeg"; + } + + // Default to JPEG for backward compatibility + return "jpeg"; +} + +// Function to fetch an image and generate a blur hash (no caching, JPEG only) +export async function fetchBlurHash(url) { + try { + if (env.BLUR_HASH === "false") { + console.log("Blur hash disabled"); + return "LDMZ?kMaD$?w.9R:NIIU=V?bw[RP"; + } + + console.log("Fetching URL for blur hash:", url); + const options = { + cf: { + image: { + compression: "fast", + width: 100, + height: 100, + fit: "cover", + quality: 80, + format: "jpeg", + }, + }, + }; + + const response = await fetch(url, options); + console.log("Fetch complete."); + + if (!response.ok) { + console.log("Failed to fetch image:", response.status); + return null; + } + + let arrayBuffer = await response.arrayBuffer(); + + const transformed = await env.IMAGES.input(arrayBuffer) + .transform({ width: 128, height: 128 }) // can combine width/height + .output({ format: "image/jpeg" }); + + const compressedArrayBuffer = + (await transformed.arrayBuffer?.()) || + (await transformed.response().arrayBuffer()); + + const fileSizeMB = ( + compressedArrayBuffer.byteLength / + (1024 * 1024) + ).toFixed(2); + console.log( + "File size:", + compressedArrayBuffer.byteLength, + "bytes (", + fileSizeMB, + "MB)", + ); + + // Decode JPEG + const decodedImage = decodeJPEG(compressedArrayBuffer); + const { data, width, height } = decodedImage; + + // Generate blur hash + console.log("Encoding blur hash..."); + const blurHash = encode(data, width, height, 4, 3); + console.log("Encoded blur hash."); + + // Clear memory + console.log("Clearing memory..."); + data.fill(0); + arrayBuffer = null; + + return blurHash; + } catch (error) { + console.error(`Error generating blur hash for ${url}:`, error); + return null; + } +} diff --git a/src/utils/companies.js b/src/utils/companies.js new file mode 100644 index 0000000..be8dd6d --- /dev/null +++ b/src/utils/companies.js @@ -0,0 +1,237 @@ +import { getItemByNotionId, transformContent, sortDuration } from "./notion.js"; +import _ from "lodash"; +import diff from "microdiff"; +import { getNavigation } from "../utils/navigation.js"; +import { processNotionIcon } from "./icon.js"; + +export async function getCompanies(env, cached = false) { + try { + // If cached=true, try to get from Cloudflare Cache API first + if (cached) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/Companies` + ); + const cachedResponse = await cache.match(cacheKey); + + if (cachedResponse) { + const cachedData = await cachedResponse.json(); + console.log("Companies retrieved from Cloudflare cache"); + return cachedData; + } + } catch (cacheError) { + console.log("Cache miss or error, falling back to KV:", cacheError); + } + } + + // Fall back to KV storage + const kvData = await env.CONTENT_KV.get(env.COMPANIES_KEY, { + type: "json", + }); + + // If we were trying to use cache but it failed, store the KV data in cache + if (cached && kvData) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/Companies` + ); + + const response = new Response(JSON.stringify(kvData), { + headers: { + "Content-Type": "application/json", + "Cache-Control": "max-age=60", // 1 minute TTL + ETag: `"Companies-${Date.now()}"`, // Add ETag for cache validation + }, + }); + + await cache.put(cacheKey, response); + console.log("Companies stored in Cloudflare cache after KV fallback"); + } catch (cacheError) { + console.warn("Error storing in cache after KV fallback:", cacheError); + } + } + + return kvData || []; + } catch (error) { + console.log("Error fetching Companies cache:", error); + return null; + } +} + +export async function storeCompanies(env, Companies) { + try { + // Always store in KV first + await env.CONTENT_KV.put(env.COMPANIES_KEY, JSON.stringify(Companies)); + console.log("Companies stored in KV."); + + // Purge the Cloudflare Cache API + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/Companies` + ); + + await cache.delete(cacheKey); + console.log("Companies cache purged successfully."); + } catch (cacheError) { + console.warn( + "Error purging Cloudflare cache, but KV was updated successfully:", + cacheError + ); + } + } catch (error) { + console.error("Error storing Companies cache:", error); + throw error; + } +} + +export function diffCompanies(newList, oldList) { + // Keys to check for changes + const keysToCheck = [ + "name", + "theme", + "published", + "logo", + "image", + "content", + ]; + + // Helper: index by notionId using lodash + const oldById = _.keyBy(oldList || [], "notionId"); + const newById = _.keyBy(newList || [], "notionId"); + + // toAdd: in newList but not in oldList + const toAdd = newList.filter((p) => !oldById[p.notionId]); + + // toDelete: in oldList but not in newList + const toDelete = oldList.filter((p) => !newById[p.notionId]); + + // toUpdate: in both, but with different content (using microdiff) + const toUpdate = newList.filter((p) => { + const old = oldById[p.notionId]; + if (!old) return false; + + // Prepare objects for comparison + const { notionId, ...restNew } = p; + const { notionId: _, ...restOld } = old; + + // Use microdiff to get differences + const differences = diff(restOld, restNew); + + // Filter differences to only include keys we care about + const relevantDifferences = differences.filter((change) => { + // Check if the changed path starts with any of our keysToCheck + const path = change.path.join("."); + return keysToCheck.some((key) => path.startsWith(key)); + }); + + return relevantDifferences.length > 0; + }); + + return { toAdd, toUpdate, toDelete }; +} + +// Transform Notion company data to desired format +export async function transformNotionCompany( + env, + notionCompany, + settingsData, + positionsData +) { + const navigationItems = await getNavigation(env); + + const properties = notionCompany.properties; + + // Extract theme information + let theme = undefined; // default + if (properties.Theme?.relation?.[0]?.id) { + const themeId = properties.Theme.relation[0].id; + const themeData = getItemByNotionId(settingsData.themes, themeId); + if (themeData?.name) { + theme = themeData.name; + } + } + + const published = properties["Published"]?.checkbox || false; + + // Extract external link + const externalLink = properties["External Link"]?.url || null; + + // Extract image URL (handle both external and file images) + const imageFile = notionCompany?.cover; + const image = imageFile?.external?.url || imageFile?.file?.url || null; + + // Extract logo URL (handle both external and file images) + const logoFiles = properties["Logo"]?.files || []; + const logo = logoFiles[0]?.file + ? await processNotionIcon(logoFiles[0].file, "tb-company-logo") + : undefined; + + // Extract name from title + const name = properties.Name?.title?.[0]?.plain_text || "Untitled"; + + // Extract slug from formula + const slug = properties.Slug?.formula?.string || "unknown"; + + // Fetch and transform the actual page content from Notion blocks + const content = [{ type: "image", url: image }]; + const companyContent = await transformContent( + env, + notionCompany.id, + navigationItems + ); + content.push(...companyContent); + + const filteredPositions = positionsData + .filter((position) => position.company === notionCompany.id) + .sort(sortDuration); + + // Add positions timeline at the end if positions are provided + if (filteredPositions && filteredPositions.length > 0) { + // Filter positions for this company based on company name + + const timelinePositions = filteredPositions.map((position) => ({ + type: "positionTimelineItem", + name: position.name, + duration: position.duration, + content: position?.content || [], + })); + + if (timelinePositions.length > 0) { + content.push({ + type: "positionsTimeline", + children: timelinePositions, + }); + } + } + + if (published == false) { + return null; + } + + const positionsList = filteredPositions.map((position) => ({ + name: position.name, + notionId: position.notionId, + })); + + const duration = { + start: filteredPositions[filteredPositions.length - 1]?.duration?.start, + end: filteredPositions[0]?.duration?.end, + }; + + return { + notionId: notionCompany.id, + name: name, + theme: theme, + published, + logo: logo, + image: image, + slug: slug, + positions: positionsList, + externalLink: externalLink, + duration: duration, + content, + }; +} diff --git a/src/utils/contentCache.js b/src/utils/contentCache.js new file mode 100644 index 0000000..f810691 --- /dev/null +++ b/src/utils/contentCache.js @@ -0,0 +1,44 @@ +import { getImages } from "./imageCache.js"; +import { getPages } from "./pages.js"; +import { getBlogs } from "./blogs.js"; +import { getSettings } from "./settings.js"; +import { getProjects } from "./projects.js"; +import { getCompanies } from "./companies.js"; +import { getCvs } from "./cv.js"; +import { getFiles } from "./fileCache.js"; +import { getVideos } from "./videoCache.js"; + +export async function getCombinedCachedContent(env) { + try { + // Get current images from image cache + const cachedImages = await getImages(env, true); + const cachedFiles = await getFiles(env, true); + const cachedVideos = await getVideos(env, true); + const cachedPages = await getPages(env, true); + const cachedBlogs = await getBlogs(env, true); + const cachedSettings = await getSettings(env, true); + const cachedProjects = await getProjects(env, true); + const cachedCompanies = await getCompanies(env, true); + const cachedCvs = await getCvs(env, true); + // Combine content with current images + const content = { + pages: cachedPages, + blogs: cachedBlogs, + settings: cachedSettings, + images: cachedImages, + files: cachedFiles, + videos: cachedVideos, + projects: cachedProjects, + companies: cachedCompanies, + cvs: cachedCvs, + }; + + console.log("Serving content..."); + return content; + + return null; + } catch (error) { + console.log("Error getting combined cached content:", error); + return null; + } +} diff --git a/src/utils/cv.js b/src/utils/cv.js new file mode 100644 index 0000000..435efd2 --- /dev/null +++ b/src/utils/cv.js @@ -0,0 +1,154 @@ +import { getPlainText, toCamelCase } from "./notion.js"; +import _ from "lodash"; +import diff from "microdiff"; + +export async function getCvs(env, cached = false) { + try { + // If cached=true, try to get from Cloudflare Cache API first + if (cached) { + try { + const cache = caches.default; + const cacheKey = new Request(`https://${env.CACHE_URL || "cache"}/CVs`); + const cachedResponse = await cache.match(cacheKey); + + if (cachedResponse) { + const cachedData = await cachedResponse.json(); + console.log("CVs retrieved from Cloudflare cache"); + return cachedData; + } + } catch (cacheError) { + console.log("Cache miss or error, falling back to KV:", cacheError); + } + } + + // Fall back to KV storage + const kvData = await env.CONTENT_KV.get(env.CV_KEY, { + type: "json", + }); + + // If we were trying to use cache but it failed, store the KV data in cache + if (cached && kvData) { + try { + const cache = caches.default; + const cacheKey = new Request(`https://${env.CACHE_URL || "cache"}/CVs`); + + const response = new Response(JSON.stringify(kvData), { + headers: { + "Content-Type": "application/json", + "Cache-Control": "max-age=60", // 1 minute TTL + ETag: `"CVs-${Date.now()}"`, // Add ETag for cache validation + }, + }); + + await cache.put(cacheKey, response); + console.log("CVs stored in Cloudflare cache after KV fallback"); + } catch (cacheError) { + console.warn("Error storing in cache after KV fallback:", cacheError); + } + } + + return kvData || []; + } catch (error) { + console.log("Error fetching CVs cache:", error); + return null; + } +} + +export async function storeCvs(env, Cvs) { + try { + // Always store in KV first + await env.CONTENT_KV.put(env.CV_KEY, JSON.stringify(Cvs)); + console.log("CVs stored in KV."); + + // Purge the Cloudflare Cache API + try { + const cache = caches.default; + const cacheKey = new Request(`https://${env.CACHE_URL || "cache"}/CVs`); + + await cache.delete(cacheKey); + console.log("CVs cache purged successfully."); + } catch (cacheError) { + console.warn( + "Error purging Cloudflare cache, but KV was updated successfully:", + cacheError + ); + } + } catch (error) { + console.error("Error storing CVs cache:", error); + throw error; + } +} + +export function diffCvs(newList, oldList) { + // Keys to check for changes + const keysToCheck = ["version", "type", "published", "files"]; + + // Helper: index by notionId using lodash + const oldById = _.keyBy(oldList || [], "notionId"); + const newById = _.keyBy(newList || [], "notionId"); + + // toAdd: in newList but not in oldList + const toAdd = newList.filter((p) => !oldById[p.notionId]); + + // toDelete: in oldList but not in newList + const toDelete = oldList.filter((p) => !newById[p.notionId]); + + // toUpdate: in both, but with different content (using microdiff) + const toUpdate = newList.filter((p) => { + const old = oldById[p.notionId]; + if (!old) return false; + + // Prepare objects for comparison + const { notionId, ...restNew } = p; + const { notionId: _, ...restOld } = old; + + // Use microdiff to get differences + const differences = diff(restOld, restNew); + + // Filter differences to only include keys we care about + const relevantDifferences = differences.filter((change) => { + // Check if the changed path starts with any of our keysToCheck + const path = change.path.join("."); + return keysToCheck.some((key) => path.startsWith(key)); + }); + + return relevantDifferences.length > 0; + }); + + return { toAdd, toUpdate, toDelete }; +} + +// Transform Notion CV data to desired format +export async function transformNotionCv(env, notionCv) { + const properties = notionCv.properties; + + console.log("Notion CV:", notionCv); + + const published = properties["Published"]?.checkbox || false; + + // Extract version (text) + const version = getPlainText(properties["Version"]); + + // Extract type (select) + const type = toCamelCase(properties["Type"]?.select?.name || null); + + const date = properties["Date"]?.date?.start || undefined; + + // Extract files (files array) + const files = (properties["Files"]?.files || []) + .map((f) => f?.external?.url || f?.file?.url) + .filter(Boolean); + + if (published == false) { + return null; + } + + return { + notionId: notionCv.id, + version: version, + type: type, + published: published, + files: files, + date: date, + }; +} diff --git a/src/utils/fileCache.js b/src/utils/fileCache.js new file mode 100644 index 0000000..a82dae5 --- /dev/null +++ b/src/utils/fileCache.js @@ -0,0 +1,369 @@ +function generateFileId(url) { + const urlObj = new URL(url); + const origin = urlObj.origin; + const pathname = urlObj.pathname; + + const combined = `${origin}${pathname}`; + + let hash = 0; + for (let i = 0; i < combined.length; i++) { + const char = combined.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; + } + + const positiveHash = Math.abs(hash); + const base36Hash = positiveHash.toString(36); + + return base36Hash.padStart(8, "0").substring(0, 12); +} + +function generateR2KeyFromFileId(fileId, url) { + const urlObj = new URL(url); + const pathname = urlObj.pathname; + const filename = pathname.split("/").pop() || "file"; + const extension = filename.includes(".") ? filename.split(".").pop() : ""; + const baseFilename = filename.replace(/\.[^/.]+$/, ""); + + return extension + ? `files/${fileId}-${baseFilename}.${extension}` + : `files/${fileId}-${baseFilename}`; +} + +function extractR2KeyFromUrl(mirrorUrl) { + const urlObj = new URL(mirrorUrl); + return urlObj.pathname.substring(1); +} + +export async function getFiles(env, cached = false) { + try { + if (cached) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/files` + ); + const cachedResponse = await cache.match(cacheKey); + + if (cachedResponse) { + const cachedData = await cachedResponse.json(); + console.log("Files retrieved from Cloudflare cache"); + return cachedData; + } + } catch (cacheError) { + console.log( + "File cache miss or error, falling back to KV:", + cacheError + ); + } + } + + const kvData = await env.CONTENT_KV.get(env.FILES_KEY, { + type: "json", + }); + + if (cached && kvData) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/files` + ); + + const response = new Response(JSON.stringify(kvData), { + headers: { + "Content-Type": "application/json", + "Cache-Control": "max-age=60", + ETag: `"files-${Date.now()}"`, + }, + }); + + await cache.put(cacheKey, response); + console.log("Files stored in Cloudflare cache after KV fallback"); + } catch (cacheError) { + console.warn( + "Error storing files in cache after KV fallback:", + cacheError + ); + } + } + + return kvData || []; + } catch (error) { + console.log("Error fetching files cache:", error); + return null; + } +} + +export async function updateFiles(env, currentFileUrls) { + console.log("Updating file cache..."); + + const currentFiles = currentFileUrls.map((url) => ({ + id: generateFileId(url), + url, + })); + + const existingFiles = await getFiles(env); + const existingFileMap = new Map(existingFiles.map((file) => [file.id, file])); + + const filesToAdd = currentFiles.filter( + (file) => !existingFileMap.has(file.id) + ); + + const currentFileMap = new Map(currentFiles.map((file) => [file.id, file])); + const filesToKeep = existingFiles + .filter((file) => currentFileMap.has(file.id)) + .map((file) => ({ + ...file, + url: currentFileMap.get(file.id).url, + })); + + const filesToRemove = existingFiles.filter( + (file) => !currentFileMap.has(file.id) + ); + + console.log( + `Files to add: ${filesToAdd.length}, Files to keep: ${filesToKeep.length}, Files to remove: ${filesToRemove.length}` + ); + + for (const file of filesToRemove) { + if (file.mirrorUrl) { + try { + const key = extractR2KeyFromUrl(file.mirrorUrl); + await env.TB_STORAGE.delete(key); + console.log(`Deleted ${key} from R2 for file ID ${file.id}`); + } catch (error) { + console.error( + `Failed to delete ${file.mirrorUrl} from R2 for file ID ${file.id}:`, + error + ); + } + } + } + + const newFiles = []; + for (let i = 0; i < filesToAdd.length; i++) { + const fileData = filesToAdd[i]; + const { id, url } = fileData; + + console.log( + `Uploading file to R2 (${i + 1}/${filesToAdd.length}): ${url} (ID: ${id})` + ); + + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to fetch file: ${response.status}`); + } + + const contentType = response.headers.get("content-type") || null; + const contentLengthHeader = response.headers.get("content-length"); + const contentLength = contentLengthHeader + ? Number.parseInt(contentLengthHeader, 10) + : null; + + const urlObj = new URL(url); + const pathname = urlObj.pathname; + const filename = pathname.split("/").pop() || null; + + const key = generateR2KeyFromFileId(id, url); + + const putOptions = { + httpMetadata: {}, + }; + if (contentType) { + putOptions.httpMetadata.contentType = contentType; + } + if (Object.keys(putOptions.httpMetadata).length === 0) { + delete putOptions.httpMetadata; + } + + const result = await env.TB_STORAGE.put( + key, + response.body, + Object.keys(putOptions).length ? putOptions : undefined + ); + + if (result == null) { + throw new Error(`Failed to upload file to R2: ${key}`); + } + + const mirrorUrl = `${env.R2_PUBLIC_URL}/${key}`; + + const metadata = + contentType || contentLength || filename + ? { + contentType, + contentLength, + filename, + } + : null; + + newFiles.push({ + id, + url, + metadata, + mirrorUrl, + }); + + console.log( + `Successfully uploaded and mirrored file: ${url} -> ${mirrorUrl} (ID: ${id})` + ); + } catch (error) { + console.error(`Failed to upload ${url} to R2 (ID: ${id}):`, error); + newFiles.push({ + id, + url, + metadata: null, + mirrorUrl: null, + }); + } + + await new Promise((resolve) => setTimeout(resolve, 100)); + } + + const updatedFiles = [...filesToKeep, ...newFiles]; + + await storeFiles(env, updatedFiles); + + return updatedFiles; +} + +export async function storeFiles(env, files) { + try { + await env.CONTENT_KV.put(env.FILES_KEY, JSON.stringify(files)); + console.log("Files stored in KV."); + + try { + const cache = caches.default; + const cacheKey = new Request(`https://${env.CACHE_URL || "cache"}/files`); + + await cache.delete(cacheKey); + console.log("Files cache purged successfully."); + } catch (cacheError) { + console.warn( + "Error purging Cloudflare file cache, but KV was updated successfully:", + cacheError + ); + } + } catch (error) { + console.error("Error storing files cache:", error); + throw error; + } +} + +function findFileChildren(obj, fileUrls = []) { + if (!obj || typeof obj !== "object") { + return fileUrls; + } + + if (obj.type === "file") { + if (obj.url) { + fileUrls.push(obj.url); + } else if (obj.href) { + fileUrls.push(obj.href); + } else if (obj.file) { + fileUrls.push(obj.file); + } + } + + if (obj.children && Array.isArray(obj.children)) { + for (const child of obj.children) { + findFileChildren(child, fileUrls); + } + } + + for (const [key, value] of Object.entries(obj)) { + if ( + key !== "children" && + key !== "content" && + key !== "blocks" && + typeof value === "object" + ) { + findFileChildren(value, fileUrls); + } + } + + return fileUrls; +} + +export function collectFileUrls(contentObjects) { + const fileUrls = []; + + for (const object of contentObjects) { + if (object.file) { + fileUrls.push(object.file); + } + if (object.files) { + object.files.forEach((url) => fileUrls.push(url)); + } + if (object.content) { + findFileChildren(object.content, fileUrls); + } + } + + return fileUrls; +} + +export async function handleFileMetadataUpdate(request, env) { + const cachedFiles = await env.CONTENT_KV.get(env.FILES_KEY, { + type: "json", + }); + if (!cachedFiles) return; + + let updated = false; + + for (const file of cachedFiles) { + if ( + !file.metadata || + !file.metadata.contentType || + !file.metadata.contentLength + ) { + const targetUrl = file.mirrorUrl || file.url; + try { + const response = await fetch(targetUrl, { method: "HEAD" }); + if (response.ok) { + const contentType = response.headers.get("content-type") || null; + const contentLengthHeader = response.headers.get("content-length"); + const contentLength = contentLengthHeader + ? Number.parseInt(contentLengthHeader, 10) + : null; + + const urlObj = new URL(file.url); + const pathname = urlObj.pathname; + const filename = pathname.split("/").pop() || null; + + if (contentType || contentLength || filename) { + file.metadata = { + contentType, + contentLength, + filename, + }; + updated = true; + console.log( + `Updated metadata for file ID ${file.id} (${file.url})` + ); + } + } + } catch (error) { + console.warn( + `Failed to update metadata for file ID ${file.id} (${file.url}):`, + error + ); + } + } + } + + if (updated) { + await env.CONTENT_KV.put(env.FILES_KEY, JSON.stringify(cachedFiles)); + } + + return cachedFiles; +} + +export function getFileById(cachedFiles, fileId) { + return cachedFiles.find((file) => file.id === fileId); +} + +export function getFileIdFromUrl(url) { + return generateFileId(url); +} diff --git a/src/utils/icon.js b/src/utils/icon.js new file mode 100644 index 0000000..4a88402 --- /dev/null +++ b/src/utils/icon.js @@ -0,0 +1,72 @@ +import { parse, stringify } from "svgson"; + +async function processSVG(svgText, cssClass) { + try { + // Parse SVG to JSON + const json = await parse(svgText); + + // Remove all fill attributes and fill styles recursively + const removeFill = (node) => { + if (node.attributes?.fill) delete node.attributes.fill; + + // Remove fill from inline styles + if (node.attributes?.style) { + const styles = node.attributes.style + .split(";") + .map((s) => s.trim()) + .filter((s) => s && !s.toLowerCase().startsWith("fill:")) + .join("; "); + + if (styles) { + node.attributes.style = styles; + } else { + delete node.attributes.style; + } + } + + if (node.children) node.children.forEach(removeFill); + }; + removeFill(json); + + // Add class to root + json.attributes.class = cssClass; + + // Convert back to SVG string + return stringify(json); + } catch (err) { + throw new Error(`Error processing SVG: ${err.message}`); + } +} + +// processNotionIcon fetches and validates the SVG +export async function processNotionIcon(iconObj, cssClass = "") { + if (iconObj?.external?.url && iconObj?.url) { + return undefined; + } + + const url = iconObj?.external?.url || iconObj?.file?.url || iconObj.url; + + const urlObject = new URL(url); + + // Fetch the file headers first + console.log("Fetching icon:", urlObject.origin + urlObject.pathname); + const svgResponse = await fetch(url, { method: "GET" }); + const contentType = svgResponse.headers.get("content-type") || ""; + + if (!contentType.includes("image/svg+xml")) { + console.log(`Not an SVG file (content-type was ${contentType}).`); + return `Page Icon`; + } + + console.log("Fetched SVG:", urlObject.origin + urlObject.pathname); + if (!svgResponse.ok) { + throw new Error( + `Failed to fetch SVG: ${svgResponse.status} ${svgResponse.statusText}` + ); + } + + const svgText = await svgResponse.text(); + const processedSVG = processSVG(svgText, cssClass); + + return processedSVG; +} diff --git a/src/utils/imageCache.js b/src/utils/imageCache.js new file mode 100644 index 0000000..2203915 --- /dev/null +++ b/src/utils/imageCache.js @@ -0,0 +1,349 @@ +import { fetchBlurHash } from "./blurHash"; + +function generateImageId(url) { + const urlObj = new URL(url); + const origin = urlObj.origin; + const pathname = urlObj.pathname; + + // Create a consistent hash from origin + pathname + const combined = `${origin}${pathname}`; + + // Simple hash function for better distribution + let hash = 0; + for (let i = 0; i < combined.length; i++) { + const char = combined.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; // Convert to 32-bit integer + } + + // Convert to positive number and create readable ID + const positiveHash = Math.abs(hash); + const base36Hash = positiveHash.toString(36); + + // Pad with zeros if needed and limit length + return base36Hash.padStart(8, "0").substring(0, 12); +} + +// Generate R2 key from image ID and original URL +function generateR2KeyFromImageId(imageId, url) { + const urlObj = new URL(url); + const pathname = urlObj.pathname; + const filename = pathname.split("/").pop() || "image"; + const extension = filename.includes(".") ? filename.split(".").pop() : "jpg"; + const baseFilename = filename.replace(/\.[^/.]+$/, ""); + + return `images/${imageId}-${baseFilename}.${extension}`; +} + +function extractR2KeyFromUrl(mirrorUrl) { + // Extract the key from a mirror URL like "https://r2-domain.com/images/abc123-image.jpg" + const urlObj = new URL(mirrorUrl); + return urlObj.pathname.substring(1); // Remove leading slash +} + +export async function getImages(env, cached = false) { + try { + // If cached=true, try to get from Cloudflare Cache API first + if (cached) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/images` + ); + const cachedResponse = await cache.match(cacheKey); + + if (cachedResponse) { + const cachedData = await cachedResponse.json(); + console.log("Images retrieved from Cloudflare cache"); + return cachedData; + } + } catch (cacheError) { + console.log("Cache miss or error, falling back to KV:", cacheError); + } + } + + // Fall back to KV storage + const kvData = await env.CONTENT_KV.get(env.IMAGES_KEY, { + type: "json", + }); + + // If we were trying to use cache but it failed, store the KV data in cache + if (cached && kvData) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/images` + ); + + const response = new Response(JSON.stringify(kvData), { + headers: { + "Content-Type": "application/json", + "Cache-Control": "max-age=60", // 1 minute TTL + ETag: `"images-${Date.now()}"`, // Add ETag for cache validation + }, + }); + + await cache.put(cacheKey, response); + console.log("Images stored in Cloudflare cache after KV fallback"); + } catch (cacheError) { + console.warn("Error storing in cache after KV fallback:", cacheError); + } + } + + return kvData || []; + } catch (error) { + console.log("Error fetching images cache:", error); + return null; + } +} + +export async function updateImages(env, currentImageUrls) { + console.log("Updating image cache..."); + + // Convert URLs to image objects with IDs + const currentImages = currentImageUrls.map((url) => ({ + id: generateImageId(url), + url, + })); + + // Get existing image cache + const existingImages = await getImages(env); + const existingImageMap = new Map(existingImages.map((img) => [img.id, img])); + + // Find images to add (current images with IDs not in cache) + const imagesToAdd = currentImages.filter( + (img) => !existingImageMap.has(img.id) + ); + + // Find images to keep (existing images with IDs in current set) + const currentImageMap = new Map(currentImages.map((img) => [img.id, img])); + const imagesToKeep = existingImages + .filter((img) => currentImageMap.has(img.id)) + .map((img) => ({ + ...img, + // Update URL in case it has changed (different query params, etc.) + url: currentImageMap.get(img.id).url, + })); + + // Find images to remove (existing images with IDs not in current set) + const imagesToRemove = existingImages.filter( + (img) => !currentImageMap.has(img.id) + ); + + console.log( + `Images to add: ${imagesToAdd.length}, Images to keep: ${imagesToKeep.length}, Images to remove: ${imagesToRemove.length}` + ); + + // Delete removed images from R2 + for (const image of imagesToRemove) { + if (image.mirrorUrl) { + try { + const key = extractR2KeyFromUrl(image.mirrorUrl); + await env.TB_STORAGE.delete(key); + console.log(`Deleted ${key} from R2 for image ID ${image.id}`); + } catch (error) { + console.error( + `Failed to delete ${image.mirrorUrl} from R2 for image ID ${image.id}:`, + error + ); + } + } + } + + // Upload new images to R2 and create mirror URLs + const newImages = []; + for (let i = 0; i < imagesToAdd.length; i++) { + const imageData = imagesToAdd[i]; + const { id, url } = imageData; + + console.log( + `Uploading image to R2 (${i + 1}/${ + imagesToAdd.length + }): ${url} (ID: ${id})` + ); + + try { + // Fetch the image + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to fetch image: ${response.status}`); + } + + // Generate R2 key from image ID + const key = generateR2KeyFromImageId(id, url); + + const result = await env.TB_STORAGE.put(key, response.body); + + if (result == null) { + throw new Error(`Failed to upload image to R2: ${key}`); + } + + // Create mirror URL + const mirrorUrl = `${env.R2_PUBLIC_URL}/${key}`; + + newImages.push({ + id, + url, + blurHash: null, + mirrorUrl, + }); + + console.log( + `Successfully uploaded and mirrored: ${url} -> ${mirrorUrl} (ID: ${id})` + ); + } catch (error) { + console.error(`Failed to upload ${url} to R2 (ID: ${id}):`, error); + // Add image without mirror URL if upload fails + newImages.push({ + id, + url, + blurHash: null, + mirrorUrl: null, + }); + } + + // Small delay to avoid overwhelming the system + await new Promise((resolve) => setTimeout(resolve, 100)); + } + + // Combine kept images with new images + const updatedImages = [...imagesToKeep, ...newImages]; + + // Store updated image cache + await storeImages(env, updatedImages); + + return updatedImages; +} + +export async function storeImages(env, images) { + try { + // Always store in KV first + await env.CONTENT_KV.put(env.IMAGES_KEY, JSON.stringify(images)); + console.log("Images stored in KV."); + + // Purge the Cloudflare Cache API + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/images` + ); + + await cache.delete(cacheKey); + console.log("Images cache purged successfully."); + } catch (cacheError) { + console.warn( + "Error purging Cloudflare cache, but KV was updated successfully:", + cacheError + ); + } + } catch (error) { + console.error("Error storing images cache:", error); + throw error; + } +} + +// Recursively traverse objects to find children with type: "image" +function findImageChildren(obj, imageUrls = []) { + if (!obj || typeof obj !== "object") { + return imageUrls; + } + + // Check if this object has type: "image" + if (obj.type === "image") { + // Extract image URL from various possible properties + if (obj.url) { + imageUrls.push(obj.url); + } else if (obj.src) { + imageUrls.push(obj.src); + } else if (obj.image) { + imageUrls.push(obj.image); + } + } + + // Recursively check children array + if (obj.children && Array.isArray(obj.children)) { + for (const child of obj.children) { + findImageChildren(child, imageUrls); + } + } + + // Recursively check all other object properties + for (const [key, value] of Object.entries(obj)) { + if ( + key !== "children" && + key !== "content" && + key !== "blocks" && + typeof value === "object" + ) { + findImageChildren(value, imageUrls); + } + } + + return imageUrls; +} + +export function collectImageUrls(contentObjects) { + const imageUrls = []; + + // Collect from pages + for (const object of contentObjects) { + if (object.image) { + imageUrls.push(object.image); + } + if (object.images) { + object.images.forEach((url) => imageUrls.push(url)); + } + if (object.content) { + // Use recursive function to find all image children + findImageChildren(object.content, imageUrls); + } + } + + return imageUrls; +} + +export async function handleBlurhashUpdate(request, env) { + // Read the image cache + const cachedImages = await env.CONTENT_KV.get(env.IMAGES_KEY, { + type: "json", + }); + if (!cachedImages) return; + + let updated = false; + + for (const image of cachedImages) { + if (image.blurHash === null) { + // Try mirrorUrl first + let blurHash = image.mirrorUrl + ? await fetchBlurHash(image.mirrorUrl) + : null; + // Fallback to original URL if mirrorUrl fails + if (!blurHash) { + blurHash = await fetchBlurHash(image.url); + } + + if (blurHash) { + image.blurHash = blurHash; + updated = true; + console.log(`Updated blurHash for image ID ${image.id} (${image.url})`); + } + } + } + + // Store updated cache if anything changed + if (updated) { + await env.CONTENT_KV.put(env.IMAGES_KEY, JSON.stringify(cachedImages)); + } + + return cachedImages; +} + +// Helper function to get image by ID +export function getImageById(cachedImages, imageId) { + return cachedImages.find((img) => img.id === imageId); +} + +// Helper function to get image ID from URL +export function getImageIdFromUrl(url) { + return generateImageId(url); +} diff --git a/src/utils/navigation.js b/src/utils/navigation.js new file mode 100644 index 0000000..172a740 --- /dev/null +++ b/src/utils/navigation.js @@ -0,0 +1,90 @@ +import { processNotionIcon } from "./icon"; + +export async function getNavigation(env, cached = false) { + try { + // If cached=true, try to get from Cloudflare Cache API first + if (cached) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.NAVIGATION_CACHE_URL || "cache"}/navigation` + ); + const cachedResponse = await cache.match(cacheKey); + + if (cachedResponse) { + const cachedData = await cachedResponse.json(); + console.log("Navigation retrieved from Cloudflare cache"); + return cachedData; + } + } catch (cacheError) { + console.log("Cache miss or error, falling back to KV:", cacheError); + } + } + + // Fall back to KV storage + const kvData = await env.CONTENT_KV.get(env.NAVIGATION_KEY, { + type: "json", + }); + return kvData || null; + } catch (error) { + console.log("Error fetching navigation cache:", error); + return null; + } +} + +export async function storeNavigation(env, navigation) { + try { + // Always store in KV first + await env.CONTENT_KV.put(env.NAVIGATION_KEY, JSON.stringify(navigation)); + console.log("Navigation stored in KV."); + + // Also update the Cloudflare Cache API + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.NAVIGATION_CACHE_URL || "cache"}/navigation` + ); + + // Create a response with appropriate cache headers + const response = new Response(JSON.stringify(navigation), { + headers: { + "Content-Type": "application/json", + "Cache-Control": "max-age=60", // 1 minute TTL + ETag: `"navigation-${Date.now()}"`, // Add ETag for cache validation + }, + }); + + await cache.put(cacheKey, response); + console.log("Navigation stored in Cloudflare cache."); + } catch (cacheError) { + console.warn( + "Error updating Cloudflare cache, but KV was updated successfully:", + cacheError + ); + } + } catch (error) { + console.error("Error storing navigation cache:", error); + throw error; + } +} + +// Transform basic Notion page data to desired format +export async function transformNavigation(notionPage, type) { + var icon = undefined; + if (notionPage.icon) { + icon = await processNotionIcon(notionPage.icon, "tb-icon"); + } + const properties = notionPage.properties; + const id = notionPage.id; + const slug = properties.Slug?.formula?.string || "unknown"; + // Extract name from title + const name = properties.Name?.title?.[0]?.plain_text || "Untitled"; + + return { + slug: type == "blog" ? `properties/${slug}` : slug, + name: name, + icon: icon, + notionId: id, + type: type, + }; +} diff --git a/src/utils/notion.js b/src/utils/notion.js new file mode 100644 index 0000000..c499e1c --- /dev/null +++ b/src/utils/notion.js @@ -0,0 +1,627 @@ +import { processNotionIcon } from "./icon"; +import { env } from "cloudflare:workers"; +import { getNavigation } from "./navigation"; + +const NOTION_API_BASE = "https://api.notion.com/v1"; +const NOTION_VERSION = "2025-09-03"; // or your preferred version + +const iconColorSubstitutions = [ + ["#D44C47", "#FF453A"], + ["#55534E", "#FFFFFF"], + ["#448361", "#32D74B"], + ["#337ea9", "#0A84FF"], + ["#9065B0", "#BF5AF2"], + ["#CB912F", "#FFD60A"], + ["#C14C8A", "#FF375F"], + ["#d9730d", "#FF9F0A"], +]; + +async function notionFetch(endpoint, options = {}) { + const res = await fetch(`${NOTION_API_BASE}${endpoint}`, { + headers: { + Authorization: `Bearer ${env.NOTION_AUTH}`, + "Notion-Version": NOTION_VERSION, + "Content-Type": "application/json", + }, + ...options, + }); + + if (!res.ok) { + const text = await res.text(); + throw new Error( + `Notion API error: ${res.status} ${res.statusText} - ${text}` + ); + } + return res.json(); +} + +export async function addToNotionDataSource( + properties, + dataSourceId, + icon = null +) { + console.log("Adding to Notion data source..."); + + const pageParams = {}; + const notionProperties = {}; + + if (icon != null) { + pageParams["icon"] = { + type: "external", + external: { + url: icon, + }, + }; + } + + for (const [key, value] of Object.entries(properties)) { + if (key === "Email" && value != "") { + notionProperties[key] = { email: value }; + } else if (key === "Name" && value != "") { + notionProperties[key] = { title: [{ text: { content: value } }] }; + } else if (key === "Features") { + notionProperties[key] = { + multi_select: value.map((feature) => ({ + name: feature, + })), + }; + } else if (key === "Message" && value != "") { + pageParams["children"] = [ + { + object: "block", + type: "paragraph", + paragraph: { + rich_text: [ + { + type: "text", + text: { + content: value, + }, + }, + ], + }, + }, + ]; + } else if (typeof value === "string" && value != "") { + notionProperties[key] = { + rich_text: [{ text: { content: value } }], + }; + } else if (typeof value === "number" && value != undefined) { + notionProperties[key] = { number: value }; + } else if (value && typeof value === "object" && !Array.isArray(value)) { + notionProperties[key] = value; + } + } + try { + const response = await notionFetch("/pages", { + method: "POST", + body: JSON.stringify({ + parent: { data_source_id: dataSourceId }, + ...pageParams, + properties: notionProperties, + }), + }); + + console.log("Added to Notion DB!"); + return response; + } catch (error) { + console.error("Failed to add to Notion:", error); + throw new Error("Failed to add to Notion"); + } +} + +export async function queryNotionDataSource(dataSourceId, queryParams = {}) { + console.log(`Fetching data source: ${dataSourceId} from Notion API...`); + + try { + const body = JSON.stringify(queryParams); + + const result = await notionFetch(`/data_sources/${dataSourceId}/query`, { + method: "POST", + body, + }); + + console.log("Fetched data source:", dataSourceId); + return result.results; + } catch (error) { + console.error(`Error fetching Notion data source ${dataSourceId}:`, error); + throw error; + } +} + +export async function getNotionDatabase(databaseId) { + console.log(`Fetching database: ${databaseId} from Notion API...`); + + try { + const result = await notionFetch(`/databases/${databaseId}`, { + method: "GET", + }); + return result; + } catch (error) { + console.error(`Error fetching Notion database ${databaseId}:`, error); + throw error; + } +} + +export async function getNotionDataSource(dataSourceId) { + console.log(`Fetching data source: ${dataSourceId} from Notion API...`); + + try { + const result = await notionFetch(`/data_sources/${dataSourceId}`, { + method: "GET", + }); + return result; + } catch (error) { + console.error(`Error fetching Notion data source ${dataSourceId}:`, error); + throw error; + } +} + +export async function getNotionPage(pageId) { + console.log(`Fetching page: ${pageId} from Notion API...`); + try { + const result = await notionFetch(`/pages/${pageId}`, { + method: "GET", + }); + return result; + } catch (error) { + console.error(`Error fetching Notion page ${pageId}:`, error); + throw error; + } +} + +export async function updateNotionPage( + pageId, + properties, + archive = false, + trash = false +) { + console.log(`Updating page: ${pageId} in Notion API...`); + const notionProperties = {}; + + for (const [key, value] of Object.entries(properties)) { + if (key === "Email" && value != "") { + notionProperties[key] = { email: value }; + } else if (key === "Name") { + notionProperties[key] = { title: [{ text: { content: value } }] }; + } else if (key === "Features") { + notionProperties[key] = { + multi_select: value.map((feature) => ({ + name: feature, + })), + }; + } else if (typeof value === "string" && value != "") { + notionProperties[key] = { + rich_text: [{ text: { content: value } }], + }; + } else if (typeof value === "number" && value != undefined) { + notionProperties[key] = { number: value }; + } else if (value && typeof value === "object" && !Array.isArray(value)) { + notionProperties[key] = value; + } + } + + try { + const result = await notionFetch(`/pages/${pageId}`, { + method: "PATCH", + body: JSON.stringify({ + properties: notionProperties, + archived: archive, + in_trash: trash, + }), + }); + return result; + } catch (error) { + console.error(`Error updating Notion page ${pageId}:`, error); + throw error; + } +} + +export async function getNotionBlocks(pageId, queryParams = {}) { + console.log(`Fetching blocks for page: ${pageId} from Notion API...`); + + try { + const result = await notionFetch( + `/blocks/${pageId}/children?${new URLSearchParams(queryParams)}` + ); + return result.results; + } catch (error) { + console.error(`Error fetching Notion page ${pageId}:`, error); + throw error; + } +} + +export async function getNotionSVGIcon(url) { + try { + console.log(`Fetching SVG icon from: ${url}`); + const response = await fetch(url); + if (!response.ok) + throw new Error(`Failed to fetch SVG: ${response.statusText}`); + + let svgText = await response.text(); + iconColorSubstitutions.forEach(([searchColor, replaceColor]) => { + svgText = svgText.replaceAll(searchColor, replaceColor); + }); + + return svgText; + } catch (error) { + console.error(error); + return null; + } +} + +// Transform Notion blocks to content format +export async function transformContent(env, pageId, navigationItems) { + try { + console.log(`Transforming content for page: ${pageId}`); + // Fetch blocks from Notion + const blocks = await getNotionBlocks(pageId); + + const content = []; + + for (let i = 0; i < blocks.length; i++) { + const block = blocks[i]; + const blockType = block.type; + + // Group consecutive list items + if ( + blockType === "bulleted_list_item" || + blockType === "numbered_list_item" + ) { + const isNumbered = blockType === "numbered_list_item"; + const children = []; + // Collect consecutive list items of the same type + while ( + i < blocks.length && + blocks[i].type === + (isNumbered ? "numbered_list_item" : "bulleted_list_item") + ) { + children.push({ + type: "listItem", + text: await extractRichText( + isNumbered + ? blocks[i].numbered_list_item.rich_text + : blocks[i].bulleted_list_item.rich_text, + navigationItems + ), + }); + i++; + } + // Step back one index because the for loop will increment it + i--; + content.push({ + type: "list", + ordered: isNumbered, + children, + }); + continue; + } + + switch (blockType) { + case "heading_1": + content.push({ + type: "title1", + text: await extractRichText( + block.heading_1.rich_text, + navigationItems + ), + }); + break; + + case "heading_2": + content.push({ + type: "title2", + text: await extractRichText( + block.heading_2.rich_text, + navigationItems + ), + }); + break; + + case "heading_3": + content.push({ + type: "title3", + text: await extractRichText( + block.heading_3.rich_text, + navigationItems + ), + }); + break; + + case "paragraph": + content.push({ + type: "paragraph", + text: await extractRichText( + block.paragraph.rich_text, + navigationItems + ), + }); + + break; + + case "divider": + content.push({ + type: "divider", + }); + break; + + case "image": + content.push({ + type: "image", + url: block.image.external?.url || block.image.file?.url, + caption: block.image.caption + ? await extractRichText(block.image.caption, navigationItems) + : null, + }); + break; + + case "quote": + content.push({ + type: "quote", + text: await extractRichText(block.quote.rich_text, navigationItems), + }); + break; + + case "code": + content.push({ + type: "code", + text: await extractRichText(block.code.rich_text, navigationItems), + language: block.code.language, + }); + break; + case "bookmark": + content.push({ + type: "button", + text: await extractRichText( + block.bookmark.caption, + navigationItems + ), + url: block.bookmark.url, + }); + break; + + case "callout": + console.log("Callout:", block); + var calloutChildren = []; + if (block.callout?.rich_text && block.callout.rich_text.length > 0) { + const text = await extractRichText( + block.callout.rich_text, + navigationItems + ); + calloutChildren.push({ + type: "paragraph", + text: text, + }); + } + if (block.has_children == true) { + const children = await transformContent( + env, + block.id, + navigationItems + ); + calloutChildren.push(...children); + } + content.push({ + type: "callout", + children: calloutChildren, + icon: block.callout?.icon + ? await processNotionIcon(block.callout.icon, "tb-callout-icon") + : undefined, + }); + break; + case "video": + content.push({ + type: "video", + url: block.video.file?.url || block.video?.external?.url, + }); + break; + case "file": + content.push({ + type: "file", + url: block.file.file?.url || block.file?.external?.url, + }); + break; + case "link_to_page": + const linkedNavigationItem = getItemByNotionId( + navigationItems, + block.link_to_page.page_id + ); + content.push({ + type: "button", + url: `/${linkedNavigationItem.slug}`, + text: linkedNavigationItem.name, + icon: linkedNavigationItem.icon, + }); + break; + case "column_list": + const childColumns = await getNotionBlocks(block.id); + const children = []; + for (const childColumn of childColumns) { + children.push({ + type: "columnFlexItem", + width: childColumn.column.width_ratio * 100 + "%", + children: await transformContent( + env, + childColumn.id, + navigationItems + ), + }); + } + content.push({ type: "columnFlex", children: children }); + break; + default: + // For unsupported block types, try to extract text if available + console.log(`Unsupported block type: ${blockType}`); + console.log(JSON.stringify(block)); + break; + } + } + + return content; + } catch (error) { + console.error(`Error transforming page content for ${pageId}:`, error); + // Return fallback content if transformation fails + return [ + { + type: "paragraph", + text: "Content could not be loaded at this time.", + }, + ]; + } +} + +function processLink(href, navigationItems) { + var pageIdNoDashes = null; + if (href.startsWith("/")) { + pageIdNoDashes = href.split("/").pop().replaceAll("#", ""); + } + if (href.includes("notion.so/")) { + if (href.includes("-")) { + pageIdNoDashes = href.split("-").pop().replaceAll("#", ""); + } else { + pageIdNoDashes = href.split("/").pop().replaceAll("#", ""); + } + } + + if (pageIdNoDashes != null) { + const linkedNavigationItem = getItemByNotionId( + navigationItems, + pageIdNoDashes + ); + if (!linkedNavigationItem.slug) { + return { + type: "externalLink", + url: href, + }; + } + return { + type: "internalLink", + url: `/${linkedNavigationItem.slug}`, + }; + } else { + return { + type: "externalLink", + url: href, + }; + } +} + +// Helper function to extract rich text content +export async function extractRichText(richTextArray, navigationItems) { + if (!richTextArray || !Array.isArray(richTextArray)) { + return []; + } + + const textPieces = await Promise.all( + richTextArray.map(async (textObj) => { + let text = textObj?.plain_text || ""; + let annotations = textObj?.annotations || {}; + let link = undefined; + let emoji = undefined; + // Handle custom emoji mentions + if ( + textObj.type === "mention" && + textObj.mention?.type === "custom_emoji" && + textObj.mention.custom_emoji?.url + ) { + emoji = await processNotionIcon( + textObj.mention.custom_emoji, + "tb-inline-emoji" + ); + } + + // Handle links + if (textObj.href) { + link = processLink(textObj.href, navigationItems); + } + + return { + text: text, + bold: annotations?.bold || false, + italic: annotations?.italic || false, + underline: annotations?.underline || false, + strikethrough: annotations?.strikethrough || false, + code: annotations?.code || false, + color: annotations?.color || undefined, + link: link, + }; + }) + ); + + return textPieces; +} + +// Utility: safely extract plain text from a Notion title or rich_text field +export function getPlainText(field) { + if (!field) return ""; + if (field.type === "title" || field.type === "rich_text") { + return field[field.type].map((t) => t.plain_text).join("") || ""; + } + return ""; +} + +// Utility: convert a string to camelCase +export function toCamelCase(str = "") { + return str + .replace(/[^a-zA-Z0-9 ]/g, " ") // remove non-alphanumeric except spaces + .trim() + .split(/\s+/) // split on spaces + .map((word, index) => { + if (index === 0) return word.toLowerCase(); + return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); + }) + .join(""); +} + +// Moved from Blogs.js: buildNewCache, now buildListCache +export function buildListCache( + cachedList, + { added = [], updated = [], deleted = [] } +) { + // Remove deleted and items that will be updated + const filtered = cachedList.filter( + (p) => + !deleted.some((d) => d.notionId === p.notionId) && + !updated.some((u) => u.notionId === p.notionId) + ); + + return [...filtered, ...updated, ...added]; +} + +export function getItemByNotionId(items, notionId) { + const itemWithDashes = items.find((item) => item.notionId === notionId); + if (itemWithDashes) { + return itemWithDashes; + } + const itemWithoutDashes = items.find( + (item) => item.notionId.replaceAll("-", "") === notionId.replaceAll("-", "") + ); + if (itemWithoutDashes) { + return itemWithoutDashes; + } + return null; +} + +// Sort function for objects with duration properties +// Sorts by end date (newest first), with ongoing positions (no end date) first +export function sortDuration(a, b) { + const aEnd = a.duration?.end; + const bEnd = b.duration?.end; + const aStart = a.duration?.start || ""; + const bStart = b.duration?.start || ""; + + // Positions without end date (ongoing) should be first (newest) + if (!aEnd && !bEnd) { + // Both ongoing - sort by start date, newest first + + return bStart.localeCompare(aStart); + } + if (!aEnd) return -1; // a is ongoing, should be first + if (!bEnd) return 1; // b is ongoing, should be first + + // Both have start dates - sort by start date, newest first + const startComparison = bStart.localeCompare(aStart); + if (startComparison !== 0) return startComparison; + + return bEnd.localeCompare(aEnd); +} diff --git a/src/utils/pages.js b/src/utils/pages.js new file mode 100644 index 0000000..c550ece --- /dev/null +++ b/src/utils/pages.js @@ -0,0 +1,214 @@ +import { + transformContent, + toCamelCase, + getItemByNotionId, +} from "../utils/notion.js"; +import { getNavigation } from "../utils/navigation.js"; +// Transform Notion page data to desired format + +export async function getPages(env, cached = false) { + try { + // If cached=true, try to get from Cloudflare Cache API first + if (cached) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/pages` + ); + const cachedResponse = await cache.match(cacheKey); + + if (cachedResponse) { + const cachedData = await cachedResponse.json(); + console.log("Pages retrieved from Cloudflare cache"); + return cachedData; + } + } catch (cacheError) { + console.log("Cache miss or error, falling back to KV:", cacheError); + } + } + + // Fall back to KV storage + const kvData = await env.CONTENT_KV.get(env.PAGES_KEY, { + type: "json", + }); + + // If we were trying to use cache but it failed, store the KV data in cache + if (cached && kvData) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/pages` + ); + + const response = new Response(JSON.stringify(kvData), { + headers: { + "Content-Type": "application/json", + "Cache-Control": "max-age=60", // 1 minute TTL + ETag: `"pages-${Date.now()}"`, // Add ETag for cache validation + }, + }); + + await cache.put(cacheKey, response); + console.log("Pages stored in Cloudflare cache after KV fallback"); + } catch (cacheError) { + console.warn("Error storing in cache after KV fallback:", cacheError); + } + } + + return kvData || []; + } catch (error) { + console.log("Error fetching pages cache:", error); + return null; + } +} + +export async function storePages(env, pages) { + try { + // Always store in KV first + await env.CONTENT_KV.put(env.PAGES_KEY, JSON.stringify(pages)); + console.log("Pages stored in KV."); + + // Purge the Cloudflare Cache API + try { + const cache = caches.default; + const cacheKey = new Request(`https://${env.CACHE_URL || "cache"}/pages`); + + await cache.delete(cacheKey); + console.log("Pages cache purged successfully."); + } catch (cacheError) { + console.warn( + "Error purging Cloudflare cache, but KV was updated successfully:", + cacheError + ); + } + } catch (error) { + console.error("Error storing pages cache:", error); + throw error; + } +} + +export async function transformPageData(env, notionPage, settingsData) { + const navigationItems = await getNavigation(env); + + const icon = getItemByNotionId(navigationItems, notionPage.id).icon; + + const properties = notionPage.properties; + + // Extract theme information + let theme = undefined; // default + if (properties.Theme?.relation?.[0]?.id) { + const themeId = properties.Theme.relation[0].id; + console.log(settingsData.themes); + const themeData = getItemByNotionId(settingsData.themes, themeId); + if (themeData?.name) { + theme = themeData.name; + } + } + + const published = properties["Published"]?.checkbox || false; + // Check if page is published - if not, return null to filter it out + + const pageType = toCamelCase(properties["Page Type"].select.name); + + const align = toCamelCase(properties["Align"].select?.name || "center"); + + const justify = toCamelCase(properties["Justify"].select?.name || "middle"); + // Extract slug from formula); + const slug = properties.Slug?.formula?.string || "unknown"; + + // Extract name from title + const name = properties.Name?.title?.[0]?.plain_text || "Untitled"; + + // Fetch and transform the actual page content from Notion blocks + + const content = []; + + const showPageIcon = properties["Show Page Icon"]?.checkbox || false; + + if (showPageIcon == true) { + content.push({ + type: "pageIcon", + icon: icon, + }); + } + + const pageContent = await transformContent( + env, + notionPage.id, + navigationItems + ); + content.push(...pageContent); + + const showBlogs = properties["Show Blogs"]?.checkbox || false; + const showProjects = properties["Show Projects"]?.checkbox || false; + const showExperience = properties["Show Experience"]?.checkbox || false; + + const showContactForm = properties["Show Contact Form"]?.checkbox || false; + const gradientBackground = + properties["Gradient Background"]?.checkbox || false; + + const showScroll = properties["Show Scroll Icon"]?.checkbox || false; + + const verticalScroll = properties["Vertical Scroll"]?.checkbox || false; + const horizontalScroll = properties["Horizontal Scroll"]?.checkbox || false; + + const scrollSnap = properties["Scroll Snap"]?.checkbox || false; + + const paragraphWidth = properties["Paragraph Width"]?.number || 400; + const order = properties["Order"]?.number || undefined; + + const spacing = properties["Spacing"]?.number || 5; + + const showScrollButtons = + properties["Show Scroll Buttons"]?.checkbox || false; + const scrollButtonDistance = + properties["Scroll Button Distance"]?.number || 10; + + if (showBlogs == true) { + content.push({ + type: "blogs", + }); + } + if (showProjects == true) { + content.push({ + type: "projects", + }); + } + if (showExperience == true) { + content.push({ + type: "companies", + }); + } + if (showContactForm == true) { + content.push({ + type: "contactForm", + }); + } + + return { + notionId: notionPage.id, + published: published, + icon: icon, + slug: slug, + name: name, + pageType: pageType, + content: content, + theme: theme, + align: align, + justify: justify, + showScroll: showScroll, + showBlogs: showBlogs, + showProjects: showProjects, + showExperience: showExperience, + showContactForm: showContactForm, + gradientBackground: gradientBackground, + paragraphWidth: paragraphWidth, + spacing: spacing, + order: order, + verticalScroll: verticalScroll, + horizontalScroll: horizontalScroll, + scrollSnap: scrollSnap, + showScrollButtons: showScrollButtons, + scrollButtonDistance: scrollButtonDistance, + }; +} diff --git a/src/utils/positions.js b/src/utils/positions.js new file mode 100644 index 0000000..20ef4d0 --- /dev/null +++ b/src/utils/positions.js @@ -0,0 +1,164 @@ +import { getItemByNotionId, getPlainText, transformContent } from "./notion.js"; +import _ from "lodash"; +import diff from "microdiff"; +import { getNavigation } from "../utils/navigation.js"; + +export async function getPositions(env, cached = false) { + try { + // If cached=true, try to get from Cloudflare Cache API first + if (cached) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/Positions` + ); + const cachedResponse = await cache.match(cacheKey); + + if (cachedResponse) { + const cachedData = await cachedResponse.json(); + console.log("Positions retrieved from Cloudflare cache"); + return cachedData; + } + } catch (cacheError) { + console.log("Cache miss or error, falling back to KV:", cacheError); + } + } + + // Fall back to KV storage + const kvData = await env.CONTENT_KV.get(env.POSITIONS_KEY, { + type: "json", + }); + + // If we were trying to use cache but it failed, store the KV data in cache + if (cached && kvData) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/Positions` + ); + + const response = new Response(JSON.stringify(kvData), { + headers: { + "Content-Type": "application/json", + "Cache-Control": "max-age=60", // 1 minute TTL + ETag: `"Positions-${Date.now()}"`, // Add ETag for cache validation + }, + }); + + await cache.put(cacheKey, response); + console.log("Positions stored in Cloudflare cache after KV fallback"); + } catch (cacheError) { + console.warn("Error storing in cache after KV fallback:", cacheError); + } + } + + return kvData || []; + } catch (error) { + console.log("Error fetching Positions cache:", error); + return null; + } +} + +export async function storePositions(env, Positions) { + try { + // Always store in KV first + await env.CONTENT_KV.put(env.POSITIONS_KEY, JSON.stringify(Positions)); + console.log("Positions stored in KV."); + + // Purge the Cloudflare Cache API + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/Positions` + ); + + await cache.delete(cacheKey); + console.log("Positions cache purged successfully."); + } catch (cacheError) { + console.warn( + "Error purging Cloudflare cache, but KV was updated successfully:", + cacheError + ); + } + } catch (error) { + console.error("Error storing Positions cache:", error); + throw error; + } +} + +export function diffPositions(newList, oldList) { + // Keys to check for changes + const keysToCheck = ["name", "duration", "company", "content"]; + + // Helper: index by notionId using lodash + const oldById = _.keyBy(oldList || [], "notionId"); + const newById = _.keyBy(newList || [], "notionId"); + + // toAdd: in newList but not in oldList + const toAdd = newList.filter((p) => !oldById[p.notionId]); + + // toDelete: in oldList but not in newList + const toDelete = oldList.filter((p) => !newById[p.notionId]); + + // toUpdate: in both, but with different content (using microdiff) + const toUpdate = newList.filter((p) => { + const old = oldById[p.notionId]; + if (!old) return false; + + // Prepare objects for comparison + const { notionId, ...restNew } = p; + const { notionId: _, ...restOld } = old; + + // Use microdiff to get differences + const differences = diff(restOld, restNew); + + // Filter differences to only include keys we care about + const relevantDifferences = differences.filter((change) => { + // Check if the changed path starts with any of our keysToCheck + const path = change.path.join("."); + return keysToCheck.some((key) => path.startsWith(key)); + }); + + return relevantDifferences.length > 0; + }); + + return { toAdd, toUpdate, toDelete }; +} + +// Transform Notion position data to desired format +export async function transformNotionPosition(env, notionPosition) { + const navigationItems = await getNavigation(env); + + const properties = notionPosition.properties; + + console.log("Notion Position:", notionPosition); + + // Extract company information + let company = undefined; + if (properties.Company?.relation?.[0]?.id) { + company = properties.Company.relation[0].id; + } + + // Extract duration + const duration = properties["Duration"].date; + + // Extract name from title + const name = properties.Name?.title?.[0]?.plain_text || "Untitled"; + + console.log("Company:", company); + + // Fetch and transform the actual page content from Notion blocks + const content = await transformContent( + env, + notionPosition.id, + navigationItems + ); + + return { + notionId: notionPosition.id, + name: name, + duration: duration, + company: company, + content, + }; +} diff --git a/src/utils/projects.js b/src/utils/projects.js new file mode 100644 index 0000000..8803345 --- /dev/null +++ b/src/utils/projects.js @@ -0,0 +1,225 @@ +import { + getItemByNotionId, + getPlainText, + transformContent, + toCamelCase, +} from "./notion.js"; +import _ from "lodash"; +import diff from "microdiff"; +import { getNavigation } from "../utils/navigation.js"; +import dayjs from "dayjs"; + +export async function getProjects(env, cached = false) { + try { + // If cached=true, try to get from Cloudflare Cache API first + if (cached) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/Projects` + ); + const cachedResponse = await cache.match(cacheKey); + + if (cachedResponse) { + const cachedData = await cachedResponse.json(); + console.log("Projects retrieved from Cloudflare cache"); + return cachedData; + } + } catch (cacheError) { + console.log("Cache miss or error, falling back to KV:", cacheError); + } + } + + // Fall back to KV storage + const kvData = await env.CONTENT_KV.get(env.PROJECTS_KEY, { + type: "json", + }); + + // If we were trying to use cache but it failed, store the KV data in cache + if (cached && kvData) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/Projects` + ); + + const response = new Response(JSON.stringify(kvData), { + headers: { + "Content-Type": "application/json", + "Cache-Control": "max-age=60", // 1 minute TTL + ETag: `"Projects-${Date.now()}"`, // Add ETag for cache validation + }, + }); + + await cache.put(cacheKey, response); + console.log("Projects stored in Cloudflare cache after KV fallback"); + } catch (cacheError) { + console.warn("Error storing in cache after KV fallback:", cacheError); + } + } + + return kvData || []; + } catch (error) { + console.log("Error fetching Projects cache:", error); + return null; + } +} + +export async function storeProjects(env, Projects) { + try { + // Always store in KV first + await env.CONTENT_KV.put(env.PROJECTS_KEY, JSON.stringify(Projects)); + console.log("Projects stored in KV."); + + // Purge the Cloudflare Cache API + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/Projects` + ); + + await cache.delete(cacheKey); + console.log("Projects cache purged successfully."); + } catch (cacheError) { + console.warn( + "Error purging Cloudflare cache, but KV was updated successfully:", + cacheError + ); + } + } catch (error) { + console.error("Error storing Projects cache:", error); + throw error; + } +} + +export function diffProjects(newList, oldList) { + // Keys to check for changes + const keysToCheck = [ + "name", + "date", + "image", + "content", + "published", + "externalLink", + "type", + "tools", + "client", + ]; + + // Helper: index by notionId using lodash + const oldById = _.keyBy(oldList || [], "notionId"); + const newById = _.keyBy(newList || [], "notionId"); + + // toAdd: in newList but not in oldList + const toAdd = newList.filter((p) => !oldById[p.notionId]); + + // toDelete: in oldList but not in newList + const toDelete = oldList.filter((p) => !newById[p.notionId]); + + // toUpdate: in both, but with different content (using microdiff) + const toUpdate = newList.filter((p) => { + const old = oldById[p.notionId]; + if (!old) return false; + + // Prepare objects for comparison + const { notionId, ...restNew } = p; + const { notionId: _, ...restOld } = old; + + // Use microdiff to get differences + const differences = diff(restOld, restNew); + + // Filter differences to only include keys we care about + const relevantDifferences = differences.filter((change) => { + // Check if the changed path starts with any of our keysToCheck + const path = change.path.join("."); + return keysToCheck.some((key) => path.startsWith(key)); + }); + + return relevantDifferences.length > 0; + }); + + return { toAdd, toUpdate, toDelete }; +} + +// Transform Notion project data to desired format +export async function transformNotionProject(env, notionProject, settingsData) { + const navigationItems = await getNavigation(env); + + const properties = notionProject.properties; + + console.log("Notion Project:", notionProject); + + // Extract theme information + let theme = undefined; // default + if (properties.Theme?.relation?.[0]?.id) { + const themeId = properties.Theme.relation[0].id; + console.log(settingsData.themes); + const themeData = getItemByNotionId(settingsData.themes, themeId); + if (themeData?.name) { + theme = themeData.name; + } + } + + const published = properties["Published"]?.checkbox || false; + + const slug = properties["Slug"]?.formula?.string || "unknown"; + + const status = toCamelCase(properties["Status"]?.status?.name || null); + + const dateRaw = properties["Date"]?.date; + const date = dateRaw?.start ? dayjs(dateRaw.start).format("DD/MM/YY") : null; + + // Extract image URL (handle both external and file images) + const imageFile = notionProject?.cover; + const image = imageFile?.external?.url || imageFile?.file?.url || null; + + const subTitle = getPlainText(properties["Subtitle"]); + + // Extract name from title + const name = properties.Name?.title?.[0]?.plain_text || "Untitled"; + + // Extract external link + const externalLink = properties["External Link"]?.url || null; + + // Extract type (select) + const type = properties["Type"]?.select?.name || null; + + // Extract tools (multi-select) + const tools = (properties["Tools"]?.multi_select || []).map( + (item) => item.name + ); + + // Extract client (text) + const client = getPlainText(properties["Client"]); + + // Fetch and transform the actual page content from Notion blocks + + const content = [{ type: "image", url: image, caption: subTitle }]; + const projectContent = await transformContent( + env, + notionProject.id, + navigationItems + ); + content.push(...projectContent); + + if (published == false) { + return null; + } + + return { + notionId: notionProject.id, + name: name, + date: date, + image: image, + subTitle: subTitle, + content, + published, + externalLink: externalLink, + type: type, + tools: tools, + client: client, + slug: slug, + status: status, + theme: theme, + }; +} diff --git a/src/utils/settings.js b/src/utils/settings.js new file mode 100644 index 0000000..11e0490 --- /dev/null +++ b/src/utils/settings.js @@ -0,0 +1,177 @@ +import { processNotionIcon } from "./icon"; +import { getNavigation } from "./navigation"; +import { toCamelCase, getPlainText, getItemByNotionId } from "./notion"; + +export async function getSettings(env, cached = false) { + try { + // If cached=true, try to get from Cloudflare Cache API first + if (cached) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/settings` + ); + const cachedResponse = await cache.match(cacheKey); + + if (cachedResponse) { + const cachedData = await cachedResponse.json(); + console.log("Settings retrieved from Cloudflare cache"); + return cachedData; + } + } catch (cacheError) { + console.log("Cache miss or error, falling back to KV:", cacheError); + } + } + + // Fall back to KV storage + const kvData = await env.CONTENT_KV.get(env.SETTINGS_KEY, { + type: "json", + }); + + // If we were trying to use cache but it failed, store the KV data in cache + if (cached && kvData) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/settings` + ); + + const response = new Response(JSON.stringify(kvData), { + headers: { + "Content-Type": "application/json", + "Cache-Control": "max-age=60", // 1 minute TTL + ETag: `"settings-${Date.now()}"`, // Add ETag for cache validation + }, + }); + + await cache.put(cacheKey, response); + console.log("Settings stored in Cloudflare cache after KV fallback"); + } catch (cacheError) { + console.warn("Error storing in cache after KV fallback:", cacheError); + } + } + + return kvData || {}; + } catch (error) { + console.log("Error fetching settings cache:", error); + return null; + } +} + +export async function storeSettings(env, settings) { + try { + // Always store in KV first + await env.CONTENT_KV.put(env.SETTINGS_KEY, JSON.stringify(settings)); + console.log("Settings stored in KV."); + + // Purge the Cloudflare Cache API + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/settings` + ); + + await cache.delete(cacheKey); + console.log("Settings cache purged successfully."); + } catch (cacheError) { + console.warn( + "Error purging Cloudflare cache, but KV was updated successfully:", + cacheError + ); + } + } catch (error) { + console.error("Error storing settings cache:", error); + throw error; + } +} + +export async function transformRedirectsData(env, redirectsData = []) { + const redirects = {}; + + const navigationItems = await getNavigation(env); + + // Iterate over redirects and build object + for (const redirect of redirectsData) { + const redirectName = getPlainText(redirect.properties?.Name); + const relationIds = + redirect.properties?.Page?.relation?.map((r) => r.id) || []; + // Map relations to full basicData entries + const relatedItem = relationIds + .map((notionId) => getItemByNotionId(navigationItems, notionId)) + .filter(Boolean)[0]; + + const key = toCamelCase(redirectName); + redirects[key] = relatedItem.slug; + } + + return redirects; +} + +// Transform Notion themes data to desired format +export async function transformThemes(themesData) { + if (!themesData || !Array.isArray(themesData)) { + return []; + } + + const transformed = await Promise.all( + themesData.map(async (theme) => { + const properties = theme.properties; + // Extract theme name + const name = properties.Name?.title?.[0]?.plain_text || "unknown"; + + // Extract background color + const backgroundColor = + properties["Background Color"]?.rich_text?.[0]?.plain_text || "#ffffff"; + + // Extract text color + const textColor = + properties["Text Color"]?.rich_text?.[0]?.plain_text || "#000000"; + + return { + notionId: theme.id, + name: toCamelCase(name), + backgroundColor, + textColor, + }; + }) + ); + + return transformed; +} + +// Transform Notion page data to desired format +export async function transformSettingsData( + env, + globalThemesData = [], + redirectsData = [], + themesData = [] +) { + const redirects = await transformRedirectsData(env, redirectsData); + const themes = await transformThemes(themesData); + const globalThemes = transformGlobalThemesData(env, globalThemesData, themes); + + return { redirects, themes, globalThemes }; +} + +export function transformGlobalThemesData( + env, + globalThemesData = [], + themes = [] +) { + const globalThemes = {}; + + // Iterate over redirects and build object + for (const globalTheme of globalThemesData) { + const globalThemeName = getPlainText(globalTheme.properties?.Name); + const relationIds = + globalTheme.properties?.Theme?.relation?.map((r) => r.id) || []; + // Map relations to full basicData entries + const relatedTheme = relationIds + .map((notionId) => getItemByNotionId(themes, notionId)) + .filter(Boolean)[0]; + const key = toCamelCase(globalThemeName); + globalThemes[key] = relatedTheme; + } + + return globalThemes; +} diff --git a/src/utils/videoCache.js b/src/utils/videoCache.js new file mode 100644 index 0000000..e507440 --- /dev/null +++ b/src/utils/videoCache.js @@ -0,0 +1,340 @@ +function generateVideoId(url) { + const urlObj = new URL(url); + const origin = urlObj.origin; + const pathname = urlObj.pathname; + + const combined = `${origin}${pathname}`; + + let hash = 0; + for (let i = 0; i < combined.length; i++) { + const char = combined.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; + } + + const positiveHash = Math.abs(hash); + const base36Hash = positiveHash.toString(36); + + return base36Hash.padStart(8, "0").substring(0, 12); +} + +function generateR2KeyFromVideoId(videoId, url) { + const urlObj = new URL(url); + const pathname = urlObj.pathname; + const filename = pathname.split("/").pop() || "video"; + const extension = filename.includes(".") ? filename.split(".").pop() : "mp4"; + const baseFilename = filename.replace(/\.[^/.]+$/, ""); + + return `videos/${videoId}-${baseFilename}.${extension}`; +} + +function extractR2KeyFromUrl(mirrorUrl) { + const urlObj = new URL(mirrorUrl); + return urlObj.pathname.substring(1); +} + +export async function getVideos(env, cached = false) { + try { + if (cached) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/videos` + ); + const cachedResponse = await cache.match(cacheKey); + + if (cachedResponse) { + const cachedData = await cachedResponse.json(); + console.log("Videos retrieved from Cloudflare cache"); + return cachedData; + } + } catch (cacheError) { + console.log( + "Video cache miss or error, falling back to KV:", + cacheError + ); + } + } + + const kvData = await env.CONTENT_KV.get(env.VIDEOS_KEY, { + type: "json", + }); + + if (cached && kvData) { + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/videos` + ); + + const response = new Response(JSON.stringify(kvData), { + headers: { + "Content-Type": "application/json", + "Cache-Control": "max-age=60", + ETag: `"videos-${Date.now()}"`, + }, + }); + + await cache.put(cacheKey, response); + console.log("Videos stored in Cloudflare cache after KV fallback"); + } catch (cacheError) { + console.warn( + "Error storing videos in cache after KV fallback:", + cacheError + ); + } + } + + return kvData || []; + } catch (error) { + console.log("Error fetching videos cache:", error); + return null; + } +} + +export async function updateVideos(env, currentVideoUrls) { + console.log("Updating video cache..."); + + const currentVideos = currentVideoUrls.map((url) => ({ + id: generateVideoId(url), + url, + })); + + const existingVideos = await getVideos(env); + const existingVideoMap = new Map( + existingVideos.map((video) => [video.id, video]) + ); + + const videosToAdd = currentVideos.filter( + (video) => !existingVideoMap.has(video.id) + ); + + const currentVideoMap = new Map( + currentVideos.map((video) => [video.id, video]) + ); + const videosToKeep = existingVideos + .filter((video) => currentVideoMap.has(video.id)) + .map((video) => ({ + ...video, + url: currentVideoMap.get(video.id).url, + })); + + const videosToRemove = existingVideos.filter( + (video) => !currentVideoMap.has(video.id) + ); + + console.log( + `Videos to add: ${videosToAdd.length}, Videos to keep: ${videosToKeep.length}, Videos to remove: ${videosToRemove.length}` + ); + + for (const video of videosToRemove) { + if (video.mirrorUrl) { + try { + const key = extractR2KeyFromUrl(video.mirrorUrl); + await env.TB_STORAGE.delete(key); + console.log(`Deleted ${key} from R2 for video ID ${video.id}`); + } catch (error) { + console.error( + `Failed to delete ${video.mirrorUrl} from R2 for video ID ${video.id}:`, + error + ); + } + } + } + + const newVideos = []; + for (let i = 0; i < videosToAdd.length; i++) { + const videoData = videosToAdd[i]; + const { id, url } = videoData; + + console.log( + `Uploading video to R2 (${i + 1}/${ + videosToAdd.length + }): ${url} (ID: ${id})` + ); + + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to fetch video: ${response.status}`); + } + + const contentType = response.headers.get("content-type") || null; + const contentLengthHeader = response.headers.get("content-length"); + const contentLength = contentLengthHeader + ? Number.parseInt(contentLengthHeader, 10) + : null; + + const key = generateR2KeyFromVideoId(id, url); + + const putOptions = contentType + ? { httpMetadata: { contentType } } + : undefined; + const result = await env.TB_STORAGE.put(key, response.body, putOptions); + + if (result == null) { + throw new Error(`Failed to upload video to R2: ${key}`); + } + + const mirrorUrl = `${env.R2_PUBLIC_URL}/${key}`; + + newVideos.push({ + id, + url, + metadata: + contentType || contentLength ? { contentType, contentLength } : null, + mirrorUrl, + }); + + console.log( + `Successfully uploaded and mirrored video: ${url} -> ${mirrorUrl} (ID: ${id})` + ); + } catch (error) { + console.error(`Failed to upload ${url} to R2 (ID: ${id}):`, error); + newVideos.push({ + id, + url, + metadata: null, + mirrorUrl: null, + }); + } + + await new Promise((resolve) => setTimeout(resolve, 100)); + } + + const updatedVideos = [...videosToKeep, ...newVideos]; + + await storeVideos(env, updatedVideos); + + return updatedVideos; +} + +export async function storeVideos(env, videos) { + try { + await env.CONTENT_KV.put(env.VIDEOS_KEY, JSON.stringify(videos)); + console.log("Videos stored in KV."); + + try { + const cache = caches.default; + const cacheKey = new Request( + `https://${env.CACHE_URL || "cache"}/videos` + ); + + await cache.delete(cacheKey); + console.log("Videos cache purged successfully."); + } catch (cacheError) { + console.warn( + "Error purging Cloudflare video cache, but KV was updated successfully:", + cacheError + ); + } + } catch (error) { + console.error("Error storing videos cache:", error); + throw error; + } +} + +function findVideoChildren(obj, videoUrls = []) { + if (!obj || typeof obj !== "object") { + return videoUrls; + } + + if (obj.type === "video") { + if (obj.url) { + videoUrls.push(obj.url); + } else if (obj.src) { + videoUrls.push(obj.src); + } else if (obj.video) { + videoUrls.push(obj.video); + } + } + + if (obj.children && Array.isArray(obj.children)) { + for (const child of obj.children) { + findVideoChildren(child, videoUrls); + } + } + + for (const [key, value] of Object.entries(obj)) { + if ( + key !== "children" && + key !== "content" && + key !== "blocks" && + typeof value === "object" + ) { + findVideoChildren(value, videoUrls); + } + } + + return videoUrls; +} + +export function collectVideoUrls(contentObjects) { + const videoUrls = []; + + for (const object of contentObjects) { + if (object.video) { + videoUrls.push(object.video); + } + if (object.videos) { + object.videos.forEach((url) => videoUrls.push(url)); + } + if (object.content) { + findVideoChildren(object.content, videoUrls); + } + } + + return videoUrls; +} + +export async function handleVideoMetadataUpdate(request, env) { + const cachedVideos = await env.CONTENT_KV.get(env.VIDEOS_KEY, { + type: "json", + }); + if (!cachedVideos) return; + + let updated = false; + + for (const video of cachedVideos) { + if (!video.metadata) { + const targetUrl = video.mirrorUrl || video.url; + try { + const response = await fetch(targetUrl, { method: "HEAD" }); + if (response.ok) { + const contentType = response.headers.get("content-type") || null; + const contentLengthHeader = response.headers.get("content-length"); + const contentLength = contentLengthHeader + ? Number.parseInt(contentLengthHeader, 10) + : null; + + if (contentType || contentLength) { + video.metadata = { contentType, contentLength }; + updated = true; + console.log( + `Updated metadata for video ID ${video.id} (${video.url})` + ); + } + } + } catch (error) { + console.warn( + `Failed to update metadata for video ID ${video.id} (${video.url}):`, + error + ); + } + } + } + + if (updated) { + await env.CONTENT_KV.put(env.VIDEOS_KEY, JSON.stringify(cachedVideos)); + } + + return cachedVideos; +} + +export function getVideoById(cachedVideos, videoId) { + return cachedVideos.find((video) => video.id === videoId); +} + +export function getVideoIdFromUrl(url) { + return generateVideoId(url); +} diff --git a/tb-api.paw b/tb-api.paw new file mode 100644 index 0000000..1d06168 Binary files /dev/null and b/tb-api.paw differ diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 0000000..977f64c --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,11 @@ +import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config'; + +export default defineWorkersConfig({ + test: { + poolOptions: { + workers: { + wrangler: { configPath: './wrangler.jsonc' }, + }, + }, + }, +}); diff --git a/wrangler.jsonc b/wrangler.jsonc new file mode 100644 index 0000000..f2fda0f --- /dev/null +++ b/wrangler.jsonc @@ -0,0 +1,140 @@ +/** + * For more details on how to configure Wrangler, refer to: + * https://developers.cloudflare.com/workers/wrangler/configuration/ + */ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "tombutcher-api", + "main": "src/index.js", + "compatibility_date": "2025-02-24", + "observability": { + "enabled": true, + "head_sampling_rate": 1 + }, + /** + * Smart Placement + * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement + */ + // "placement": { "mode": "smart" }, + + /** + * Bindings + * Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including + * databases, object storage, AI inference, real-time communication and more. + * https://developers.cloudflare.com/workers/runtime-apis/bindings/ + */ + + /** + * Environment Variables + * https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables + */ + "vars": { + "THEMES_DB": "289dd26d-60b6-8195-843b-000b65b97c0a", + "GLOBAL_THEMES_DB": "289dd26d-60b6-81d8-8823-000b24e2a783", + "PAGES_DB": "289dd26d-60b6-81d0-b598-000becb15373", + "BLOGS_DB": "289dd26d-60b6-811d-9bdb-000bc5557136", + "MESSAGES_DB": "289dd26d-60b6-818e-9f49-000b5fb2ba34", + "REDIRECTS_DB": "289dd26d-60b6-817c-9f97-000ba041fcfb", + "PROJECTS_DB": "297dd26d-60b6-8031-9de2-000bb1de652b", + "COMPANIES_DB": "29fdd26d-60b6-80e8-8b70-000b0c24c7b7", + "POSITIONS_DB": "29fdd26d-60b6-8018-9fd5-000bdce63f48", + "CV_DB": "2a3dd26d-60b6-8098-85c0-000b12e171c4", + "BLOGS_KEY": "th-blogs-cache", + "COMPANIES_KEY": "th-companies-cache", + "POSITIONS_KEY": "th-positions-cache", + "NAVIGATION_KEY": "th-navigation-cache", + "PAGES_KEY": "th-pages-cache", + "IMAGES_KEY": "th-images-cache", + "BOOKINGS_KEY": "th-bookings-cache", + "PROJECTS_KEY": "th-projects-cache", + "VIDEOS_KEY": "th-videos-cache", + "FILES_KEY": "th-files-cache", + "PROPERTIES_KEY": "th-properties-cache", + "SETTINGS_KEY": "th-settings-cache", + "CV_KEY": "th-cv-cache", + "CACHE_URL": "https://api.tombutcherltd.com/cache", + "R2_PUBLIC_URL": "https://cdn2026.tombutcher.work", + "BLUR_HASH": "true", + "CORS_ORIGIN": "https://tombutcher.work" + }, + + "kv_namespaces": [ + { + "binding": "CONTENT_KV", // the variable you’ll use in the Worker + "id": "05c5283d5b74488da7f90297ea350f9c" // ID from Cloudflare dashboard + } + ], + "r2_buckets": [ + { + "binding": "TB_STORAGE", + "bucket_name": "tb-2026-storage" + } + ], + "images": { + "binding": "IMAGES" // i.e. available in your Worker on env.IMAGES + }, + + "env": { + "production": { + "kv_namespaces": [ + { + "binding": "CONTENT_KV", // the variable you’ll use in the Worker + "id": "05c5283d5b74488da7f90297ea350f9c" // ID from Cloudflare dashboard + } + ], + "r2_buckets": [ + { + "binding": "TB_STORAGE", + "bucket_name": "tb-2026-storage" + } + ], + "images": { + "binding": "IMAGES" // i.e. available in your Worker on env.IMAGES + }, + "vars": { + "THEMES_DB": "26d4d3a4-6a6f-8052-966a-000bc1c836c1", + "GLOBAL_THEMES_DB": "26d4d3a4-6a6f-80a8-b5f8-000b1b040695", + "PAGES_DB": "26d4d3a4-6a6f-80f1-a0fc-000b3c4c5013", + "PROPERTIES_DB": "26e4d3a4-6a6f-80b8-ac80-000b7b236ebc", + "BOOKINGS_DB": "26e4d3a4-6a6f-80fe-960a-000be56680fd", + "GUESTS_DB": "2764d3a4-6a6f-808b-af87-000b072894d7", + "MESSAGES_DB": "2754d3a4-6a6f-80b5-8f8e-000b2ab6ae7a", + "REDIRECTS_DB": "2754d3a4-6a6f-80ca-b30f-000b828310d2", + "BRANDING_DB": "2764d3a4-6a6f-80f6-8354-000bf2304e00", + "NAVIGATION_KEY": "th-navigation-cache", + "PAGES_KEY": "th-pages-cache", + "IMAGES_KEY": "th-images-cache", + "BOOKINGS_KEY": "th-bookings-cache", + "GUESTS_KEY": "th-guests-cache", + "PROPERTIES_KEY": "th-properties-cache", + "SETTINGS_KEY": "th-settings-cache", + "CACHE_URL": "https://api.tombutcherltd.com/cache", + "R2_PUBLIC_URL": "https://cdn2026.tombutcher.work", + "BLUR_HASH": "true", + "CORS_ORIGIN": "https://tombutcher.work" + } + } + }, + + /** + * Note: Use secrets to store sensitive data. + * https://developers.cloudflare.com/workers/configuration/secrets/ + */ + + /** + * Static Assets + * https://developers.cloudflare.com/workers/static-assets/binding/ + */ + // "assets": { "directory": "./public/", "binding": "ASSETS" }, + + /** + * Service Bindings (communicate between multiple Workers) + * https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings + */ + // "services": [{ "binding": "MY_SERVICE", "service": "my-service" }] + + "dev": { + "ip": "0.0.0.0", + "port": 8787 + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..7b629e9 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,3217 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@cloudflare/kv-asset-handler@0.3.4": + version "0.3.4" + resolved "https://registry.yarnpkg.com/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz#5cc152847c8ae4d280ec5d7f4f6ba8c976b585c3" + integrity sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q== + dependencies: + mime "^3.0.0" + +"@cloudflare/kv-asset-handler@0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.0.tgz#a8588c6a2e89bb3e87fb449295a901c9f6d3e1bf" + integrity sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA== + dependencies: + mime "^3.0.0" + +"@cloudflare/unenv-preset@2.7.7": + version "2.7.7" + resolved "https://registry.yarnpkg.com/@cloudflare/unenv-preset/-/unenv-preset-2.7.7.tgz#b02c1447d0231cff8d774a1009649043997eb5ff" + integrity sha512-HtZuh166y0Olbj9bqqySckz0Rw9uHjggJeoGbDx5x+sgezBXlxO6tQSig2RZw5tgObF8mWI8zaPvQMkQZtAODw== + +"@cloudflare/vitest-pool-workers@^0.6.4": + version "0.6.16" + resolved "https://registry.yarnpkg.com/@cloudflare/vitest-pool-workers/-/vitest-pool-workers-0.6.16.tgz#a36b7a64a98ce95658de102e7ee451a0833a2604" + integrity sha512-fKGP+jMyrIh54QncFa7A5NE3k8fWW6oCU4v9IW6Hiu7cAVKBvhdPUAfYBQS7EU1TsjGJl/y5N4Urds3PUFocoQ== + dependencies: + birpc "0.2.14" + cjs-module-lexer "^1.2.3" + devalue "^4.3.0" + esbuild "0.17.19" + miniflare "3.20250204.1" + semver "^7.5.1" + wrangler "3.109.1" + zod "^3.22.3" + +"@cloudflare/workerd-darwin-64@1.20250204.0": + version "1.20250204.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250204.0.tgz#4e61a4247b2e04227e0ccdd631127cbf0e80b791" + integrity sha512-HpsgbWEfvdcwuZ8WAZhi1TlSCyyHC3tbghpKsOqGDaQNltyAFAWqa278TPNfcitYf/FmV4961v3eqUE+RFdHNQ== + +"@cloudflare/workerd-darwin-64@1.20251008.0": + version "1.20251008.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20251008.0.tgz#3169a0336b59343fb6266b4a91f9b25840b28356" + integrity sha512-yph0H+8mMOK5Z9oDwjb8rI96oTVt4no5lZ43aorcbzsWG9VUIaXSXlBBoB3von6p4YCRW+J3n36fBM9XZ6TLaA== + +"@cloudflare/workerd-darwin-arm64@1.20250204.0": + version "1.20250204.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250204.0.tgz#7242a45a8c4ce5c8eec8b4362f2fdb7f1019b7ec" + integrity sha512-AJ8Tk7KMJqePlch3SH8oL41ROtsrb07hKRHD6M+FvGC3tLtf26rpteAAMNYKMDYKzFNFUIKZNijYDFZjBFndXQ== + +"@cloudflare/workerd-darwin-arm64@1.20251008.0": + version "1.20251008.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20251008.0.tgz#b4f554f4de89cb28293ac7678c688d4126569773" + integrity sha512-Yc4lMGSbM4AEtYRpyDpmk77MsHb6X2BSwJgMgGsLVPmckM7ZHivZkJChfcNQjZ/MGR6nkhYc4iF6TcVS+UMEVw== + +"@cloudflare/workerd-linux-64@1.20250204.0": + version "1.20250204.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250204.0.tgz#d3c255f201bf49bb8511ba734e506d87865ec151" + integrity sha512-RIUfUSnDC8h73zAa+u1K2Frc7nc+eeQoBBP7SaqsRe6JdX8jfIv/GtWjQWCoj8xQFgLvhpJKZ4sTTTV+AilQbw== + +"@cloudflare/workerd-linux-64@1.20251008.0": + version "1.20251008.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20251008.0.tgz#a42a8f4f72fa2586e3b373a7d627140bb4fb1da9" + integrity sha512-AjoQnylw4/5G6SmfhZRsli7EuIK7ZMhmbxtU0jkpciTlVV8H01OsFOgS1d8zaTXMfkWamEfMouy8oH/L7B9YcQ== + +"@cloudflare/workerd-linux-arm64@1.20250204.0": + version "1.20250204.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250204.0.tgz#bda13fd9c919050ba436d2f3235407910d23843c" + integrity sha512-8Ql8jDjoIgr2J7oBD01kd9kduUz60njofrBpAOkjCPed15He8e8XHkYaYow3g0xpae4S2ryrPOeoD3M64sRxeg== + +"@cloudflare/workerd-linux-arm64@1.20251008.0": + version "1.20251008.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20251008.0.tgz#f84829920184fffe5b049c3f5157248eab092ac0" + integrity sha512-hRy9yyvzVq1HsqHZUmFkAr0C8JGjAD/PeeVEGCKL3jln3M9sNCKIrbDXiL+efe+EwajJNNlDxpO+s30uVWVaRg== + +"@cloudflare/workerd-windows-64@1.20250204.0": + version "1.20250204.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250204.0.tgz#34096e76d25fe0e21b6e2774c8e8d827bd787208" + integrity sha512-RpDJO3+to+e17X3EWfRCagboZYwBz2fowc+jL53+fd7uD19v3F59H48lw2BDpHJMRyhg6ouWcpM94OhsHv8ecA== + +"@cloudflare/workerd-windows-64@1.20251008.0": + version "1.20251008.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20251008.0.tgz#60abca6ebc53b1892b81e2e2f7081feb08b3413f" + integrity sha512-Gm0RR+ehfNMsScn2pUcn3N9PDUpy7FyvV9ecHEyclKttvztyFOcmsF14bxEaSVv7iM4TxWEBn1rclmYHxDM4ow== + +"@cspotcode/source-map-support@0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@emnapi/runtime@^1.2.0", "@emnapi/runtime@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.5.0.tgz#9aebfcb9b17195dce3ab53c86787a6b7d058db73" + integrity sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ== + dependencies: + tslib "^2.4.0" + +"@esbuild-plugins/node-globals-polyfill@0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz#0e4497a2b53c9e9485e149bc92ddb228438d6bcf" + integrity sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw== + +"@esbuild-plugins/node-modules-polyfill@0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz#cefa3dc0bd1c16277a8338b52833420c94987327" + integrity sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA== + dependencies: + escape-string-regexp "^4.0.0" + rollup-plugin-node-polyfills "^0.2.1" + +"@esbuild/aix-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" + integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== + +"@esbuild/aix-ppc64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz#830d6476cbbca0c005136af07303646b419f1162" + integrity sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q== + +"@esbuild/android-arm64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz#bafb75234a5d3d1b690e7c2956a599345e84a2fd" + integrity sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA== + +"@esbuild/android-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" + integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== + +"@esbuild/android-arm64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz#d11d4fc299224e729e2190cacadbcc00e7a9fd67" + integrity sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A== + +"@esbuild/android-arm@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz#5898f7832c2298bc7d0ab53701c57beb74d78b4d" + integrity sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A== + +"@esbuild/android-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" + integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== + +"@esbuild/android-arm@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.4.tgz#5660bd25080553dd2a28438f2a401a29959bd9b1" + integrity sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ== + +"@esbuild/android-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz#658368ef92067866d95fb268719f98f363d13ae1" + integrity sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww== + +"@esbuild/android-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" + integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== + +"@esbuild/android-x64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.4.tgz#18ddde705bf984e8cd9efec54e199ac18bc7bee1" + integrity sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ== + +"@esbuild/darwin-arm64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz#584c34c5991b95d4d48d333300b1a4e2ff7be276" + integrity sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg== + +"@esbuild/darwin-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" + integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== + +"@esbuild/darwin-arm64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz#b0b7fb55db8fc6f5de5a0207ae986eb9c4766e67" + integrity sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g== + +"@esbuild/darwin-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz#7751d236dfe6ce136cce343dce69f52d76b7f6cb" + integrity sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw== + +"@esbuild/darwin-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" + integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== + +"@esbuild/darwin-x64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz#e6813fdeba0bba356cb350a4b80543fbe66bf26f" + integrity sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A== + +"@esbuild/freebsd-arm64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz#cacd171665dd1d500f45c167d50c6b7e539d5fd2" + integrity sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ== + +"@esbuild/freebsd-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" + integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== + +"@esbuild/freebsd-arm64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz#dc11a73d3ccdc308567b908b43c6698e850759be" + integrity sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ== + +"@esbuild/freebsd-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz#0769456eee2a08b8d925d7c00b79e861cb3162e4" + integrity sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ== + +"@esbuild/freebsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" + integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== + +"@esbuild/freebsd-x64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz#91da08db8bd1bff5f31924c57a81dab26e93a143" + integrity sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ== + +"@esbuild/linux-arm64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz#38e162ecb723862c6be1c27d6389f48960b68edb" + integrity sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg== + +"@esbuild/linux-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" + integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== + +"@esbuild/linux-arm64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz#efc15e45c945a082708f9a9f73bfa8d4db49728a" + integrity sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ== + +"@esbuild/linux-arm@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz#1a2cd399c50040184a805174a6d89097d9d1559a" + integrity sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA== + +"@esbuild/linux-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" + integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== + +"@esbuild/linux-arm@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz#9b93c3e54ac49a2ede6f906e705d5d906f6db9e8" + integrity sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ== + +"@esbuild/linux-ia32@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz#e28c25266b036ce1cabca3c30155222841dc035a" + integrity sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ== + +"@esbuild/linux-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" + integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== + +"@esbuild/linux-ia32@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz#be8ef2c3e1d99fca2d25c416b297d00360623596" + integrity sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ== + +"@esbuild/linux-loong64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz#0f887b8bb3f90658d1a0117283e55dbd4c9dcf72" + integrity sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ== + +"@esbuild/linux-loong64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" + integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== + +"@esbuild/linux-loong64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz#b0840a2707c3fc02eec288d3f9defa3827cd7a87" + integrity sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA== + +"@esbuild/linux-mips64el@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz#f5d2a0b8047ea9a5d9f592a178ea054053a70289" + integrity sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A== + +"@esbuild/linux-mips64el@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" + integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== + +"@esbuild/linux-mips64el@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz#2a198e5a458c9f0e75881a4e63d26ba0cf9df39f" + integrity sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg== + +"@esbuild/linux-ppc64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz#876590e3acbd9fa7f57a2c7d86f83717dbbac8c7" + integrity sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg== + +"@esbuild/linux-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" + integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== + +"@esbuild/linux-ppc64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz#64f4ae0b923d7dd72fb860b9b22edb42007cf8f5" + integrity sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag== + +"@esbuild/linux-riscv64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz#7f49373df463cd9f41dc34f9b2262d771688bf09" + integrity sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA== + +"@esbuild/linux-riscv64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" + integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== + +"@esbuild/linux-riscv64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz#fb2844b11fdddd39e29d291c7cf80f99b0d5158d" + integrity sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA== + +"@esbuild/linux-s390x@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz#e2afd1afcaf63afe2c7d9ceacd28ec57c77f8829" + integrity sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q== + +"@esbuild/linux-s390x@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" + integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== + +"@esbuild/linux-s390x@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz#1466876e0aa3560c7673e63fdebc8278707bc750" + integrity sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g== + +"@esbuild/linux-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz#8a0e9738b1635f0c53389e515ae83826dec22aa4" + integrity sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw== + +"@esbuild/linux-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" + integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== + +"@esbuild/linux-x64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz#c10fde899455db7cba5f11b3bccfa0e41bf4d0cd" + integrity sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA== + +"@esbuild/netbsd-arm64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz#02e483fbcbe3f18f0b02612a941b77be76c111a4" + integrity sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ== + +"@esbuild/netbsd-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz#c29fb2453c6b7ddef9a35e2c18b37bda1ae5c462" + integrity sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q== + +"@esbuild/netbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" + integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== + +"@esbuild/netbsd-x64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz#ec401fb0b1ed0ac01d978564c5fc8634ed1dc2ed" + integrity sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw== + +"@esbuild/openbsd-arm64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz#f272c2f41cfea1d91b93d487a51b5c5ca7a8c8c4" + integrity sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A== + +"@esbuild/openbsd-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz#95e75a391403cb10297280d524d66ce04c920691" + integrity sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g== + +"@esbuild/openbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" + integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== + +"@esbuild/openbsd-x64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz#2e25950bc10fa9db1e5c868e3d50c44f7c150fd7" + integrity sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw== + +"@esbuild/sunos-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz#722eaf057b83c2575937d3ffe5aeb16540da7273" + integrity sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg== + +"@esbuild/sunos-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" + integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== + +"@esbuild/sunos-x64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz#cd596fa65a67b3b7adc5ecd52d9f5733832e1abd" + integrity sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q== + +"@esbuild/win32-arm64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz#9aa9dc074399288bdcdd283443e9aeb6b9552b6f" + integrity sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag== + +"@esbuild/win32-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" + integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== + +"@esbuild/win32-arm64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz#b4dbcb57b21eeaf8331e424c3999b89d8951dc88" + integrity sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ== + +"@esbuild/win32-ia32@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz#95ad43c62ad62485e210f6299c7b2571e48d2b03" + integrity sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw== + +"@esbuild/win32-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" + integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== + +"@esbuild/win32-ia32@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz#410842e5d66d4ece1757634e297a87635eb82f7a" + integrity sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg== + +"@esbuild/win32-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061" + integrity sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA== + +"@esbuild/win32-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" + integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== + +"@esbuild/win32-x64@0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz#0b17ec8a70b2385827d52314c1253160a0b9bacc" + integrity sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ== + +"@fastify/busboy@^2.0.0": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" + integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== + +"@img/colour@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@img/colour/-/colour-1.0.0.tgz#d2fabb223455a793bf3bf9c70de3d28526aa8311" + integrity sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw== + +"@img/sharp-darwin-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz#ef5b5a07862805f1e8145a377c8ba6e98813ca08" + integrity sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ== + optionalDependencies: + "@img/sharp-libvips-darwin-arm64" "1.0.4" + +"@img/sharp-darwin-arm64@0.34.4": + version "0.34.4" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.4.tgz#8a0dcac9e621ff533fbf2e830f6a977b38d67a0c" + integrity sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA== + optionalDependencies: + "@img/sharp-libvips-darwin-arm64" "1.2.3" + +"@img/sharp-darwin-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz#e03d3451cd9e664faa72948cc70a403ea4063d61" + integrity sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q== + optionalDependencies: + "@img/sharp-libvips-darwin-x64" "1.0.4" + +"@img/sharp-darwin-x64@0.34.4": + version "0.34.4" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.4.tgz#0ba2bd9dbf07f7300fab73305b787e66156f7752" + integrity sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg== + optionalDependencies: + "@img/sharp-libvips-darwin-x64" "1.2.3" + +"@img/sharp-libvips-darwin-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz#447c5026700c01a993c7804eb8af5f6e9868c07f" + integrity sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg== + +"@img/sharp-libvips-darwin-arm64@1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.3.tgz#f43c9aa3b74fd307e4318da63ebbe0ed4c34e744" + integrity sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw== + +"@img/sharp-libvips-darwin-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz#e0456f8f7c623f9dbfbdc77383caa72281d86062" + integrity sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ== + +"@img/sharp-libvips-darwin-x64@1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.3.tgz#c42ff786d4a1f42ef8929dba4a989dd5df6417f0" + integrity sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA== + +"@img/sharp-libvips-linux-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz#979b1c66c9a91f7ff2893556ef267f90ebe51704" + integrity sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA== + +"@img/sharp-libvips-linux-arm64@1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.3.tgz#c9073e5c4b629ee417f777db21c552910d84ed77" + integrity sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ== + +"@img/sharp-libvips-linux-arm@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz#99f922d4e15216ec205dcb6891b721bfd2884197" + integrity sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g== + +"@img/sharp-libvips-linux-arm@1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.3.tgz#3cbc333fd6b8f224a14d69b03a1dd11df897c799" + integrity sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA== + +"@img/sharp-libvips-linux-ppc64@1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.3.tgz#68e0e0076299f43d838468675674fabcc7161d16" + integrity sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg== + +"@img/sharp-libvips-linux-s390x@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz#f8a5eb1f374a082f72b3f45e2fb25b8118a8a5ce" + integrity sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA== + +"@img/sharp-libvips-linux-s390x@1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.3.tgz#7da9ab11a50c0ca905979f0aae14a4ccffab27b2" + integrity sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w== + +"@img/sharp-libvips-linux-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz#d4c4619cdd157774906e15770ee119931c7ef5e0" + integrity sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw== + +"@img/sharp-libvips-linux-x64@1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.3.tgz#3b162d6b190cf77926819040e09fb15eec42135e" + integrity sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg== + +"@img/sharp-libvips-linuxmusl-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz#166778da0f48dd2bded1fa3033cee6b588f0d5d5" + integrity sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA== + +"@img/sharp-libvips-linuxmusl-arm64@1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.3.tgz#ac99576630dd8e33cb598d7c4586f6e0655912ea" + integrity sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw== + +"@img/sharp-libvips-linuxmusl-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz#93794e4d7720b077fcad3e02982f2f1c246751ff" + integrity sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw== + +"@img/sharp-libvips-linuxmusl-x64@1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.3.tgz#93e9495af7bf6c4e0d41dd71d0196c35c3753a1c" + integrity sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g== + +"@img/sharp-linux-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz#edb0697e7a8279c9fc829a60fc35644c4839bb22" + integrity sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA== + optionalDependencies: + "@img/sharp-libvips-linux-arm64" "1.0.4" + +"@img/sharp-linux-arm64@0.34.4": + version "0.34.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.4.tgz#0570ff1a4fa6e1d6779456fca8b5e8c18a6a9cf2" + integrity sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ== + optionalDependencies: + "@img/sharp-libvips-linux-arm64" "1.2.3" + +"@img/sharp-linux-arm@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz#422c1a352e7b5832842577dc51602bcd5b6f5eff" + integrity sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ== + optionalDependencies: + "@img/sharp-libvips-linux-arm" "1.0.5" + +"@img/sharp-linux-arm@0.34.4": + version "0.34.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.4.tgz#5f020d933f54f3fc49203d32c3b7dd0ec11ffcdb" + integrity sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA== + optionalDependencies: + "@img/sharp-libvips-linux-arm" "1.2.3" + +"@img/sharp-linux-ppc64@0.34.4": + version "0.34.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.4.tgz#8d5775f6dc7e30ea3a1efa43798b7690bb5cb344" + integrity sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ== + optionalDependencies: + "@img/sharp-libvips-linux-ppc64" "1.2.3" + +"@img/sharp-linux-s390x@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz#f5c077926b48e97e4a04d004dfaf175972059667" + integrity sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q== + optionalDependencies: + "@img/sharp-libvips-linux-s390x" "1.0.4" + +"@img/sharp-linux-s390x@0.34.4": + version "0.34.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.4.tgz#740aa5b369188ee2c1913b1015e7f830f4dfdb50" + integrity sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw== + optionalDependencies: + "@img/sharp-libvips-linux-s390x" "1.2.3" + +"@img/sharp-linux-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz#d806e0afd71ae6775cc87f0da8f2d03a7c2209cb" + integrity sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA== + optionalDependencies: + "@img/sharp-libvips-linux-x64" "1.0.4" + +"@img/sharp-linux-x64@0.34.4": + version "0.34.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.4.tgz#573ce4196b2d0771bba32acc13a37b7adc9b6212" + integrity sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A== + optionalDependencies: + "@img/sharp-libvips-linux-x64" "1.2.3" + +"@img/sharp-linuxmusl-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz#252975b915894fb315af5deea174651e208d3d6b" + integrity sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" + +"@img/sharp-linuxmusl-arm64@0.34.4": + version "0.34.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.4.tgz#3c91bc8348cc3b42b43c6fca14f9dbb5cb47bd0d" + integrity sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64" "1.2.3" + +"@img/sharp-linuxmusl-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz#3f4609ac5d8ef8ec7dadee80b560961a60fd4f48" + integrity sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64" "1.0.4" + +"@img/sharp-linuxmusl-x64@0.34.4": + version "0.34.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.4.tgz#33de7d476ac9e2db7ef654331b54cc679b806bda" + integrity sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64" "1.2.3" + +"@img/sharp-wasm32@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz#6f44f3283069d935bb5ca5813153572f3e6f61a1" + integrity sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg== + dependencies: + "@emnapi/runtime" "^1.2.0" + +"@img/sharp-wasm32@0.34.4": + version "0.34.4" + resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.34.4.tgz#d617f7b3f851f899802298f360667c20605c0198" + integrity sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA== + dependencies: + "@emnapi/runtime" "^1.5.0" + +"@img/sharp-win32-arm64@0.34.4": + version "0.34.4" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.4.tgz#38e2c8a88826eac647f7c3f99efefb39897a8f5c" + integrity sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA== + +"@img/sharp-win32-ia32@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz#1a0c839a40c5351e9885628c85f2e5dfd02b52a9" + integrity sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ== + +"@img/sharp-win32-ia32@0.34.4": + version "0.34.4" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.4.tgz#003a7eb0fdaba600790c3007cfd756e41a9cf749" + integrity sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw== + +"@img/sharp-win32-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz#56f00962ff0c4e0eb93d34a047d29fa995e3e342" + integrity sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg== + +"@img/sharp-win32-x64@0.34.4": + version "0.34.4" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.4.tgz#b19f1f88ace8bfc20784a0ad31767f3438e025d1" + integrity sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig== + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.5.5": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@napi-rs/canvas-android-arm64@0.1.80": + version "0.1.80" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.80.tgz#2779ca5c8aaeb46c85eb72d29f1eb34efd46fb45" + integrity sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ== + +"@napi-rs/canvas-darwin-arm64@0.1.80": + version "0.1.80" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.80.tgz#638eaa2d0a2a373c7d15748743182718dcd95c4b" + integrity sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ== + +"@napi-rs/canvas-darwin-x64@0.1.80": + version "0.1.80" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.80.tgz#bd6bc048dbd4b02b9620d9d07117ed93e6970978" + integrity sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA== + +"@napi-rs/canvas-linux-arm-gnueabihf@0.1.80": + version "0.1.80" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.80.tgz#ce6bfbeb19d9234c42df5c384e5989aa7d734789" + integrity sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ== + +"@napi-rs/canvas-linux-arm64-gnu@0.1.80": + version "0.1.80" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.80.tgz#3b7a7832fef763826fa5fb740d5757204e52607d" + integrity sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw== + +"@napi-rs/canvas-linux-arm64-musl@0.1.80": + version "0.1.80" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.80.tgz#d8ccd91f31d70760628623cd575134ada17690a3" + integrity sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg== + +"@napi-rs/canvas-linux-riscv64-gnu@0.1.80": + version "0.1.80" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.80.tgz#927a3b859a0e3c691beaf52a19bc4736c4ffc9b8" + integrity sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw== + +"@napi-rs/canvas-linux-x64-gnu@0.1.80": + version "0.1.80" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.80.tgz#25c0416bcedd6fadc15295e9afa8d9697232050c" + integrity sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA== + +"@napi-rs/canvas-linux-x64-musl@0.1.80": + version "0.1.80" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.80.tgz#de85d644e09120a60996bbe165cc2efee804551b" + integrity sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg== + +"@napi-rs/canvas-win32-x64-msvc@0.1.80": + version "0.1.80" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.80.tgz#6bb95885d9377912d71f1372fc1916fb54d6ef0a" + integrity sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg== + +"@napi-rs/canvas@^0.1.80": + version "0.1.80" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas/-/canvas-0.1.80.tgz#53615bea56fd94e07331ab13caa7a39efc4914ab" + integrity sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww== + optionalDependencies: + "@napi-rs/canvas-android-arm64" "0.1.80" + "@napi-rs/canvas-darwin-arm64" "0.1.80" + "@napi-rs/canvas-darwin-x64" "0.1.80" + "@napi-rs/canvas-linux-arm-gnueabihf" "0.1.80" + "@napi-rs/canvas-linux-arm64-gnu" "0.1.80" + "@napi-rs/canvas-linux-arm64-musl" "0.1.80" + "@napi-rs/canvas-linux-riscv64-gnu" "0.1.80" + "@napi-rs/canvas-linux-x64-gnu" "0.1.80" + "@napi-rs/canvas-linux-x64-musl" "0.1.80" + "@napi-rs/canvas-win32-x64-msvc" "0.1.80" + +"@notionhq/client@^5.1.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@notionhq/client/-/client-5.2.0.tgz#c0dbf76dd594073d0d7b2e2ed594ecb51250cd9e" + integrity sha512-eRtjbwZ9sIZIHMZuJQMmq/dw4+8PI2lGfo0yu0XC65LFEPpQ75by4ZFZ8RlCxBbN2OJG8xCpP2hbCA8j+Sj3Jw== + +"@poppinss/colors@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@poppinss/colors/-/colors-4.1.5.tgz#09273b845a4816f5fd9c53c78a3bc656650fe18f" + integrity sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw== + dependencies: + kleur "^4.1.5" + +"@poppinss/dumper@^0.6.4": + version "0.6.4" + resolved "https://registry.yarnpkg.com/@poppinss/dumper/-/dumper-0.6.4.tgz#b902ff0b2850f5367f947ffdb2d7154f22856d43" + integrity sha512-iG0TIdqv8xJ3Lt9O8DrPRxw1MRLjNpoqiSGU03P/wNLP/s0ra0udPJ1J2Tx5M0J3H/cVyEgpbn8xUKRY9j59kQ== + dependencies: + "@poppinss/colors" "^4.1.5" + "@sindresorhus/is" "^7.0.2" + supports-color "^10.0.0" + +"@poppinss/exception@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@poppinss/exception/-/exception-1.2.2.tgz#8d30d42e126c54fe84e997433e4dcac610090743" + integrity sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg== + +"@rollup/rollup-android-arm-eabi@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz#59e7478d310f7e6a7c72453978f562483828112f" + integrity sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA== + +"@rollup/rollup-android-arm64@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz#a825192a0b1b2f27a5c950c439e7e37a33c5d056" + integrity sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w== + +"@rollup/rollup-darwin-arm64@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz#4ee37078bccd725ae3c5f30ef92efc8e1bf886f3" + integrity sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg== + +"@rollup/rollup-darwin-x64@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz#43cc08bd05bf9f388f125e7210a544e62d368d90" + integrity sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw== + +"@rollup/rollup-freebsd-arm64@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz#bc8e640e28abe52450baf3fc80d9b26d9bb6587d" + integrity sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ== + +"@rollup/rollup-freebsd-x64@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz#e981a22e057cc8c65bb523019d344d3a66b15bbc" + integrity sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw== + +"@rollup/rollup-linux-arm-gnueabihf@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz#4036b68904f392a20f3499d63b33e055b67eb274" + integrity sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ== + +"@rollup/rollup-linux-arm-musleabihf@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz#d3b1b9589606e0ff916801c855b1ace9e733427a" + integrity sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q== + +"@rollup/rollup-linux-arm64-gnu@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz#cbf0943c477e3b96340136dd3448eaf144378cf2" + integrity sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg== + +"@rollup/rollup-linux-arm64-musl@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz#837f5a428020d5dce1c3b4cc049876075402cf78" + integrity sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g== + +"@rollup/rollup-linux-loong64-gnu@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz#532c214ababb32ab4bc21b4054278b9a8979e516" + integrity sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ== + +"@rollup/rollup-linux-ppc64-gnu@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz#93900163b61b49cee666d10ee38257a8b1dd161a" + integrity sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g== + +"@rollup/rollup-linux-riscv64-gnu@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz#f0ffdcc7066ca04bc972370c74289f35c7a7dc42" + integrity sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg== + +"@rollup/rollup-linux-riscv64-musl@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz#361695c39dbe96773509745d77a870a32a9f8e48" + integrity sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA== + +"@rollup/rollup-linux-s390x-gnu@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz#09fc6cc2e266a2324e366486ae5d1bca48c43a6a" + integrity sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA== + +"@rollup/rollup-linux-x64-gnu@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz#aa9d5b307c08f05d3454225bb0a2b4cc87eeb2e1" + integrity sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg== + +"@rollup/rollup-linux-x64-musl@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz#26949e5b4645502a61daba2f7a8416bd17cb5382" + integrity sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw== + +"@rollup/rollup-openharmony-arm64@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz#ef493c072f9dac7e0edb6c72d63366846b6ffcd9" + integrity sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA== + +"@rollup/rollup-win32-arm64-msvc@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz#56e1aaa6a630d2202ee7ec0adddd05cf384ffd44" + integrity sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ== + +"@rollup/rollup-win32-ia32-msvc@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz#0a44bbf933a9651c7da2b8569fa448dec0de7480" + integrity sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw== + +"@rollup/rollup-win32-x64-gnu@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz#730e12f0b60b234a7c02d5d3179ca3ec7972033d" + integrity sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ== + +"@rollup/rollup-win32-x64-msvc@4.52.4": + version "4.52.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz#5b2dd648a960b8fa00d76f2cc4eea2f03daa80f4" + integrity sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w== + +"@sindresorhus/is@^7.0.2": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-7.1.0.tgz#ae17a8f9188644a9be064e0e67932351f82fe967" + integrity sha512-7F/yz2IphV39hiS2zB4QYVkivrptHHh0K8qJJd9HhuWSdvf8AN7NpebW3CcDZDBQsUPMoDKWsY2WWgW7bqOcfA== + +"@speed-highlight/core@^1.2.7": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@speed-highlight/core/-/core-1.2.7.tgz#eeaa7c1e7198559abbb98e4acbc93d108d35f2d3" + integrity sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g== + +"@types/estree@1.0.8", "@types/estree@^1.0.0": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + +"@vitest/expect@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.9.tgz#b566ea20d58ea6578d8dc37040d6c1a47ebe5ff8" + integrity sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw== + dependencies: + "@vitest/spy" "2.1.9" + "@vitest/utils" "2.1.9" + chai "^5.1.2" + tinyrainbow "^1.2.0" + +"@vitest/mocker@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.9.tgz#36243b27351ca8f4d0bbc4ef91594ffd2dc25ef5" + integrity sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg== + dependencies: + "@vitest/spy" "2.1.9" + estree-walker "^3.0.3" + magic-string "^0.30.12" + +"@vitest/pretty-format@2.1.9", "@vitest/pretty-format@^2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.9.tgz#434ff2f7611689f9ce70cd7d567eceb883653fdf" + integrity sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ== + dependencies: + tinyrainbow "^1.2.0" + +"@vitest/runner@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.9.tgz#cc18148d2d797fd1fd5908d1f1851d01459be2f6" + integrity sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g== + dependencies: + "@vitest/utils" "2.1.9" + pathe "^1.1.2" + +"@vitest/snapshot@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.9.tgz#24260b93f798afb102e2dcbd7e61c6dfa118df91" + integrity sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ== + dependencies: + "@vitest/pretty-format" "2.1.9" + magic-string "^0.30.12" + pathe "^1.1.2" + +"@vitest/spy@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.9.tgz#cb28538c5039d09818b8bfa8edb4043c94727c60" + integrity sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ== + dependencies: + tinyspy "^3.0.2" + +"@vitest/utils@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.9.tgz#4f2486de8a54acf7ecbf2c5c24ad7994a680a6c1" + integrity sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ== + dependencies: + "@vitest/pretty-format" "2.1.9" + loupe "^3.1.2" + tinyrainbow "^1.2.0" + +acorn-walk@8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" + integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== + +acorn@8.14.0: + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== + +acorn@^8.15.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + +ajv@^6.12.3: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +align-text@^0.1.1, align-text@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" + integrity sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg== + dependencies: + kind-of "^3.0.2" + longest "^1.0.1" + repeat-string "^1.5.2" + +as-table@^1.0.36: + version "1.0.55" + resolved "https://registry.yarnpkg.com/as-table/-/as-table-1.0.55.tgz#dc984da3937745de902cea1d45843c01bdbbec4f" + integrity sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ== + dependencies: + printable-characters "^1.0.42" + +asn1@~0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== + +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + +async@^0.9.0: + version "0.9.2" + resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" + integrity sha512-l6ToIJIotphWahxxHyzK9bnLR6kM4jJIIgLShZeqLY7iboHoGkdgFl7W2/Ivi4SkMJYGKqW8vSuk0uKUj6qsSw== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== + +aws4@^1.8.0: + version "1.13.2" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.13.2.tgz#0aa167216965ac9474ccfa83892cfb6b3e1e52ef" + integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +batch@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.5.3.tgz#3f3414f380321743bfc1042f9a83ff1d5824d464" + integrity sha512-aQgHPLH2DHpFTpBl5/GiVdNzHEqsLCSs1RiPvqkKP1+7RkNJlv71kL8/KXmvvaLqoZ7ylmvqkZhLjjAoRz8Xgw== + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== + dependencies: + tweetnacl "^0.14.3" + +birpc@0.2.14: + version "0.2.14" + resolved "https://registry.yarnpkg.com/birpc/-/birpc-0.2.14.tgz#4a5498771e6ff24cf8ae5f47faf90e76ca2fce03" + integrity sha512-37FHE8rqsYM5JEKCnXFyHpBCzvgHEExwVVTq+nUmloInU7l8ezD1TpOhKpS8oe1DTYFqEK27rFZVKG43oTqXRA== + +blake3-wasm@2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/blake3-wasm/-/blake3-wasm-2.1.5.tgz#b22dbb84bc9419ed0159caa76af4b1b132e6ba52" + integrity sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g== + +blurhash@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-2.0.5.tgz#efde729fc14a2f03571a6aa91b49cba80d1abe4b" + integrity sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w== + +boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +brace-expansion@^1.0.0: + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + +camelcase@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" + integrity sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g== + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== + +center-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" + integrity sha512-Baz3aNe2gd2LP2qk5U+sDk/m4oSuwSDcBfayTCTBoWpfIGO5XFxPmjILQII4NGiZjD6DoDI6kf7gKaxkf7s3VQ== + dependencies: + align-text "^0.1.3" + lazy-cache "^1.0.3" + +chai@^5.1.2: + version "5.3.3" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.3.3.tgz#dd3da955e270916a4bd3f625f4b919996ada7e06" + integrity sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" + +check-error@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" + integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== + +cheerio@0.19.0, cheerio@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.19.0.tgz#772e7015f2ee29965096d71ea4175b75ab354925" + integrity sha512-Fwcm3zkR37STnPC8FepSHeSYJM5Rd596TZOcfDUdojR4Q735aK1Xn+M+ISagNneuCwMjK28w4kX+ETILGNT/UQ== + dependencies: + css-select "~1.0.0" + dom-serializer "~0.1.0" + entities "~1.1.1" + htmlparser2 "~3.8.1" + lodash "^3.2.0" + +cjs-module-lexer@^1.2.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" + integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== + +clean-css@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-1.1.7.tgz#601ef9cf7642b982cb33efc9488a6444c986686e" + integrity sha512-eIhTPpuWxQpiKPvZdF92jx0/6l4awOtf4zCawFE8f4Gboka/eXBofEFkXf1KtbLj5wjjHWDg6gtHQB538qEt9w== + dependencies: + commander "2.0.x" + +cli-color@^0.3.2: + version "0.3.3" + resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-0.3.3.tgz#12d5bdd158ff8a0b0db401198913c03df069f6f5" + integrity sha512-e8BuO18ajBxfbL2eJldk8VgYDjshE8hV1ECafR0pl5EzgbyK9YnmGWLHylXVrQgaK4J1SJJzWDjIncfVuJvtpg== + dependencies: + d "~0.1.1" + es5-ext "~0.10.6" + memoizee "~0.3.8" + timers-ext "0.1" + +cliui@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" + integrity sha512-GIOYRizG+TGoc7Wgc1LiOTLare95R3mzKgoln+Q/lE4ceiYH19gUpl0l0Ffq4lJDEf3FxujMe6IBfOCs7pfqNA== + dependencies: + center-align "^0.1.1" + right-align "^0.1.1" + wordwrap "0.0.2" + +clone-stats@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" + integrity sha512-dhUqc57gSMCo6TX85FLfe51eC/s+Im2MLkAgJwfaRRexR2tA4dd3eLEW4L6efzHc2iNorrRRXITifnDLlRrhaA== + +clone@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f" + integrity sha512-g62n3Kb9cszeZvmvBUqP/dsEJD/+80pDA8u8KqHnAPrVnQ2Je9rVV6opxkhuWCd1kCn2gOibzDKxCtBvD3q5kA== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + +colors@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@2.0.x: + version "2.0.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.0.0.tgz#d1b86f901f8b64bd941bdeadaf924530393be928" + integrity sha512-qebjpyeaA/nJ4w3EO2cV2++/zEkccPnjWogzA2rff+Lk8ILI75vULeTmyd4wPxWdKwtP3J+G39IXVZadh0UHyw== + +commander@2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" + integrity sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A== + dependencies: + graceful-readlink ">= 1.0.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +confbox@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06" + integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w== + +cookie@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + +cookie@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.0.2.tgz#27360701532116bd3f1f9416929d176afe1e4610" + integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA== + +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cross-spawn-async@^2.1.8: + version "2.2.5" + resolved "https://registry.yarnpkg.com/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz#845ff0c0834a3ded9d160daca6d390906bb288cc" + integrity sha512-snteb3aVrxYYOX9e8BabYFK9WhCDhTlw1YQktfTthBogxri4/2r9U2nQc0ffY73ZAxezDc+U8gvHAeU1wy1ubQ== + dependencies: + lru-cache "^4.0.0" + which "^1.2.8" + +css-select@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.0.0.tgz#b1121ca51848dd264e2244d058cee254deeb44b0" + integrity sha512-/xPlD7betkfd7ChGkLGGWx5HWyiHDOSn7aACLzdH0nwucPvB0EAm8hMBm7Xn7vGfAeRRN7KZ8wumGm8NoNcMRw== + dependencies: + boolbase "~1.0.0" + css-what "1.0" + domutils "1.4" + nth-check "~1.0.0" + +css-what@1.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-1.0.0.tgz#d7cc2df45180666f99d2b14462639469e00f736c" + integrity sha512-60SUMPBreXrLXgvpM8kYpO0AOyMRhdRlXFX5BMQbZq1SIJCyNE56nqFQhmvREQdUJpedbGRYZ5wOyq3/F6q5Zw== + +css@^2.2.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" + integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== + dependencies: + inherits "^2.0.3" + source-map "^0.6.1" + source-map-resolve "^0.5.2" + urix "^0.1.0" + +cssom@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.0.tgz#386d5135528fe65c1ee1bc7c4e55a38854dbcf7a" + integrity sha512-rdm8ap6kLpJjI9MDKoECB/Eb8ft+JZNDWJ6zETzR34vUTLLgztSNUVFxOzojw3F1WJewhzQETeh5EHwHMUumbQ== + +d@1, d@^1.0.1, d@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.2.tgz#2aefd554b81981e7dccf72d6842ae725cb17e5de" + integrity sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw== + dependencies: + es5-ext "^0.10.64" + type "^2.7.2" + +d@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/d/-/d-0.1.1.tgz#da184c535d18d8ee7ba2aa229b914009fae11309" + integrity sha512-0SdM9V9pd/OXJHoWmTfNPTAeD+lw6ZqHg+isPyBFuJsZLSE0Ygg1cYZ/0l6DrKQXMOqGOu1oWupMoOfoRfMZrQ== + dependencies: + es5-ext "~0.10.2" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== + dependencies: + assert-plus "^1.0.0" + +data-uri-to-buffer@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz#d296973d5a4897a5dbe31716d118211921f04770" + integrity sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA== + +datauri@~0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/datauri/-/datauri-0.2.1.tgz#f4e8addbb3e54e3dc12d1c88543b8b0b1bf692fa" + integrity sha512-DnSr++hwAaHxdj7vwxbPTGz+MAiN+ZaiutJW4CpuYGIFMaJcox6v2/Va3++CQOhx0tYD1dPQn/qKeTnrLihlUw== + dependencies: + mimer "*" + templayed "*" + +dayjs@^1.11.18: + version "1.11.18" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.18.tgz#835fa712aac52ab9dec8b1494098774ed7070a11" + integrity sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA== + +debug@^4.3.7: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +decamelize@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + +decode-uri-component@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== + +deep-eql@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + +deep-extend@^0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" + integrity sha512-cQ0iXSEKi3JRNhjUsLWvQ+MVPxLVqpwCd0cFsWbJxlCim2TlCo1JvN5WaPdPvSpUdEnkJ/X+mPGcq5RJ68EK8g== + +deep-rename-keys@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/deep-rename-keys/-/deep-rename-keys-0.2.1.tgz#ede78537d7a66a2be61517e2af956d7f58a3f1d8" + integrity sha512-RHd9ABw4Fvk+gYDWqwOftG849x0bYOySl/RgX0tLI9i27ZIeSO91mLZJEp7oPHOMFqHvpgu21YptmDt0FYD/0A== + dependencies: + kind-of "^3.0.2" + rename-keys "^1.1.2" + +defu@^6.1.4: + version "6.1.4" + resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.4.tgz#4e0c9cf9ff68fe5f3d7f2765cc1a012dfdcb0479" + integrity sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +detect-libc@^2.0.3, detect-libc@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" + integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== + +devalue@^4.3.0: + version "4.3.3" + resolved "https://registry.yarnpkg.com/devalue/-/devalue-4.3.3.tgz#e35df3bdc49136837e77986f629b9fa6fef50726" + integrity sha512-UH8EL6H2ifcY8TbD2QsxwCC/pr5xSwPvv85LrLXVihmHVC3T3YqTCIwnR5ak0yO1KYqlxrPVOA/JVZJYPy2ATg== + +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + +dom-serializer@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" + integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== + dependencies: + domelementtype "^1.3.0" + entities "^1.1.1" + +domelementtype@1, domelementtype@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + +domelementtype@^2.0.1: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738" + integrity sha512-q9bUwjfp7Eif8jWxxxPSykdRZAb6GkguBGSgvvCrhI9wB71W2K/Kvv4E61CF/mcCfnVJDeDWx/Vb/uAqbDj6UQ== + dependencies: + domelementtype "1" + +domutils@1.4: + version "1.4.3" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.4.3.tgz#0865513796c6b306031850e175516baf80b72a6f" + integrity sha512-ZkVgS/PpxjyJMb+S2iVHHEZjVnOUtjGp0/zstqKGTE9lrZtNHlNQmLwP/lhLMEApYbzc08BKMx9IFpKhaSbW1w== + dependencies: + domelementtype "1" + +domutils@1.5: + version "1.5.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" + integrity sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw== + dependencies: + dom-serializer "0" + domelementtype "1" + +duplexify@^3.2.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +end-of-stream@^1.0.0: + version "1.4.5" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c" + integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg== + dependencies: + once "^1.4.0" + +entities@1.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26" + integrity sha512-LbLqfXgJMmy81t+7c14mnulFHJ170cM6E+0vMXR9k/ZiZwgX8i5pNgjTCX3SO4VeUsFLV+8InixoretwU+MjBQ== + +entities@^1.1.1, entities@~1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +error-stack-parser-es@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz#e6a1655dd12f39bb3a85bf4c7088187d78740327" + integrity sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA== + +es-module-lexer@^1.5.4: + version "1.7.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" + integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== + +es5-ext@^0.10.35, es5-ext@^0.10.62, es5-ext@^0.10.64, es5-ext@~0.10.11, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.5, es5-ext@~0.10.6: + version "0.10.64" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.64.tgz#12e4ffb48f1ba2ea777f1fcdd1918ef73ea21714" + integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + esniff "^2.0.1" + next-tick "^1.1.0" + +es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-iterator@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-0.1.3.tgz#d6f58b8c4fc413c249b4baa19768f8e4d7c8944e" + integrity sha512-6TOmbFM6OPWkTe+bQ3ZuUkvqcWUjAnYjKUCLdbvRsAUz2Pr+fYIibwNXNkLNtIK9PPFbNMZZddaRNkyJhlGJhA== + dependencies: + d "~0.1.1" + es5-ext "~0.10.5" + es6-symbol "~2.0.1" + +es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.4" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.4.tgz#f4e7d28013770b4208ecbf3e0bf14d3bcb557b8c" + integrity sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg== + dependencies: + d "^1.0.2" + ext "^1.7.0" + +es6-symbol@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-2.0.1.tgz#761b5c67cfd4f1d18afb234f691d678682cb3bf3" + integrity sha512-wjobO4zO8726HVU7mI2OA/B6QszqwHJuKab7gKHVx+uRfVVYGcWJkCIFxV2Madqb9/RUSrhJ/r6hPfG7FsWtow== + dependencies: + d "~0.1.1" + es5-ext "~0.10.5" + +es6-weak-map@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-0.1.4.tgz#706cef9e99aa236ba7766c239c8b9e286ea7d228" + integrity sha512-P+N5Cd2TXeb7G59euFiM7snORspgbInS29Nbf3KNO2JQp/DyhvMCDWd58nsVAXwYJ6W3Bx7qDdy6QQ3PCJ7jKQ== + dependencies: + d "~0.1.1" + es5-ext "~0.10.6" + es6-iterator "~0.1.3" + es6-symbol "~2.0.1" + +esbuild@0.17.19: + version "0.17.19" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.19.tgz#087a727e98299f0462a3d0bcdd9cd7ff100bd955" + integrity sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw== + optionalDependencies: + "@esbuild/android-arm" "0.17.19" + "@esbuild/android-arm64" "0.17.19" + "@esbuild/android-x64" "0.17.19" + "@esbuild/darwin-arm64" "0.17.19" + "@esbuild/darwin-x64" "0.17.19" + "@esbuild/freebsd-arm64" "0.17.19" + "@esbuild/freebsd-x64" "0.17.19" + "@esbuild/linux-arm" "0.17.19" + "@esbuild/linux-arm64" "0.17.19" + "@esbuild/linux-ia32" "0.17.19" + "@esbuild/linux-loong64" "0.17.19" + "@esbuild/linux-mips64el" "0.17.19" + "@esbuild/linux-ppc64" "0.17.19" + "@esbuild/linux-riscv64" "0.17.19" + "@esbuild/linux-s390x" "0.17.19" + "@esbuild/linux-x64" "0.17.19" + "@esbuild/netbsd-x64" "0.17.19" + "@esbuild/openbsd-x64" "0.17.19" + "@esbuild/sunos-x64" "0.17.19" + "@esbuild/win32-arm64" "0.17.19" + "@esbuild/win32-ia32" "0.17.19" + "@esbuild/win32-x64" "0.17.19" + +esbuild@0.25.4: + version "0.25.4" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.4.tgz#bb9a16334d4ef2c33c7301a924b8b863351a0854" + integrity sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q== + optionalDependencies: + "@esbuild/aix-ppc64" "0.25.4" + "@esbuild/android-arm" "0.25.4" + "@esbuild/android-arm64" "0.25.4" + "@esbuild/android-x64" "0.25.4" + "@esbuild/darwin-arm64" "0.25.4" + "@esbuild/darwin-x64" "0.25.4" + "@esbuild/freebsd-arm64" "0.25.4" + "@esbuild/freebsd-x64" "0.25.4" + "@esbuild/linux-arm" "0.25.4" + "@esbuild/linux-arm64" "0.25.4" + "@esbuild/linux-ia32" "0.25.4" + "@esbuild/linux-loong64" "0.25.4" + "@esbuild/linux-mips64el" "0.25.4" + "@esbuild/linux-ppc64" "0.25.4" + "@esbuild/linux-riscv64" "0.25.4" + "@esbuild/linux-s390x" "0.25.4" + "@esbuild/linux-x64" "0.25.4" + "@esbuild/netbsd-arm64" "0.25.4" + "@esbuild/netbsd-x64" "0.25.4" + "@esbuild/openbsd-arm64" "0.25.4" + "@esbuild/openbsd-x64" "0.25.4" + "@esbuild/sunos-x64" "0.25.4" + "@esbuild/win32-arm64" "0.25.4" + "@esbuild/win32-ia32" "0.25.4" + "@esbuild/win32-x64" "0.25.4" + +esbuild@^0.21.3: + version "0.21.5" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" + integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== + 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" + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +esniff@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308" + integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg== + dependencies: + d "^1.0.1" + es5-ext "^0.10.62" + event-emitter "^0.3.5" + type "^2.7.2" + +estree-walker@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" + integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== + +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + +event-emitter@^0.3.5, event-emitter@~0.3.4: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== + dependencies: + d "1" + es5-ext "~0.10.14" + +eventemitter3@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" + integrity sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg== + +exit-hook@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-2.2.1.tgz#007b2d92c6428eda2b76e7016a34351586934593" + integrity sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw== + +expect-type@^1.1.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.2.2.tgz#c030a329fb61184126c8447585bc75a7ec6fbff3" + integrity sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA== + +exsolve@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/exsolve/-/exsolve-1.0.7.tgz#3b74e4c7ca5c5f9a19c3626ca857309fa99f9e9e" + integrity sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw== + +ext@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + dependencies: + type "^2.7.2" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== + +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +find-index@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4" + integrity sha512-uJ5vWrfBKMcE6y2Z8834dwEZj9mNGxYa3t3I53OwFeuZ8D9oc2E5zcsrkuhX6h4iYrjhiv0T3szQmxlAV9uxDg== + +first-chunk-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e" + integrity sha512-ArRi5axuv66gEsyl3UuK80CzW7t56hem73YGNYxNWTGNKFJUadSb9Gu9SHijYEUi8ulQMf1bJomYNwSCPHhtTQ== + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +gaze@^0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/gaze/-/gaze-0.5.2.tgz#40b709537d24d1d45767db5a908689dfe69ac44f" + integrity sha512-3IWbXGkDDHFX8zIlNdfnmhvlSMhpBO6tDr4InB8fGku6dh/gjFPGNqcdsXJajZg05x9jRzXbL6gCnCnuMap4tw== + dependencies: + globule "~0.1.0" + +get-source@^2.0.12: + version "2.0.12" + resolved "https://registry.yarnpkg.com/get-source/-/get-source-2.0.12.tgz#0b47d57ea1e53ce0d3a69f4f3d277eb8047da944" + integrity sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w== + dependencies: + data-uri-to-buffer "^2.0.0" + source-map "^0.6.1" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== + dependencies: + assert-plus "^1.0.0" + +glob-stream@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-4.1.1.tgz#b842df10d688c7eb6bcfcebd846f3852296b3200" + integrity sha512-P/IscjTmgjOQ53stHEjq2QUzTqcdBOzxL/Cif8SYad+Kb8IgqjYlggefqCSi33Vg5Ey1tx1pn+pyE+tRtf8j1A== + dependencies: + glob "^4.3.1" + glob2base "^0.0.12" + minimatch "^2.0.1" + ordered-read-streams "^0.1.0" + through2 "^0.6.1" + unique-stream "^2.0.2" + +glob-to-regexp@0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob-watcher@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.8.tgz#68aeb661e7e2ce8d3634381b2ec415f00c6bc2a4" + integrity sha512-Wbq7bkUwTSc3L25dKq3CPpFaaeYxARAUJtEUeNMmjcDOwQl6T2CI3blthbfLP5BCiYbAX7c/eSxZ3M1R+bPnDw== + dependencies: + gaze "^0.5.1" + +glob2base@^0.0.12: + version "0.0.12" + resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56" + integrity sha512-ZyqlgowMbfj2NPjxaZZ/EtsXlOch28FRXgMd64vqZWk1bT9+wvSRLYD1om9M7QfQru51zJPAT17qXm4/zd+9QA== + dependencies: + find-index "^0.1.1" + +glob@^4.3.1: + version "4.5.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f" + integrity sha512-I0rTWUKSZKxPSIAIaqhSXTM/DiII6wame+rEC3cFA5Lqmr9YmdL7z6Hj9+bdWtTvoY1Su4/OiMLmb37Y7JzvJQ== + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "^2.0.1" + once "^1.3.0" + +glob@~3.1.21: + version "3.1.21" + resolved "https://registry.yarnpkg.com/glob/-/glob-3.1.21.tgz#d29e0a055dea5138f4d07ed40e8982e83c2066cd" + integrity sha512-ANhy2V2+tFpRajE3wN4DhkNQ08KDr0Ir1qL12/cUe5+a7STEK8jkW4onUYuY8/06qAFuT5je7mjAqzx0eKI2tQ== + dependencies: + graceful-fs "~1.2.0" + inherits "1" + minimatch "~0.2.11" + +globule@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/globule/-/globule-0.1.0.tgz#d9c8edde1da79d125a151b79533b978676346ae5" + integrity sha512-3eIcA2OjPCm4VvwIwZPzIxCVssA8HSpM2C6c6kK5ufJH4FGwWoyqL3In19uuX4oe+TwH3w2P1nQDmW56iehO4A== + dependencies: + glob "~3.1.21" + lodash "~1.0.1" + minimatch "~0.2.11" + +graceful-fs@^3.0.0: + version "3.0.12" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.12.tgz#0034947ce9ed695ec8ab0b854bc919e82b1ffaef" + integrity sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg== + dependencies: + natives "^1.1.3" + +graceful-fs@^4.2.6: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graceful-fs@~1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364" + integrity sha512-iiTUZ5vZ+2ZV+h71XAgwCSu6+NAizhFU3Yw8aC/hH5SQ3SnISqEqAek40imAFGtDcwJKNhXvSY+hzIolnLwcdQ== + +"graceful-readlink@>= 1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" + integrity sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w== + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + +htmlparser2@~3.8.1: + version "3.8.3" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.8.3.tgz#996c28b191516a8be86501a7d79757e5c70c1068" + integrity sha512-hBxEg3CYXe+rPIua8ETe7tmG3XDn9B0edOE/e9wH2nLczxzgdu0m0aNHY+5wFZiviLWLdANPJTssa92dMcXQ5Q== + dependencies: + domelementtype "1" + domhandler "2.3" + domutils "1.5" + entities "1.0" + readable-stream "1.1" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-1.0.2.tgz#ca4309dadee6b54cc0b8d247e8d7c7a0975bdc9b" + integrity sha512-Al67oatbRSo3RV5hRqIoln6Y5yMVbJSIn4jEJNL7VCImzq/kLr7vvb6sFRJXqr8rpHc/2kJOM+y0sPKN47VdzA== + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arrayish@^0.3.1: + version "0.3.4" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.4.tgz#1ee5553818511915685d33bb13d31bf854e5059d" + integrity sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA== + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q== + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== + +jpeg-js@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" + integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg== + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + +jsprim@^1.2.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" + +juice@^1.0.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/juice/-/juice-1.11.0.tgz#4a7ff6bef3f3fa4b610f824aeb27ed8f3601ad73" + integrity sha512-i2lbYX1H5xjtV6Tqa8kIcE4rKZ5cFeuNlQZTXZ5uh2nk5CKCCWtgDs6v5DehQMc0cD6+6LdyjIHIZnRLHGbohw== + dependencies: + batch "0.5.3" + cheerio "0.19.0" + commander "2.9.0" + cross-spawn-async "^2.1.8" + cssom "0.3.0" + deep-extend "^0.4.0" + slick "1.12.2" + util-deprecate "^1.0.2" + web-resource-inliner "1.2.1" + +kind-of@^3.0.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ== + dependencies: + is-buffer "^1.1.5" + +kleur@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" + integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== + +lazy-cache@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" + integrity sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ== + +lodash@^3.10.1, lodash@^3.2.0, lodash@^3.5.0: + version "3.10.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" + integrity sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +lodash@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" + integrity sha512-0VSEDVec/Me2eATuoiQd8IjyBMMX0fahob8YJ96V1go2RjvCk1m1GxmtfXn8RNSaLaTtop7fsuhhu9oLk3hUgA== + +longest@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" + integrity sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg== + +loupe@^3.1.0, loupe@^3.1.2: + version "3.2.1" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.2.1.tgz#0095cf56dc5b7a9a7c08ff5b1a8796ec8ad17e76" + integrity sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ== + +lru-cache@2: + version "2.7.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" + integrity sha512-WpibWJ60c3AgAz8a2iYErDrcT2C7OmKnsWhIcHOjkUHFjkXncJhtLxNSqUmxRxRunpb5I8Vprd7aNSd2NtksJQ== + +lru-cache@^4.0.0: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +lru-queue@0.1: + version "0.1.0" + resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" + integrity sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ== + dependencies: + es5-ext "~0.10.2" + +magic-string@^0.25.3: + version "0.25.9" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" + integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== + dependencies: + sourcemap-codec "^1.4.8" + +magic-string@^0.30.12: + version "0.30.19" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.19.tgz#cebe9f104e565602e5d2098c5f2e79a77cc86da9" + integrity sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.5" + +memoizee@~0.3.8: + version "0.3.10" + resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.3.10.tgz#4eca0d8aed39ec9d017f4c5c2f2f6432f42e5c8f" + integrity sha512-LLzVUuWwGBKK188spgOK/ukrp5zvd9JGsiLDH41pH9vt5jvhZfsu5pxDuAnYAMG8YEGce72KO07sSBy9KkvOfw== + dependencies: + d "~0.1.1" + es5-ext "~0.10.11" + es6-weak-map "~0.1.4" + event-emitter "~0.3.4" + lru-queue "0.1" + next-tick "~0.2.2" + timers-ext "0.1" + +merge-stream@^0.1.7: + version "0.1.8" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-0.1.8.tgz#48a07b3b4a121d74a3edbfdcdb4b08adbf0240b1" + integrity sha512-ivGsLZth/AkvevAzPlRLSie8Q3GdyH/5xUYgn+ItAJYslT0NsKd2cxx0bAjmqoY5swX0NoWJjvkDkfpaVZx9lw== + dependencies: + through2 "^0.6.1" + +microdiff@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/microdiff/-/microdiff-1.5.0.tgz#d16219b223396f11ffcf441da26a43d3e6bd06f8" + integrity sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" + integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== + +mimer@*: + version "2.0.2" + resolved "https://registry.yarnpkg.com/mimer/-/mimer-2.0.2.tgz#941da26070e80bb485aed8a1ef4a5a325cbbfa96" + integrity sha512-izxvjsB7Ur5HrTbPu6VKTrzxSMBFBqyZQc6dWlZNQ4/wAvf886fD4lrjtFd8IQ8/WmZKdxKjUtqFFNaj3hQ52g== + +miniflare@3.20250204.1: + version "3.20250204.1" + resolved "https://registry.yarnpkg.com/miniflare/-/miniflare-3.20250204.1.tgz#12c6d6b1ca9c3afdfdf05432652188d787edfabc" + integrity sha512-B4PQi/Ai4d0ZTWahQwsFe5WAfr1j8ISMYxJZTc56g2/btgbX+Go099LmojAZY/fMRLhIYsglcStW8SeW3f/afA== + dependencies: + "@cspotcode/source-map-support" "0.8.1" + acorn "8.14.0" + acorn-walk "8.3.2" + exit-hook "2.2.1" + glob-to-regexp "0.4.1" + stoppable "1.1.0" + undici "^5.28.4" + workerd "1.20250204.0" + ws "8.18.0" + youch "3.2.3" + zod "3.22.3" + +miniflare@4.20251008.0: + version "4.20251008.0" + resolved "https://registry.yarnpkg.com/miniflare/-/miniflare-4.20251008.0.tgz#b8cc820ea06089f7955edd56a46e1c3fedabd83d" + integrity sha512-sKCNYNzXG6l8qg0Oo7y8WcDKcpbgw0qwZsxNpdZilFTR4EavRow2TlcwuPSVN99jqAjhz0M4VXvTdSGdtJ2VfQ== + dependencies: + "@cspotcode/source-map-support" "0.8.1" + acorn "8.14.0" + acorn-walk "8.3.2" + exit-hook "2.2.1" + glob-to-regexp "0.4.1" + sharp "^0.33.5" + stoppable "1.1.0" + undici "7.14.0" + workerd "1.20251008.0" + ws "8.18.0" + youch "4.1.0-beta.10" + zod "3.22.3" + +minimatch@^2.0.1: + version "2.0.10" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7" + integrity sha512-jQo6o1qSVLEWaw3l+bwYA2X0uLuK2KjNh2wjgO7Q/9UJnXr1Q3yQKR8BI0/Bt/rPg75e6SMW4hW/6cBHVTZUjA== + dependencies: + brace-expansion "^1.0.0" + +minimatch@~0.2.11: + version "0.2.14" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.2.14.tgz#c74e780574f63c6f9a090e90efbe6ef53a6a756a" + integrity sha512-zZ+Jy8lVWlvqqeM8iZB7w7KmQkoJn8djM585z88rywrEbzoqawVa9FR5p2hwD+y74nfuKOjmNvi9gtWJNLqHvA== + dependencies: + lru-cache "2" + sigmund "~1.0.0" + +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mkdirp@^0.5.0: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +mlly@^1.7.4: + version "1.8.0" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.8.0.tgz#e074612b938af8eba1eaf43299cbc89cb72d824e" + integrity sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g== + dependencies: + acorn "^8.15.0" + pathe "^2.0.3" + pkg-types "^1.3.1" + ufo "^1.6.1" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mustache@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64" + integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== + +nanoid@^3.3.11: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + +natives@^1.1.3: + version "1.1.6" + resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.6.tgz#a603b4a498ab77173612b9ea1acdec4d980f00bb" + integrity sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA== + +next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + +next-tick@~0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-0.2.2.tgz#75da4a927ee5887e39065880065b7336413b310d" + integrity sha512-f7h4svPtl+QidoBv4taKXUjJ70G2asaZ8G28nS0OkqaalX8dwwrtWtyxEDPK62AC00ur/+/E0pUwBwY5EPn15Q== + +nth-check@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-assign@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa" + integrity sha512-CdsOUYIh5wIiozhJ3rLQgmUTgcyzFwZZrqhkKhODMoGtPKM+wt0h0CNIoauJWMsS9822EdzPsF/6mb4nLvPN5g== + +ohash@^1.1.4: + version "1.1.6" + resolved "https://registry.yarnpkg.com/ohash/-/ohash-1.1.6.tgz#9ff7b0271d7076290794537d68ec2b40a60d133e" + integrity sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg== + +ohash@^2.0.11: + version "2.0.11" + resolved "https://registry.yarnpkg.com/ohash/-/ohash-2.0.11.tgz#60b11e8cff62ca9dee88d13747a5baa145f5900b" + integrity sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ== + +once@^1.3.0, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +ordered-read-streams@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz#fd565a9af8eb4473ba69b6ed8a34352cb552f126" + integrity sha512-PMX5ehiNri4+lgk9fl09xuPeciGmyPyVUSBwwPT4C/3EHGxoVf7UdgKDE3SLBD4pUDmlzrg1L1cK5igrp+Tyuw== + +pako@^1.0.5: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + +path-to-regexp@6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz#2b6a26a337737a8e1416f9272ed0766b1c0389f4" + integrity sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ== + +path@^0.11.14: + version "0.11.14" + resolved "https://registry.yarnpkg.com/path/-/path-0.11.14.tgz#cbc7569355cb3c83afeb4ace43ecff95231e5a7d" + integrity sha512-CzEXTDgcEfa0yqMe+DJCSbEB5YCv4JZoic5xulBNFF2ifIMjNrTWbNSPNhgKfSo0MjneGIx9RLy4pCFuZPaMSQ== + +pathe@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" + integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== + +pathe@^2.0.1, pathe@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" + integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== + +pathval@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.1.tgz#8855c5a2899af072d6ac05d11e46045ad0dc605d" + integrity sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ== + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +pkg-types@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.3.1.tgz#bd7cc70881192777eef5326c19deb46e890917df" + integrity sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ== + dependencies: + confbox "^0.1.8" + mlly "^1.7.4" + pathe "^2.0.1" + +postcss@^8.4.43: + version "8.5.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== + dependencies: + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +printable-characters@^1.0.42: + version "1.0.42" + resolved "https://registry.yarnpkg.com/printable-characters/-/printable-characters-1.0.42.tgz#3f18e977a9bd8eb37fcc4ff5659d7be90868b3d8" + integrity sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== + +psl@^1.1.28: + version "1.15.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6" + integrity sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w== + dependencies: + punycode "^2.3.1" + +punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +qs@~6.5.2: + version "6.5.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" + integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== + +readable-stream@1.1: + version "1.1.13" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e" + integrity sha512-E98tWzqShvKDGpR2MbjsDkDQWLW2TfWUC15H4tNQhIJ5Lsta84l8nUGL9/ybltGwe+wZzWPpc1Kmd2wQP4bdCA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +"readable-stream@>=1.0.33-1 <1.1.0-0": + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + integrity sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@^2.0.0, readable-stream@~2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +remove-svg-properties@^0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/remove-svg-properties/-/remove-svg-properties-0.3.4.tgz#52f10f0685a596def822b5619d0b698578899574" + integrity sha512-y7neLGtA2egeW9JjmtdKJ1PXNVWsM9ZzJQWRNGQSQvmmXLKh7VS0nEVvoEqHdwLFyvY0KGO7pHxPIKjxUoJdhQ== + dependencies: + cheerio "^0.19.0" + colors "^1.1.0" + css "^2.2.0" + graceful-fs "^4.2.6" + juice "^1.0.0" + lodash "^3.5.0" + mkdirp "^0.5.0" + path "^0.11.14" + stream-consume "^0.1.0" + through2 "^0.6.3" + vinyl-fs "^1.0.0" + +rename-keys@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/rename-keys/-/rename-keys-1.2.0.tgz#be602fb0b750476b513ebe85ba4465d03254f0a3" + integrity sha512-U7XpAktpbSgHTRSNRrjKSrjYkZKuhUukfoBlXWXUExCAqhzh1TU3BDRAfJmarcl5voKS+pbKU9MvyLWKZ4UEEg== + +repeat-string@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== + +request@^2.49.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== + +right-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" + integrity sha512-yqINtL/G7vs2v+dFIZmFUDbnVyFUJFKd6gK22Kgo6R4jfJGFtisKyncWDDULgjfqf4ASQuIQyjJ7XZ+3aWpsAg== + dependencies: + align-text "^0.1.1" + +rollup-plugin-inject@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz#e4233855bfba6c0c12a312fd6649dff9a13ee9f4" + integrity sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w== + dependencies: + estree-walker "^0.6.1" + magic-string "^0.25.3" + rollup-pluginutils "^2.8.1" + +rollup-plugin-node-polyfills@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz#53092a2744837164d5b8a28812ba5f3ff61109fd" + integrity sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA== + dependencies: + rollup-plugin-inject "^3.0.0" + +rollup-pluginutils@^2.8.1: + version "2.8.2" + resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" + integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== + dependencies: + estree-walker "^0.6.1" + +rollup@^4.20.0: + version "4.52.4" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.52.4.tgz#71e64cce96a865fcbaa6bb62c6e82807f4e378a1" + integrity sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ== + dependencies: + "@types/estree" "1.0.8" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.52.4" + "@rollup/rollup-android-arm64" "4.52.4" + "@rollup/rollup-darwin-arm64" "4.52.4" + "@rollup/rollup-darwin-x64" "4.52.4" + "@rollup/rollup-freebsd-arm64" "4.52.4" + "@rollup/rollup-freebsd-x64" "4.52.4" + "@rollup/rollup-linux-arm-gnueabihf" "4.52.4" + "@rollup/rollup-linux-arm-musleabihf" "4.52.4" + "@rollup/rollup-linux-arm64-gnu" "4.52.4" + "@rollup/rollup-linux-arm64-musl" "4.52.4" + "@rollup/rollup-linux-loong64-gnu" "4.52.4" + "@rollup/rollup-linux-ppc64-gnu" "4.52.4" + "@rollup/rollup-linux-riscv64-gnu" "4.52.4" + "@rollup/rollup-linux-riscv64-musl" "4.52.4" + "@rollup/rollup-linux-s390x-gnu" "4.52.4" + "@rollup/rollup-linux-x64-gnu" "4.52.4" + "@rollup/rollup-linux-x64-musl" "4.52.4" + "@rollup/rollup-openharmony-arm64" "4.52.4" + "@rollup/rollup-win32-arm64-msvc" "4.52.4" + "@rollup/rollup-win32-ia32-msvc" "4.52.4" + "@rollup/rollup-win32-x64-gnu" "4.52.4" + "@rollup/rollup-win32-x64-msvc" "4.52.4" + fsevents "~2.3.2" + +safe-buffer@^5.0.1, safe-buffer@^5.1.2: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^7.5.1, semver@^7.6.3, semver@^7.7.2: + version "7.7.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" + integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== + +sharp@^0.33.5: + version "0.33.5" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.33.5.tgz#13e0e4130cc309d6a9497596715240b2ec0c594e" + integrity sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw== + dependencies: + color "^4.2.3" + detect-libc "^2.0.3" + semver "^7.6.3" + optionalDependencies: + "@img/sharp-darwin-arm64" "0.33.5" + "@img/sharp-darwin-x64" "0.33.5" + "@img/sharp-libvips-darwin-arm64" "1.0.4" + "@img/sharp-libvips-darwin-x64" "1.0.4" + "@img/sharp-libvips-linux-arm" "1.0.5" + "@img/sharp-libvips-linux-arm64" "1.0.4" + "@img/sharp-libvips-linux-s390x" "1.0.4" + "@img/sharp-libvips-linux-x64" "1.0.4" + "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" + "@img/sharp-libvips-linuxmusl-x64" "1.0.4" + "@img/sharp-linux-arm" "0.33.5" + "@img/sharp-linux-arm64" "0.33.5" + "@img/sharp-linux-s390x" "0.33.5" + "@img/sharp-linux-x64" "0.33.5" + "@img/sharp-linuxmusl-arm64" "0.33.5" + "@img/sharp-linuxmusl-x64" "0.33.5" + "@img/sharp-wasm32" "0.33.5" + "@img/sharp-win32-ia32" "0.33.5" + "@img/sharp-win32-x64" "0.33.5" + +sharp@^0.34.3: + version "0.34.4" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.34.4.tgz#73c2c5a425e98250b8b927e5537f711da8966e38" + integrity sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA== + dependencies: + "@img/colour" "^1.0.0" + detect-libc "^2.1.0" + semver "^7.7.2" + optionalDependencies: + "@img/sharp-darwin-arm64" "0.34.4" + "@img/sharp-darwin-x64" "0.34.4" + "@img/sharp-libvips-darwin-arm64" "1.2.3" + "@img/sharp-libvips-darwin-x64" "1.2.3" + "@img/sharp-libvips-linux-arm" "1.2.3" + "@img/sharp-libvips-linux-arm64" "1.2.3" + "@img/sharp-libvips-linux-ppc64" "1.2.3" + "@img/sharp-libvips-linux-s390x" "1.2.3" + "@img/sharp-libvips-linux-x64" "1.2.3" + "@img/sharp-libvips-linuxmusl-arm64" "1.2.3" + "@img/sharp-libvips-linuxmusl-x64" "1.2.3" + "@img/sharp-linux-arm" "0.34.4" + "@img/sharp-linux-arm64" "0.34.4" + "@img/sharp-linux-ppc64" "0.34.4" + "@img/sharp-linux-s390x" "0.34.4" + "@img/sharp-linux-x64" "0.34.4" + "@img/sharp-linuxmusl-arm64" "0.34.4" + "@img/sharp-linuxmusl-x64" "0.34.4" + "@img/sharp-wasm32" "0.34.4" + "@img/sharp-win32-arm64" "0.34.4" + "@img/sharp-win32-ia32" "0.34.4" + "@img/sharp-win32-x64" "0.34.4" + +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + +sigmund@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" + integrity sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g== + +simple-swizzle@^0.2.2: + version "0.2.4" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.4.tgz#a8d11a45a11600d6a1ecdff6363329e3648c3667" + integrity sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw== + dependencies: + is-arrayish "^0.3.1" + +slick@1.12.2: + version "1.12.2" + resolved "https://registry.yarnpkg.com/slick/-/slick-1.12.2.tgz#bd048ddb74de7d1ca6915faa4a57570b3550c2d7" + integrity sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A== + +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +source-map-resolve@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-url@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" + integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== + +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@~0.5.1: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + +sourcemap-codec@^1.4.8: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + +sshpk@^1.7.0: + version "1.18.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028" + integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + +stacktracey@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/stacktracey/-/stacktracey-2.1.8.tgz#bf9916020738ce3700d1323b32bd2c91ea71199d" + integrity sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw== + dependencies: + as-table "^1.0.36" + get-source "^2.0.12" + +std-env@^3.8.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.10.0.tgz#d810b27e3a073047b2b5e40034881f5ea6f9c83b" + integrity sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg== + +stoppable@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stoppable/-/stoppable-1.1.0.tgz#32da568e83ea488b08e4d7ea2c3bcc9d75015d5b" + integrity sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw== + +stream-consume@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.1.tgz#d3bdb598c2bd0ae82b8cac7ac50b1107a7996c48" + integrity sha512-tNa3hzgkjEP7XbCkbRXe1jpg+ievoa0O4SCFlMOYEscGSS4JJsckGL8swUyAa/ApGU3Ae4t6Honor4HhL+tRyg== + +stream-shift@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.3.tgz#85b8fab4d71010fc3ba8772e8046cc49b8a3864b" + integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ== + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-bom@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-1.0.0.tgz#85b8862f3844b5a6d5ec8467a93598173a36f794" + integrity sha512-qVAeAIjblKDp/8Cd0tJdxpe3Iq/HooI7En98alEaMbz4Wedlrcj3WI72dDQSrziRW5IQ0zeBo3JXsmS8RcS9jg== + dependencies: + first-chunk-stream "^1.0.0" + is-utf8 "^0.2.0" + +supports-color@^10.0.0: + version "10.2.2" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-10.2.2.tgz#466c2978cc5cd0052d542a0b576461c2b802ebb4" + integrity sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g== + +svgson@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/svgson/-/svgson-5.3.1.tgz#f3df0303231f2e99e4618983cfa7e8db155f79d7" + integrity sha512-qdPgvUNWb40gWktBJnbJRelWcPzkLed/ShhnRsjbayXz8OtdPOzbil9jtiZdrYvSDumAz/VNQr6JaNfPx/gvPA== + dependencies: + deep-rename-keys "^0.2.1" + xml-reader "2.4.3" + +templayed@*: + version "0.2.3" + resolved "https://registry.yarnpkg.com/templayed/-/templayed-0.2.3.tgz#4706df625bc6aecd86b7c9f6b0fb548b95cdf769" + integrity sha512-PkryVKPQ++1gp2sVUREJhGmtK2GF/DNEZ0KnGcam0dyD+XbFO+IWp9/vPIOfpLRzwW1SMrcr/5BkA0yMbNYPGw== + +through2-filter@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254" + integrity sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA== + dependencies: + through2 "~2.0.0" + xtend "~4.0.0" + +through2@^0.6.1, through2@^0.6.3: + version "0.6.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" + integrity sha512-RkK/CCESdTKQZHdmKICijdKKsCRVHs5KsLZ6pACAmF/1GPUQhonHSXWNERctxEp7RmvjdNbZTL5z9V7nSCXKcg== + dependencies: + readable-stream ">=1.0.33-1 <1.1.0-0" + xtend ">=4.0.0 <4.1.0-0" + +through2@~2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +timers-ext@0.1: + version "0.1.8" + resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.8.tgz#b4e442f10b7624a29dd2aa42c295e257150cf16c" + integrity sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww== + dependencies: + es5-ext "^0.10.64" + next-tick "^1.1.0" + +tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + +tinyexec@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" + integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== + +tinypool@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.1.1.tgz#059f2d042bd37567fbc017d3d426bdd2a2612591" + integrity sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg== + +tinyrainbow@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" + integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ== + +tinyspy@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" + integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== + +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tslib@^2.4.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== + +type@^2.7.2: + version "2.7.3" + resolved "https://registry.yarnpkg.com/type/-/type-2.7.3.tgz#436981652129285cc3ba94f392886c2637ea0486" + integrity sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ== + +ufo@^1.5.4, ufo@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.6.1.tgz#ac2db1d54614d1b22c1d603e3aef44a85d8f146b" + integrity sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA== + +uglify-js@^2.4.1: + version "2.8.29" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" + integrity sha512-qLq/4y2pjcU3vhlhseXGGJ7VbFO4pBANu0kwl8VCa9KEI0V8VfZIx2Fy3w01iSTA/pGwKZSmu/+I4etLNDdt5w== + dependencies: + source-map "~0.5.1" + yargs "~3.10.0" + optionalDependencies: + uglify-to-browserify "~1.0.0" + +uglify-to-browserify@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" + integrity sha512-vb2s1lYx2xBtUgy+ta+b2J/GLVUR+wmpINwHePmPRhOsIVCG2wDzKJ0n14GslH1BifsqVzSOwQhRaCAsZ/nI4Q== + +undici@7.14.0: + version "7.14.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-7.14.0.tgz#7e616eeb3900deb1c4dda0e51384303975eec72c" + integrity sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ== + +undici@^5.28.4: + version "5.29.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.29.0.tgz#419595449ae3f2cdcba3580a2e8903399bd1f5a3" + integrity sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg== + dependencies: + "@fastify/busboy" "^2.0.0" + +unenv@2.0.0-rc.1: + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/unenv/-/unenv-2.0.0-rc.1.tgz#7299a1ae1613d441e207ced9a5ee6f90c40856af" + integrity sha512-PU5fb40H8X149s117aB4ytbORcCvlASdtF97tfls4BPIyj4PeVxvpSuy1jAptqYHqB0vb2w2sHvzM0XWcp2OKg== + dependencies: + defu "^6.1.4" + mlly "^1.7.4" + ohash "^1.1.4" + pathe "^1.1.2" + ufo "^1.5.4" + +unenv@2.0.0-rc.21: + version "2.0.0-rc.21" + resolved "https://registry.yarnpkg.com/unenv/-/unenv-2.0.0-rc.21.tgz#cc6082ef32370eb7098cb2f86ca7af3ef4c2c49f" + integrity sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A== + dependencies: + defu "^6.1.4" + exsolve "^1.0.7" + ohash "^2.0.11" + pathe "^2.0.3" + ufo "^1.6.1" + +unique-stream@^2.0.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.4.0.tgz#5d995309556b5ba324197a3f541d675a0a052ff4" + integrity sha512-V6QarSfeSgDipGA9EZdoIzu03ZDlOFkk+FbEP5cwgrZXN3iIkYR91IjU2EnM6rB835kGQsqHX8qncObTXV+6KA== + dependencies: + json-stable-stringify-without-jsonify "^1.0.1" + through2-filter "3.0.0" + +upng-js@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/upng-js/-/upng-js-2.1.0.tgz#7176e73973db361ca95d0fa14f958385db6b9dd2" + integrity sha512-d3xzZzpMP64YkjP5pr8gNyvBt7dLk/uGI67EctzDuVp4lCZyVMo0aJO6l/VDlgbInJYDY6cnClLoBp29eKWI6g== + dependencies: + pako "^1.0.5" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== + +util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +vinyl-fs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-1.0.0.tgz#d15752e68c2dad74364e7e853473735354692edf" + integrity sha512-FETN6RUe6r2Dbrhq1GxovuyHf2G5ub6NO1HFtz6nMY21YQzTQhHPARcyzJWPUohjG2eCaUOyoTxnNyEtOtCI1Q== + dependencies: + duplexify "^3.2.0" + glob-stream "^4.0.1" + glob-watcher "^0.0.8" + graceful-fs "^3.0.0" + merge-stream "^0.1.7" + mkdirp "^0.5.0" + object-assign "^2.0.0" + strip-bom "^1.0.0" + through2 "^0.6.1" + vinyl "^0.4.0" + +vinyl@^0.4.0: + version "0.4.6" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" + integrity sha512-pmza4M5VA15HOImIQYWhoXGlGNafCm0QK5BpBUXkzzEwrRxKqBsbAhTfkT2zMcJhUX1G1Gkid0xaV8WjOl7DsA== + dependencies: + clone "^0.2.0" + clone-stats "^0.0.1" + +vite-node@2.1.9: + version "2.1.9" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.9.tgz#549710f76a643f1c39ef34bdb5493a944e4f895f" + integrity sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA== + dependencies: + cac "^6.7.14" + debug "^4.3.7" + es-module-lexer "^1.5.4" + pathe "^1.1.2" + vite "^5.0.0" + +vite@^5.0.0: + version "5.4.20" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.20.tgz#3267a5e03f21212f44edfd72758138e8fcecd76a" + integrity sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g== + dependencies: + esbuild "^0.21.3" + postcss "^8.4.43" + rollup "^4.20.0" + optionalDependencies: + fsevents "~2.3.3" + +vitest@~2.1.9: + version "2.1.9" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.9.tgz#7d01ffd07a553a51c87170b5e80fea3da7fb41e7" + integrity sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q== + 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" + +web-resource-inliner@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/web-resource-inliner/-/web-resource-inliner-1.2.1.tgz#c02332ad985ed00da4c2310c0699fb2e5ba9675f" + integrity sha512-0532DtlDjxRLTjJebh9o41hoPATspwJOWSN3/eSgtRZVVeU/6NiEo+oMrtcivAYhVGFvVChOy6dniRIkc5IeaQ== + dependencies: + async "^0.9.0" + clean-css "1.1.7" + cli-color "^0.3.2" + datauri "~0.2.0" + lodash "^3.10.1" + request "^2.49.0" + uglify-js "^2.4.1" + xtend "^4.0.0" + +which@^1.2.8: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + +window-size@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" + integrity sha512-1pTPQDKTdd61ozlKGNCjhNRd+KPmgLSGa3mZTHoOliaGcESD8G1PXhh7c1fgiPjVbNVfgy2Faw4BI8/m0cC8Mg== + +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + integrity sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q== + +workerd@1.20250204.0: + version "1.20250204.0" + resolved "https://registry.yarnpkg.com/workerd/-/workerd-1.20250204.0.tgz#fffc680dbc5589cba6dba33877d68f00a226e779" + integrity sha512-zcKufjVFsQMiD3/acg1Ix00HIMCkXCrDxQXYRDn/1AIz3QQGkmbVDwcUk1Ki2jBUoXmBCMsJdycRucgMVEypWg== + optionalDependencies: + "@cloudflare/workerd-darwin-64" "1.20250204.0" + "@cloudflare/workerd-darwin-arm64" "1.20250204.0" + "@cloudflare/workerd-linux-64" "1.20250204.0" + "@cloudflare/workerd-linux-arm64" "1.20250204.0" + "@cloudflare/workerd-windows-64" "1.20250204.0" + +workerd@1.20251008.0: + version "1.20251008.0" + resolved "https://registry.yarnpkg.com/workerd/-/workerd-1.20251008.0.tgz#622d61520fa95b3a98d96eed63ec0f701641ad6c" + integrity sha512-HwaJmXO3M1r4S8x2ea2vy8Rw/y/38HRQuK/gNDRQ7w9cJXn6xSl1sIIqKCffULSUjul3wV3I3Nd/GfbmsRReEA== + optionalDependencies: + "@cloudflare/workerd-darwin-64" "1.20251008.0" + "@cloudflare/workerd-darwin-arm64" "1.20251008.0" + "@cloudflare/workerd-linux-64" "1.20251008.0" + "@cloudflare/workerd-linux-arm64" "1.20251008.0" + "@cloudflare/workerd-windows-64" "1.20251008.0" + +wrangler@3.109.1: + version "3.109.1" + resolved "https://registry.yarnpkg.com/wrangler/-/wrangler-3.109.1.tgz#92191865bc4e593540e412434f1e0b7b880c460a" + integrity sha512-1Jx+nZ6eCXPQ2rsGdrV6Qy/LGvhpqudeuTl4AYHl9P8Zugp44Uzxnj5w11qF4v/rv1dOZoA5TydSt9xMFfhpKg== + dependencies: + "@cloudflare/kv-asset-handler" "0.3.4" + "@esbuild-plugins/node-globals-polyfill" "0.2.3" + "@esbuild-plugins/node-modules-polyfill" "0.2.2" + blake3-wasm "2.1.5" + esbuild "0.17.19" + miniflare "3.20250204.1" + path-to-regexp "6.3.0" + unenv "2.0.0-rc.1" + workerd "1.20250204.0" + optionalDependencies: + fsevents "~2.3.2" + sharp "^0.33.5" + +wrangler@^4.38.0: + version "4.43.0" + resolved "https://registry.yarnpkg.com/wrangler/-/wrangler-4.43.0.tgz#cadd8703fef40f71118f0646c7d72ee2531dc2ef" + integrity sha512-IBNqXlYHSUSCNNWj/tQN4hFiQy94l7fTxEnJWETXyW69+cjUyjQ7MfeoId3vIV9KBgY8y5M5uf2XulU95OikJg== + dependencies: + "@cloudflare/kv-asset-handler" "0.4.0" + "@cloudflare/unenv-preset" "2.7.7" + blake3-wasm "2.1.5" + esbuild "0.25.4" + miniflare "4.20251008.0" + path-to-regexp "6.3.0" + unenv "2.0.0-rc.21" + workerd "1.20251008.0" + optionalDependencies: + fsevents "~2.3.2" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + +xml-lexer@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/xml-lexer/-/xml-lexer-0.2.2.tgz#518193a4aa334d58fc7d248b549079b89907e046" + integrity sha512-G0i98epIwiUEiKmMcavmVdhtymW+pCAohMRgybyIME9ygfVu8QheIi+YoQh3ngiThsT0SQzJT4R0sKDEv8Ou0w== + dependencies: + eventemitter3 "^2.0.0" + +xml-reader@2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/xml-reader/-/xml-reader-2.4.3.tgz#9f810caf7c425a5aafb848b1c45103c9e71d7530" + integrity sha512-xWldrIxjeAMAu6+HSf9t50ot1uL5M+BtOidRCWHXIeewvSeIpscWCsp4Zxjk8kHHhdqFBrfK8U0EJeCcnyQ/gA== + dependencies: + eventemitter3 "^2.0.0" + xml-lexer "^0.2.2" + +"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== + +yargs@~3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" + integrity sha512-QFzUah88GAGy9lyDKGBqZdkYApt63rCXYBGYnEP4xDJPXNqXXnBDACnbrXnViV6jRSqAePwrATi2i8mfYm4L1A== + dependencies: + camelcase "^1.0.2" + cliui "^2.1.0" + decamelize "^1.0.0" + window-size "0.1.0" + +youch-core@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/youch-core/-/youch-core-0.3.3.tgz#c5d3d85aeea0d8bc7b36e9764ed3f14b7ceddc7d" + integrity sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA== + dependencies: + "@poppinss/exception" "^1.2.2" + error-stack-parser-es "^1.0.5" + +youch@3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/youch/-/youch-3.2.3.tgz#63c94ea504950a1a5bf1d5969439addba6c726e2" + integrity sha512-ZBcWz/uzZaQVdCvfV4uk616Bbpf2ee+F/AvuKDR5EwX/Y4v06xWdtMluqTD7+KlZdM93lLm9gMZYo0sKBS0pgw== + dependencies: + cookie "^0.5.0" + mustache "^4.2.0" + stacktracey "^2.1.8" + +youch@4.1.0-beta.10: + version "4.1.0-beta.10" + resolved "https://registry.yarnpkg.com/youch/-/youch-4.1.0-beta.10.tgz#94702059e0ba7668025f5cd1b5e5c0f3eb0e83c2" + integrity sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ== + dependencies: + "@poppinss/colors" "^4.1.5" + "@poppinss/dumper" "^0.6.4" + "@speed-highlight/core" "^1.2.7" + cookie "^1.0.2" + youch-core "^0.3.3" + +zod@3.22.3: + version "3.22.3" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.3.tgz#2fbc96118b174290d94e8896371c95629e87a060" + integrity sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug== + +zod@^3.22.3: + version "3.25.76" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" + integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==