New auto opengraph

This commit is contained in:
Adam Matthiesen 2024-01-30 04:15:05 -08:00
parent 6c43b4f81d
commit a176f11e55
14 changed files with 703 additions and 16 deletions

View File

@ -1,7 +1,7 @@
{
"name": "@matthiesenxyz/astro-ghostcms-theme-default",
"description": "Default Theme for astro-ghostcms",
"version": "0.1.6",
"version": "0.1.7",
"homepage": "https://astro-ghostcms.xyz/",
"type": "module",
"license": "MIT",
@ -50,7 +50,7 @@
"astro": "^4.2.1"
},
"dependencies": {
"@matthiesenxyz/astro-ghostcms": "^3.1.5",
"@matthiesenxyz/astro-ghostcms": "^3.1.6",
"astro-font": "^0.0.77",
"sass": "^1.70.0"
}

View File

@ -15,6 +15,17 @@ export type Props = {
permalink?: string;
};
export const getOgImagePath = (filename: string = "index") => {
if (filename.startsWith("/")) filename = filename.substring(1);
if (filename.endsWith("/"))
filename = filename.substring(0, filename.length - 1);
if (filename === "") filename = "index";
return `./open-graph/${filename}.png`;
};
const ogImage = new URL(getOgImagePath(Astro.url.pathname), Astro.url.origin)
.href;
const { content, permalink, image, settings, bodyClass = "" } = Astro.props as Props;
const ghostAccentColor = settings.accent_color;
---
@ -58,14 +69,14 @@ const ghostAccentColor = settings.accent_color;
<meta property="og:title" content={content?.title} />
{permalink && <meta property="og:url" content={permalink} />}
{content?.description && <meta property="og:description" content={content.description} />}
{image && <meta property="og:image" content={image} />}
<meta property="og:image" content={ogImage} />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:title" content={content?.title} />
{permalink && <meta property="twitter:url" content={permalink} />}
{content?.description && <meta property="twitter:description" content={content.description} />}
{image && <meta property="twitter:image" content={image} />}
<meta property="twitter:image" content={ogImage} />
<!-- Link to the global style, or the file that imports constructs -->
<link

View File

