diff --git a/packages/astro-ghostcms-theme-default/package.json b/packages/astro-ghostcms-theme-default/package.json
index bf39b09f..44018b4b 100644
--- a/packages/astro-ghostcms-theme-default/package.json
+++ b/packages/astro-ghostcms-theme-default/package.json
@@ -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"
}
diff --git a/packages/astro-ghostcms-theme-default/src/layouts/default.astro b/packages/astro-ghostcms-theme-default/src/layouts/default.astro
index 3a7f48cd..54814fda 100644
--- a/packages/astro-ghostcms-theme-default/src/layouts/default.astro
+++ b/packages/astro-ghostcms-theme-default/src/layouts/default.astro
@@ -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;
{permalink && }
{content?.description && }
- {image && }
+
{permalink && }
{content?.description && }
- {image && }
+
{
post.primary_author ? (
diff --git a/packages/astro-ghostcms/index.ts b/packages/astro-ghostcms/index.ts
index 27c836bb..070791ff 100644
--- a/packages/astro-ghostcms/index.ts
+++ b/packages/astro-ghostcms/index.ts
@@ -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 );
diff --git a/packages/astro-ghostcms/package.json b/packages/astro-ghostcms/package.json
index d1156057..fe64582b 100644
--- a/packages/astro-ghostcms/package.json
+++ b/packages/astro-ghostcms/package.json
@@ -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",
diff --git a/packages/astro-ghostcms/src/default-routes/open-graph/[slug].png.ts b/packages/astro-ghostcms/src/default-routes/open-graph/[slug].png.ts
new file mode 100644
index 00000000..39bc29f0
--- /dev/null
+++ b/packages/astro-ghostcms/src/default-routes/open-graph/[slug].png.ts
@@ -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;
+
+
+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` ${settings.title} - ${props.title} ${site}
`,
+ width: 1920,
+ height: 1080,
+ }).toResponse({
+ satori: {
+ fonts: [
+ {
+ name: "Inter Latin",
+ data: fontData,
+ style: "normal",
+ },
+ ],
+ },
+ });
+};
\ No newline at end of file
diff --git a/packages/astro-ghostcms/src/default-routes/open-graph/author/[slug].png.ts b/packages/astro-ghostcms/src/default-routes/open-graph/author/[slug].png.ts
new file mode 100644
index 00000000..ed79a8df
--- /dev/null
+++ b/packages/astro-ghostcms/src/default-routes/open-graph/author/[slug].png.ts
@@ -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;
+
+
+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` ${settings.title} - ${props.author.name} ${site}
`,
+ width: 1920,
+ height: 1080,
+ }).toResponse({
+ satori: {
+ fonts: [
+ {
+ name: "Inter Latin",
+ data: fontData,
+ style: "normal",
+ },
+ ],
+ },
+ });
+};
\ No newline at end of file
diff --git a/packages/astro-ghostcms/src/default-routes/open-graph/authors.png.ts b/packages/astro-ghostcms/src/default-routes/open-graph/authors.png.ts
new file mode 100644
index 00000000..65438463
--- /dev/null
+++ b/packages/astro-ghostcms/src/default-routes/open-graph/authors.png.ts
@@ -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;
+
+
+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` ${settings.title} - Authors ${site}
`,
+ width: 1920,
+ height: 1080,
+ }).toResponse({
+ satori: {
+ fonts: [
+ {
+ name: "Inter Latin",
+ data: fontData,
+ style: "normal",
+ },
+ ],
+ },
+ });
+};
\ No newline at end of file
diff --git a/packages/astro-ghostcms/src/default-routes/open-graph/index.png.ts b/packages/astro-ghostcms/src/default-routes/open-graph/index.png.ts
new file mode 100644
index 00000000..54091b24
--- /dev/null
+++ b/packages/astro-ghostcms/src/default-routes/open-graph/index.png.ts
@@ -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;
+
+
+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` ${settings.title} - Index ${site}
`,
+ width: 1920,
+ height: 1080,
+ }).toResponse({
+ satori: {
+ fonts: [
+ {
+ name: "Inter Latin",
+ data: fontData,
+ style: "normal",
+ },
+ ],
+ },
+ });
+};
\ No newline at end of file
diff --git a/packages/astro-ghostcms/src/default-routes/open-graph/tag/[slug].png.ts b/packages/astro-ghostcms/src/default-routes/open-graph/tag/[slug].png.ts
new file mode 100644
index 00000000..a1f7f2aa
--- /dev/null
+++ b/packages/astro-ghostcms/src/default-routes/open-graph/tag/[slug].png.ts
@@ -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;
+
+
+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` ${settings.title} - Tag: ${props.tag.name} ${site}
`,
+ width: 1920,
+ height: 1080,
+ }).toResponse({
+ satori: {
+ fonts: [
+ {
+ name: "Inter Latin",
+ data: fontData,
+ style: "normal",
+ },
+ ],
+ },
+ });
+};
\ No newline at end of file
diff --git a/packages/astro-ghostcms/src/default-routes/open-graph/tags.png.ts b/packages/astro-ghostcms/src/default-routes/open-graph/tags.png.ts
new file mode 100644
index 00000000..efe43795
--- /dev/null
+++ b/packages/astro-ghostcms/src/default-routes/open-graph/tags.png.ts
@@ -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;
+
+
+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` ${settings.title} - Tags ${site}
`,
+ width: 1920,
+ height: 1080,
+ }).toResponse({
+ satori: {
+ fonts: [
+ {
+ name: "Inter Latin",
+ data: fontData,
+ style: "normal",
+ },
+ ],
+ },
+ });
+};
\ No newline at end of file
diff --git a/packages/astro-ghostcms/src/integrations/satori.ts b/packages/astro-ghostcms/src/integrations/satori.ts
new file mode 100644
index 00000000..77579a26
--- /dev/null
+++ b/packages/astro-ghostcms/src/integrations/satori.ts
@@ -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,
+ },
+ });
+ },
+ };
+};
\ No newline at end of file
diff --git a/packages/astro-ghostcms/types.ts b/packages/astro-ghostcms/types.ts
index fa8c9574..5e40c593 100644
--- a/packages/astro-ghostcms/types.ts
+++ b/packages/astro-ghostcms/types.ts
@@ -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";
\ No newline at end of file
+export type { ContentAPICredentials, APIVersions } from "@ts-ghost/core-api";
+
+type SatoriParameters = Parameters;
+type SatoriOptions = SatoriParameters[1];
+type ResvgOptions = NonNullable[1]>;
+
+export type SatoriAstroOGOptions = {
+ template: SatoriParameters[0];
+ width: number;
+ height: number;
+};
+
+export type ToSvgOptions = Omit;
+export type ToImageOptions = {
+ satori: ToSvgOptions;
+ resvg?:
+ | ResvgOptions
+ | ((params: { width: number; height: number }) => ResvgOptions);
+};
+export type ToResponseOptions = ToImageOptions & { response?: ResponseInit };
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 498f05c2..83bafb88 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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'}