@ -26,6 +26,7 @@ const bodyClass = `post-template ${postClass}`;
content={{ title: post.title, description: post.excerpt }}
settings={settings}
bodyClass={bodyClass}
image={post.feature_image?post.feature_image:undefined}
>
{
post.primary_author ? (

View File

@ -8,6 +8,8 @@ import { loadEnv } from 'vite';
import { fromZodError } from "zod-validation-error";
import { addVirtualImport } from "./src/utils/add-virtual-import";
export * from "./types.js"
/** INTERNAL CONSTANTS */
const IC = {
/** INTERNAL PACKAGE NAME */
@ -139,6 +141,31 @@ export default function GhostCMS(options: UserConfig): AstroIntegration {
});
} else { if( !GCD.dCO ) { logger.info(IC.idRSS)}}
injectRoute({
pattern: '/open-graph/[slug].png',
entrypoint: `${IC.PKG}/open-graph/[slug].png.ts`
});
injectRoute({
pattern: '/open-graph/index.png',
entrypoint: `${IC.PKG}/open-graph/index.png.ts`
});
injectRoute({
pattern: '/open-graph/authors.png',
entrypoint: `${IC.PKG}/open-graph/authors.png.ts`
});
injectRoute({
pattern: '/open-graph/author/[slug].png',
entrypoint: `${IC.PKG}/open-graph/author/[slug].png.ts`
});
injectRoute({
pattern: '/open-graph/tags.png',
entrypoint: `${IC.PKG}/open-graph/tags.png.ts`
});
injectRoute({
pattern: '/open-graph/tag/[slug].png',
entrypoint: `${IC.PKG}/open-graph/tag/[slug].png.ts`
});
// THEME ROUTES
if( !GCD.dCO ) { logger.info( IC.ITR )}
@ -203,7 +230,15 @@ export default function GhostCMS(options: UserConfig): AstroIntegration {
// FINAL STEP TO KEEP INTEGRATION LIVE
try { updateConfig( {
// UPDATE ASTRO CONFIG WITH INTEGRATED INTEGRATIONS
integrations: [ ghostSitemap( GCD.SM ), ghostRobots( GCD.RTXT ) ],
integrations: [
ghostSitemap(GCD.SM),
ghostRobots(GCD.RTXT)
],
vite: {
optimizeDeps: {
exclude: ["@resvg/resvg-js"],
}
}
// LOAD VITE AND SETUP viteGhostCMS Configs
}) } catch ( e ) {
logger.error( e as string );

View File

@ -1,7 +1,7 @@
{
"name": "@matthiesenxyz/astro-ghostcms",
"description": "Astro GhostCMS integration to allow easier importing of GhostCMS Content",
"version": "3.1.5",
"version": "3.1.6",
"homepage": "https://astro-ghostcms.xyz/",
"type": "module",
"license": "MIT",
@ -53,7 +53,13 @@
"./404.astro": "./src/default-routes/404/404.astro",
"./rss.xml.ts": "./src/default-routes/rss.xml.ts",
"./config": "./src/integrations/virtual-config.ts",
"./types": "./types.ts"
"./types": "./types.ts",
"./open-graph/[slug].png.ts": "./src/default-routes/open-graph/[slug].png.ts",
"./open-graph/index.png.ts": "./src/default-routes/open-graph/index.png.ts",
"./open-graph/authors.png.ts": "./src/default-routes/open-graph/authors.png.ts",
"./open-graph/author/[slug].png.ts": "./src/default-routes/open-graph/author/[slug].png.ts",
"./open-graph/tags.png.ts": "./src/default-routes/open-graph/tags.png.ts",
"./open-graph/tag/[slug].png.ts": "./src/default-routes/open-graph/tag/[slug].png.ts"
},
"scripts": {
"test": "vitest run",
@ -62,7 +68,7 @@
"test:ci": "vitest run --coverage.enabled --coverage.reporter='text-summary'"
},
"peerDependencies": {
"astro": "^4.2.3"
"astro": "^4.2.6"
},
"devDependencies": {
"@astrojs/check": "^0.4.1",
@ -82,11 +88,14 @@
"vitest-fetch-mock": "^0.2.2"
},
"dependencies": {
"@matthiesenxyz/astro-ghostcms-theme-default": "^0.1.3",
"@matthiesenxyz/astro-ghostcms-theme-default": "^0.1.7",
"@astrojs/rss": "^4.0.4",
"@astrojs/sitemap": "^3.0.5",
"@resvg/resvg-js": "^2.6.0",
"@ts-ghost/core-api": "^5.1.2",
"astro-robots-txt": "^1.0.0",
"satori": "^0.10.11",
"satori-html": "^0.3.2",
"vite": "^5.0.12",
"vite-tsconfig-paths": "^4.2.2",
"zod": "^3.22.4",

View File

@ -0,0 +1,49 @@
import type { APIRoute, GetStaticPaths, GetStaticPathsItem, InferGetStaticPropsType } from "astro";
import { satoriOG } from "../../integrations/satori.js";
import { html } from "satori-html";
import { invariant, getAllPosts, getAllPages, getSettings } from "../../api/index.js";
export const getStaticPaths: GetStaticPaths = async () => {
const result: GetStaticPathsItem[] = [];
const [posts, pages, settings] = await Promise.all([getAllPosts(), await getAllPages(), await getSettings()]);
const allPosts = [...posts, ...pages];
invariant(settings, "Settings are required");
allPosts.map(allPosts => {
result.push({
params: {slug: allPosts.slug},
props: {
title: allPosts.title,
image: allPosts.feature_image
}
})
})
return result
}
export type Props = InferGetStaticPropsType<typeof getStaticPaths>;
export const GET: APIRoute = async ({ props, site}) => {
const settings = await getSettings();
invariant(settings, "Settings are required");
const fontFile = await fetch(
"https://og-playground.vercel.app/inter-latin-ext-700-normal.woff",
);
const fontData: ArrayBuffer = await fontFile.arrayBuffer();
return await satoriOG({
template: html`<div style="display: flex; height: 100%; width: 100%; alignItems: center; justifyContent: center; letterSpacing: -.02em; fontWeight: 700; background: white;"> <div style="left: 24; top: 24; position: absolute; display: flex; alignItems: center;"> <img src=${settings.icon} width="82"/> <span style="marginLeft: 8; fontSize: 48;">${settings.title} - ${props.title}</span> </div> <div style=" display: flex; flexWrap: wrap; justifyContent: center; padding: 20px 50px; margin: 0 42px; fontSize: 40; width: 1700; height: 850; textAlign: center; backgroundColor: black; color: white; lineHeight: 1.4;"> <img src=${props.image?props.image:settings.twitter_image} width="100%" height="100%"/> </div> <div style="left: 24; bottom: 24; position: absolute; display: flex; alignItems: center;"> <span style="marginLeft: 8; fontSize: 48;">${site}</span> </div> </div>`,
width: 1920,
height: 1080,
}).toResponse({
satori: {
fonts: [
{
name: "Inter Latin",
data: fontData,
style: "normal",
},
],
},
});
};

View File

@ -0,0 +1,55 @@
import type { APIRoute, GetStaticPaths, GetStaticPathsItem, InferGetStaticParamsType, InferGetStaticPropsType } from "astro";
import { satoriOG } from "../../../integrations/satori.js";
import { html } from "satori-html";
import { invariant, getAllPosts, getSettings, getAllAuthors } from "../../../api/index.js";
export const getStaticPaths: GetStaticPaths = async () => {
const result: GetStaticPathsItem[] = [];
const posts = await getAllPosts();
const { authors } = await getAllAuthors();
invariant(authors, "Settings are required");
const settings = await getSettings();
invariant(settings, "Settings are required");
authors.map((author) => {
const filteredPosts = posts.filter((post) =>
post.authors?.map((author) => author.slug).includes(author.slug)
);
result.push( {
params: { slug: author.slug },
props: {
posts: filteredPosts,
settings,
author,
},
});
});
return result;
}
export type Props = InferGetStaticPropsType<typeof getStaticPaths>;
export const GET: APIRoute = async ({ props, site }) => {
const settings = await getSettings();
invariant(settings, "Settings are required");
const fontFile = await fetch(
"https://og-playground.vercel.app/inter-latin-ext-700-normal.woff",
);
const fontData: ArrayBuffer = await fontFile.arrayBuffer();
return await satoriOG({
template: html`<div style="display: flex; height: 100%; width: 100%; alignItems: center; justifyContent: center; letterSpacing: -.02em; fontWeight: 700; background: white;"> <div style="left: 24; top: 24; position: absolute; display: flex; alignItems: center;"> <img src=${settings.icon} width="82"/> <span style="marginLeft: 8; fontSize: 48;">${settings.title} - ${props.author.name}</span> </div> <div style=" display: flex; flexWrap: wrap; justifyContent: center; padding: 20px 50px; margin: 0 42px; fontSize: 40; width: 1700; height: 850; textAlign: center; backgroundColor: black; color: white; lineHeight: 1.4;"> <img src=${props.author.profile_image} width="800" height="800"/> </div> <div style="left: 24; bottom: 24; position: absolute; display: flex; alignItems: center;"> <span style="marginLeft: 8; fontSize: 48;">${site}</span> </div> </div>`,
width: 1920,
height: 1080,
}).toResponse({
satori: {
fonts: [
{
name: "Inter Latin",
data: fontData,
style: "normal",
},
],
},
});
};

View File

@ -0,0 +1,49 @@
import type { APIRoute, GetStaticPaths, GetStaticPathsItem, InferGetStaticPropsType } from "astro";
import { satoriOG } from "../../integrations/satori.js";
import { html } from "satori-html";
import { invariant, getAllPosts, getAllPages, getSettings } from "../../api/index.js";
export const getStaticPaths: GetStaticPaths = async () => {
const result: GetStaticPathsItem[] = [];
const [posts, pages, settings] = await Promise.all([getAllPosts(), await getAllPages(), await getSettings()]);
const allPosts = [...posts, ...pages];
invariant(settings, "Settings are required");
allPosts.map(allPosts => {
result.push({
params: {slug: allPosts.slug},
props: {
title: allPosts.title,
image: allPosts.feature_image
}
})
})
return result
}
export type Props = InferGetStaticPropsType<typeof getStaticPaths>;
export const GET: APIRoute = async ({ props, site}) => {
const settings = await getSettings();
invariant(settings, "Settings are required");
const fontFile = await fetch(
"https://og-playground.vercel.app/inter-latin-ext-700-normal.woff",
);
const fontData: ArrayBuffer = await fontFile.arrayBuffer();
return await satoriOG({
template: html`<div style="display: flex; height: 100%; width: 100%; alignItems: center; justifyContent: center; letterSpacing: -.02em; fontWeight: 700; background: white;"> <div style="left: 24; top: 24; position: absolute; display: flex; alignItems: center;"> <img src=${settings.icon} width="82"/> <span style="marginLeft: 8; fontSize: 48;">${settings.title} - Authors</span> </div> <div style=" display: flex; flexWrap: wrap; justifyContent: center; padding: 20px 50px; margin: 0 42px; fontSize: 40; width: 1700; height: 850; textAlign: center; backgroundColor: black; color: white; lineHeight: 1.4;"> <img src=${settings.cover_image?settings.cover_image:settings.twitter_image} width="100%" height="100%"/> </div> <div style="left: 24; bottom: 24; position: absolute; display: flex; alignItems: center;"> <span style="marginLeft: 8; fontSize: 48;">${site}</span> </div> </div>`,
width: 1920,
height: 1080,
}).toResponse({
satori: {
fonts: [
{
name: "Inter Latin",
data: fontData,
style: "normal",
},
],
},
});
};

View File

@ -0,0 +1,49 @@
import type { APIRoute, GetStaticPaths, GetStaticPathsItem, InferGetStaticPropsType } from "astro";
import { satoriOG } from "../../integrations/satori.js";
import { html } from "satori-html";
import { invariant, getAllPosts, getAllPages, getSettings } from "../../api/index.js";
export const getStaticPaths: GetStaticPaths = async () => {
const result: GetStaticPathsItem[] = [];
const [posts, pages, settings] = await Promise.all([getAllPosts(), await getAllPages(), await getSettings()]);
const allPosts = [...posts, ...pages];
invariant(settings, "Settings are required");
allPosts.map(allPosts => {
result.push({
params: {slug: allPosts.slug},
props: {
title: allPosts.title,
image: allPosts.feature_image
}
})
})
return result
}
export type Props = InferGetStaticPropsType<typeof getStaticPaths>;
export const GET: APIRoute = async ({ props, site}) => {
const settings = await getSettings();
invariant(settings, "Settings are required");
const fontFile = await fetch(
"https://og-playground.vercel.app/inter-latin-ext-700-normal.woff",
);
const fontData: ArrayBuffer = await fontFile.arrayBuffer();
return await satoriOG({
template: html`<div style="display: flex; height: 100%; width: 100%; alignItems: center; justifyContent: center; letterSpacing: -.02em; fontWeight: 700; background: white;"> <div style="left: 24; top: 24; position: absolute; display: flex; alignItems: center;"> <img src=${settings.icon} width="82"/> <span style="marginLeft: 8; fontSize: 48;">${settings.title} - Index</span> </div> <div style=" display: flex; flexWrap: wrap; justifyContent: center; padding: 20px 50px; margin: 0 42px; fontSize: 40; width: 1700; height: 850; textAlign: center; backgroundColor: black; color: white; lineHeight: 1.4;"> <img src=${settings.cover_image?settings.cover_image:settings.twitter_image} width="100%" height="100%"/> </div> <div style="left: 24; bottom: 24; position: absolute; display: flex; alignItems: center;"> <span style="marginLeft: 8; fontSize: 48;">${site}</span> </div> </div>`,
width: 1920,
height: 1080,
}).toResponse({
satori: {
fonts: [
{
name: "Inter Latin",
data: fontData,
style: "normal",
},
],
},
});
};

View File

@ -0,0 +1,54 @@
import type { APIRoute, GetStaticPaths, GetStaticPathsItem, InferGetStaticPropsType } from "astro";
import { satoriOG } from "../../../integrations/satori.js";
import { html } from "satori-html";
import { invariant, getAllPosts, getSettings, getAllTags } from "../../../api/index.js";
export const getStaticPaths: GetStaticPaths = async () => {
const result: GetStaticPathsItem[] = [];
const posts = await getAllPosts();
const { tags } = await getAllTags();
const settings = await getSettings();
invariant(settings, "Settings are required");
tags.map((tag) => {
const filteredPosts = posts.filter((post) =>
post.tags?.map((tag) => tag.slug).includes(tag.slug)
);
result.push( {
params: { slug: tag.slug },
props: {
posts: filteredPosts,
settings,
tag,
},
});
});
return result;
}
export type Props = InferGetStaticPropsType<typeof getStaticPaths>;
export const GET: APIRoute = async ({ props, site}) => {
const settings = await getSettings();
invariant(settings, "Settings are required");
const fontFile = await fetch(
"https://og-playground.vercel.app/inter-latin-ext-700-normal.woff",
);
const fontData: ArrayBuffer = await fontFile.arrayBuffer();
return await satoriOG({
template: html`<div style="display: flex; height: 100%; width: 100%; alignItems: center; justifyContent: center; letterSpacing: -.02em; fontWeight: 700; background: white;"> <div style="left: 24; top: 24; position: absolute; display: flex; alignItems: center;"> <img src=${settings.icon} width="82"/> <span style="marginLeft: 8; fontSize: 48;">${settings.title} - Tag: ${props.tag.name}</span> </div> <div style=" display: flex; flexWrap: wrap; justifyContent: center; padding: 20px 50px; margin: 0 42px; fontSize: 40; width: 1700; height: 850; textAlign: center; backgroundColor: black; color: white; lineHeight: 1.4;"> <img src=${settings.cover_image?settings.cover_image:settings.twitter_image} width="100%" height="100%"/> </div> <div style="left: 24; bottom: 24; position: absolute; display: flex; alignItems: center;"> <span style="marginLeft: 8; fontSize: 48;">${site}</span> </div> </div>`,
width: 1920,
height: 1080,
}).toResponse({
satori: {
fonts: [
{
name: "Inter Latin",
data: fontData,
style: "normal",
},
],
},
});
};

View File

@ -0,0 +1,49 @@
import type { APIRoute, GetStaticPaths, GetStaticPathsItem, InferGetStaticPropsType } from "astro";
import { satoriOG } from "../../integrations/satori.js";
import { html } from "satori-html";
import { invariant, getAllPosts, getAllPages, getSettings } from "../../api/index.js";
export const getStaticPaths: GetStaticPaths = async () => {
const result: GetStaticPathsItem[] = [];
const [posts, pages, settings] = await Promise.all([getAllPosts(), await getAllPages(), await getSettings()]);
const allPosts = [...posts, ...pages];
invariant(settings, "Settings are required");
allPosts.map(allPosts => {
result.push({
params: {slug: allPosts.slug},
props: {
title: allPosts.title,
image: allPosts.feature_image
}
})
})
return result
}
export type Props = InferGetStaticPropsType<typeof getStaticPaths>;
export const GET: APIRoute = async ({ props, site}) => {
const settings = await getSettings();
invariant(settings, "Settings are required");
const fontFile = await fetch(
"https://og-playground.vercel.app/inter-latin-ext-700-normal.woff",
);
const fontData: ArrayBuffer = await fontFile.arrayBuffer();
return await satoriOG({
template: html`<div style="display: flex; height: 100%; width: 100%; alignItems: center; justifyContent: center; letterSpacing: -.02em; fontWeight: 700; background: white;"> <div style="left: 24; top: 24; position: absolute; display: flex; alignItems: center;"> <img src=${settings.icon} width="82"/> <span style="marginLeft: 8; fontSize: 48;">${settings.title} - Tags</span> </div> <div style=" display: flex; flexWrap: wrap; justifyContent: center; padding: 20px 50px; margin: 0 42px; fontSize: 40; width: 1700; height: 850; textAlign: center; backgroundColor: black; color: white; lineHeight: 1.4;"> <img src=${settings.cover_image?settings.cover_image:settings.twitter_image} width="100%" height="100%"/> </div> <div style="left: 24; bottom: 24; position: absolute; display: flex; alignItems: center;"> <span style="marginLeft: 8; fontSize: 48;">${site}</span> </div> </div>`,
width: 1920,
height: 1080,
}).toResponse({
satori: {
fonts: [
{
name: "Inter Latin",
data: fontData,
style: "normal",
},
],
},
});
};

View File

@ -0,0 +1,49 @@
import { Resvg } from "@resvg/resvg-js";
import satori from "satori";
import type {
SatoriAstroOGOptions,
ToSvgOptions,
ToImageOptions,
ToResponseOptions,
} from "../../types.js";
export const satoriOG = ({
width,
height,
template,
}: SatoriAstroOGOptions) => {
return {
async toSvg(options: ToSvgOptions) {
return await satori(template, { width, height, ...options });
},
async toImage({
satori: satoriOptions,
resvg: _resvgOptions,
}: ToImageOptions) {
const resvgOptions =
typeof _resvgOptions === "function"
? _resvgOptions({ width, height })
: _resvgOptions;
return new Resvg(await this.toSvg(satoriOptions), {
fitTo: { mode: "width", value: width },
...resvgOptions,
})
.render()
.asPng();
},
async toResponse({ response: init, ...rest }: ToResponseOptions) {
const image = await this.toImage(rest);
return new Response(image, {
...init,
headers: {
"Content-Type": "image/png",
"Content-Length": image.length.toString(),
"Cache-Control": "public, max-age=31536000, immutable",
...init?.headers,
},
});
},
};
};

View File

@ -1,3 +1,6 @@
import type { Resvg } from "@resvg/resvg-js";
import type satori from "satori";
export type { UserConfig } from './src/schemas';
export type {
@ -7,4 +10,23 @@ export type {
} from './src/api';
export type { ContentAPICredentials, APIVersions } from "@ts-ghost/core-api";
export type { ContentAPICredentials, APIVersions } from "@ts-ghost/core-api";
type SatoriParameters = Parameters<typeof satori>;
type SatoriOptions = SatoriParameters[1];
type ResvgOptions = NonNullable<ConstructorParameters<typeof Resvg>[1]>;
export type SatoriAstroOGOptions = {
template: SatoriParameters[0];
width: number;
height: number;
};
export type ToSvgOptions = Omit<SatoriOptions, "width" | "height">;
export type ToImageOptions = {
satori: ToSvgOptions;
resvg?:
| ResvgOptions
| ((params: { width: number; height: number }) => ResvgOptions);
};
export type ToResponseOptions = ToImageOptions & { response?: ResponseInit };

View File

@ -27,7 +27,7 @@ importers:
specifier: 3.1.5
version: link:../packages/astro-ghostcms
'@matthiesenxyz/astro-ghostcms-theme-default':
specifier: 0.1.5
specifier: 0.1.6
version: link:../packages/astro-ghostcms-theme-default
astro:
specifier: ^4.2.6
@ -51,15 +51,24 @@ importers:
'@matthiesenxyz/astro-ghostcms-theme-default':
specifier: ^0.1.3
version: link:../astro-ghostcms-theme-default
'@resvg/resvg-js':
specifier: ^2.6.0
version: 2.6.0
'@ts-ghost/core-api':
specifier: ^5.1.2
version: 5.1.2
astro:
specifier: ^4.2.3
version: 4.2.4(@types/node@20.11.10)(typescript@5.3.3)
specifier: ^4.2.6
version: 4.2.6(@types/node@20.11.10)(typescript@5.3.3)
astro-robots-txt:
specifier: ^1.0.0
version: 1.0.0
satori:
specifier: ^0.10.11
version: 0.10.13
satori-html:
specifier: ^0.3.2
version: 0.3.2
vite:
specifier: ^5.0.12
version: 5.0.12(@types/node@20.11.10)
@ -1592,6 +1601,132 @@ packages:
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
dev: true
/@resvg/resvg-js-android-arm-eabi@2.6.0:
resolution: {integrity: sha512-lJnZ/2P5aMocrFMW7HWhVne5gH82I8xH6zsfH75MYr4+/JOaVcGCTEQ06XFohGMdYRP3v05SSPLPvTM/RHjxfA==}
engines: {node: '>= 10'}
cpu: [arm]
os: [android]
requiresBuild: true
dev: false
optional: true
/@resvg/resvg-js-android-arm64@2.6.0:
resolution: {integrity: sha512-N527f529bjMwYWShZYfBD60dXA4Fux+D695QsHQ93BDYZSHUoOh1CUGUyICevnTxs7VgEl98XpArmUWBZQVMfQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
requiresBuild: true
dev: false
optional: true
/@resvg/resvg-js-darwin-arm64@2.6.0:
resolution: {integrity: sha512-MabUKLVayEwlPo0mIqAmMt+qESN8LltCvv5+GLgVga1avpUrkxj/fkU1TKm8kQegutUjbP/B0QuMuUr0uhF8ew==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: false
optional: true
/@resvg/resvg-js-darwin-x64@2.6.0:
resolution: {integrity: sha512-zrFetdnSw/suXjmyxSjfDV7i61hahv6DDG6kM7BYN2yJ3Es5+BZtqYZTcIWogPJedYKmzN1YTMWGd/3f0ubFiA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: false
optional: true
/@resvg/resvg-js-linux-arm-gnueabihf@2.6.0:
resolution: {integrity: sha512-sH4gxXt7v7dGwjGyzLwn7SFGvwZG6DQqLaZ11MmzbCwd9Zosy1TnmrMJfn6TJ7RHezmQMgBPi18bl55FZ1AT4A==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@resvg/resvg-js-linux-arm64-gnu@2.6.0:
resolution: {integrity: sha512-fCyMncqCJtrlANADIduYF4IfnWQ295UKib7DAxFXQhBsM9PLDTpizr0qemZcCNadcwSVHnAIzL4tliZhCM8P6A==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@resvg/resvg-js-linux-arm64-musl@2.6.0:
resolution: {integrity: sha512-ouLjTgBQHQyxLht4FdMPTvuY8xzJigM9EM2Tlu0llWkN1mKyTQrvYWi6TA6XnKdzDJHy7ZLpWpjZi7F5+Pg+Vg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@resvg/resvg-js-linux-x64-gnu@2.6.0:
resolution: {integrity: sha512-n3zC8DWsvxC1AwxpKFclIPapDFibs5XdIRoV/mcIlxlh0vseW1F49b97F33BtJQRmlntsqqN6GMMqx8byB7B+Q==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@resvg/resvg-js-linux-x64-musl@2.6.0:
resolution: {integrity: sha512-n4tasK1HOlAxdTEROgYA1aCfsEKk0UOFDNd/AQTTZlTmCbHKXPq+O8npaaKlwXquxlVK8vrkcWbksbiGqbCAcw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@resvg/resvg-js-win32-arm64-msvc@2.6.0:
resolution: {integrity: sha512-X2+EoBJFwDI5LDVb51Sk7ldnVLitMGr9WwU/i21i3fAeAXZb3hM16k67DeTy16OYkT2dk/RfU1tP1wG+rWbz2Q==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@resvg/resvg-js-win32-ia32-msvc@2.6.0:
resolution: {integrity: sha512-L7oevWjQoUgK5W1fCKn0euSVemhDXVhrjtwqpc7MwBKKimYeiOshO1Li1pa8bBt5PESahenhWgdB6lav9O0fEg==}
engines: {node: '>= 10'}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@resvg/resvg-js-win32-x64-msvc@2.6.0:
resolution: {integrity: sha512-8lJlghb+Unki5AyKgsnFbRJwkEj9r1NpwyuBG8yEJiG1W9eEGl03R3I7bsVa3haof/3J1NlWf0rzSa1G++A2iw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@resvg/resvg-js@2.6.0:
resolution: {integrity: sha512-Tf3YpbBKcQn991KKcw/vg7vZf98v01seSv6CVxZBbRkL/xyjnoYB6KgrFL6zskT1A4dWC/vg77KyNOW+ePaNlA==}
engines: {node: '>= 10'}
optionalDependencies:
'@resvg/resvg-js-android-arm-eabi': 2.6.0
'@resvg/resvg-js-android-arm64': 2.6.0
'@resvg/resvg-js-darwin-arm64': 2.6.0
'@resvg/resvg-js-darwin-x64': 2.6.0
'@resvg/resvg-js-linux-arm-gnueabihf': 2.6.0
'@resvg/resvg-js-linux-arm64-gnu': 2.6.0
'@resvg/resvg-js-linux-arm64-musl': 2.6.0
'@resvg/resvg-js-linux-x64-gnu': 2.6.0
'@resvg/resvg-js-linux-x64-musl': 2.6.0
'@resvg/resvg-js-win32-arm64-msvc': 2.6.0
'@resvg/resvg-js-win32-ia32-msvc': 2.6.0
'@resvg/resvg-js-win32-x64-msvc': 2.6.0
dev: false
/@rollup/rollup-android-arm-eabi@4.9.6:
resolution: {integrity: sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==}
cpu: [arm]
@ -1683,6 +1818,15 @@ packages:
requiresBuild: true
optional: true
/@shuding/opentype.js@1.4.0-beta.0:
resolution: {integrity: sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==}
engines: {node: '>= 8.0.0'}
hasBin: true
dependencies:
fflate: 0.7.4
string.prototype.codepointat: 0.2.1
dev: false
/@sinclair/typebox@0.27.8:
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
dev: true
@ -2416,8 +2560,8 @@ packages:
- typescript
dev: false
/astro@4.2.4(@types/node@20.11.10)(typescript@5.3.3):
resolution: {integrity: sha512-z1f52lXkHf71M5HSLKrd5G1PH5/Zfq4kMp0iUT7Na5VHcPDma/NYFPFPewDxqV6UPmyxupj3xuooFaN3j8zaow==}
/astro@4.2.6(@types/node@20.11.10)(typescript@5.3.3):
resolution: {integrity: sha512-k5i8pEI2r45JTkoE0I4JyhOH/dZFpjUA4AONbRd9Gr1LtnGOhKHDftiYOrRLUGx91q7BzoW3DOk+h4yZM4yC3g==}
engines: {node: '>=18.14.1', npm: '>=6.14.0'}
hasBin: true
dependencies:
@ -2441,6 +2585,7 @@ packages:
clsx: 2.1.0
common-ancestor-path: 1.0.1
cookie: 0.6.0
cssesc: 3.0.0
debug: 4.3.4
deterministic-object-hash: 2.0.2
devalue: 4.3.2
@ -2620,6 +2765,11 @@ packages:
resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==}
dev: false
/base64-js@0.0.8:
resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==}
engines: {node: '>= 0.4'}
dev: false
/base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
dev: false
@ -2772,6 +2922,10 @@ packages:
engines: {node: '>=14.16'}
dev: false
/camelize@1.0.1:
resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
dev: false
/caniuse-lite@1.0.30001579:
resolution: {integrity: sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==}
dev: false
@ -2996,10 +3150,31 @@ packages:
shebang-command: 2.0.0
which: 2.0.2
/css-background-parser@0.1.0:
resolution: {integrity: sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==}
dev: false
/css-box-shadow@1.0.0-3:
resolution: {integrity: sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==}
dev: false
/css-color-keywords@1.0.0:
resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==}
engines: {node: '>=4'}
dev: false
/css-selector-parser@3.0.4:
resolution: {integrity: sha512-pnmS1dbKsz6KA4EW4BznyPL2xxkNDRg62hcD0v8g6DEw2W7hxOln5M953jsp9hmw5Dg57S6o/A8GOn37mbAgcQ==}
dev: false
/css-to-react-native@3.2.0:
resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==}
dependencies:
camelize: 1.0.1
css-color-keywords: 1.0.0
postcss-value-parser: 4.2.0
dev: false
/cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
@ -3372,6 +3547,10 @@ packages:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'}
/escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
dev: false
/escape-string-regexp@1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
@ -3756,6 +3935,10 @@ packages:
dependencies:
reusify: 1.0.4
/fflate@0.7.4:
resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==}
dev: false
/file-entry-cache@6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0}
@ -4312,6 +4495,11 @@ packages:
space-separated-tokens: 2.0.2
dev: false
/hex-rgb@4.3.0:
resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==}
engines: {node: '>=6'}
dev: false
/hosted-git-info@2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
@ -4742,6 +4930,13 @@ packages:
type-check: 0.4.0
dev: true
/linebreak@1.1.0:
resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==}
dependencies:
base64-js: 0.0.8
unicode-trie: 2.0.0
dev: false
/lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
@ -5785,6 +5980,10 @@ packages:
'@pagefind/windows-x64': 1.0.4
dev: false
/pako@0.2.9:
resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
dev: false
/parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@ -5792,6 +5991,13 @@ packages:
callsites: 3.1.0
dev: true
/parse-css-color@0.2.1:
resolution: {integrity: sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==}
dependencies:
color-name: 1.1.4
hex-rgb: 4.3.0
dev: false
/parse-entities@4.0.1:
resolution: {integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==}
dependencies:
@ -5922,6 +6128,10 @@ packages:
cssesc: 3.0.0
util-deprecate: 1.0.2
/postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
dev: false
/postcss@8.4.33:
resolution: {integrity: sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==}
engines: {node: ^10 || ^12 || >=14}
@ -6401,6 +6611,28 @@ packages:
immutable: 4.3.4
source-map-js: 1.0.2
/satori-html@0.3.2:
resolution: {integrity: sha512-wjTh14iqADFKDK80e51/98MplTGfxz2RmIzh0GqShlf4a67+BooLywF17TvJPD6phO0Hxm7Mf1N5LtRYvdkYRA==}
dependencies:
ultrahtml: 1.5.2
dev: false
/satori@0.10.13:
resolution: {integrity: sha512-klCwkVYMQ/ZN5inJLHzrUmGwoRfsdP7idB5hfpJ1jfiJk1ErDitK8Hkc6Kll1+Ox2WtqEuGecSZLnmup3CGzvQ==}
engines: {node: '>=16'}
dependencies:
'@shuding/opentype.js': 1.4.0-beta.0
css-background-parser: 0.1.0
css-box-shadow: 1.0.0-3
css-to-react-native: 3.2.0
emoji-regex: 10.3.0
escape-html: 1.0.3
linebreak: 1.1.0
parse-css-color: 0.2.1
postcss-value-parser: 4.2.0
yoga-wasm-web: 0.3.3
dev: false
/sax@1.3.0:
resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
dev: false
@ -6729,6 +6961,10 @@ packages:
strip-ansi: 7.1.0
dev: false
/string.prototype.codepointat@0.2.1:
resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==}
dev: false
/string.prototype.trim@1.2.8:
resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==}
engines: {node: '>= 0.4'}
@ -6922,6 +7158,10 @@ packages:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
dev: true
/tiny-inflate@1.0.3:
resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
dev: false
/tinybench@2.6.0:
resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==}
dev: true
@ -7117,6 +7357,10 @@ packages:
resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==}
dev: true
/ultrahtml@1.5.2:
resolution: {integrity: sha512-qh4mBffhlkiXwDAOxvSGxhL0QEQsTbnP9BozOK3OYPEGvPvdWzvAUaXNtUSMdNsKDtuyjEbyVUPFZ52SSLhLqw==}
dev: false
/unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
dependencies:
@ -7133,6 +7377,13 @@ packages:
resolution: {integrity: sha512-akOOQ/Yln8a2sgcLj4U0Jmx0R5jpIg2IUyRrWOzmEbjBtGzBdHtSeFKgoEcoH4KYIG/Pb8GQ/BwtYm0GCq1Sqg==}
dev: false
/unicode-trie@2.0.0:
resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==}
dependencies:
pako: 0.2.9
tiny-inflate: 1.0.3
dev: false
/unified@10.1.2:
resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==}
dependencies:
@ -7491,7 +7742,7 @@ packages:
vitest: '>=0.16.0'
dependencies:
cross-fetch: 3.1.8
vitest: 1.2.1(@types/node@20.11.10)
vitest: 1.2.1
transitivePeerDependencies:
- encoding
dev: true
@ -7927,6 +8178,10 @@ packages:
resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
engines: {node: '>=12.20'}
/yoga-wasm-web@0.3.3:
resolution: {integrity: sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==}
dev: false
/zod-validation-error@3.0.0(zod@3.22.4):
resolution: {integrity: sha512-x+agsJJG9rvC7axF0xqTEdZhJkLHyIZkdOAWDJSmwGPzxNHMHwtU6w2yDOAAP6yuSfTAUhAMJRBfhVGY64ySEQ==}
engines: {node: '>=18.0.0'}