Chore: Upgrade to `AIK` & Massive Overhaul to internal processing (#78)
This PR is described under Issue #77
This commit is contained in:
commit
62159744bc
|
@ -7,5 +7,5 @@
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"baseBranch": "main",
|
"baseBranch": "main",
|
||||||
"updateInternalDependencies": "patch",
|
"updateInternalDependencies": "patch",
|
||||||
"ignore": ["playground","starlight-playground"]
|
"ignore": ["astro-playground", "starlight-playground"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"@matthiesenxyz/create-astro-ghostcms": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Bumb all templates to new `astro-ghostcms` version v3.3
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
"@matthiesenxyz/astro-ghostcms": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
This is a HUGE internal update, Our integration is now built on [`Astro-Integration-Kit`](https://github.com/florian-lefebvre/astro-integration-kit) to give better control over the entire `Astro-GhostCMS` Eco-System.
|
||||||
|
|
||||||
|
# Breaking Changes:
|
||||||
|
- NEW USER CONFIG! Some of the options have changed! Please check the Readme for a current version of the available options!
|
||||||
|
- Thats it! Some how even though this is almost an entire rebuild, There is no other USER breaking changes aside from the new more advanced config!
|
||||||
|
|
||||||
|
# Updates:
|
||||||
|
- Moved from `@ts-ghost/core-api` to `@ts-ghost/content-api` as it provides the same functions as the standard core-api but pre-wrapped with a nice `HTTPClientFactory` instead of `HTTPClient`.
|
||||||
|
- Updated a ton of Dependencies that Dependabot was reporting as needed updated.
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
"@matthiesenxyz/starlight-ghostcms": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Bumb GhostCMS API, No user facing breaking changes.
|
||||||
|
|
||||||
|
NEW:
|
||||||
|
- You can now set a `route: "blog"` in your `astro.config.mjs` to change the default `/<route>` to your blog/posts
|
|
@ -30,7 +30,9 @@ This repo is structured as a `pnpm` monorepo. All of our packages can be found
|
||||||
|
|
||||||
In this Repo you will find the Following:
|
In this Repo you will find the Following:
|
||||||
|
|
||||||
- `playground`: Development and Testing
|
- `playgrounds/`:
|
||||||
|
- [`astro-playground`](./playgrounds/astro-playground/): Playground for Astro-GhostCMS development and testing.
|
||||||
|
- [`starlight-playground`](./playgrounds/starlight-playground/): Playground of Starlight-GhostCMS development and testing.
|
||||||
- `packages/`:
|
- `packages/`:
|
||||||
- [`create-astro-ghostcms`](./packages/create-astro-ghostcms/): CLI Utility to quickly deploy new Astro-GhostCMS projects.
|
- [`create-astro-ghostcms`](./packages/create-astro-ghostcms/): CLI Utility to quickly deploy new Astro-GhostCMS projects.
|
||||||
- [`astro-ghostcms`](./packages/astro-ghostcms/): The main Integration!
|
- [`astro-ghostcms`](./packages/astro-ghostcms/): The main Integration!
|
||||||
|
@ -39,7 +41,6 @@ In this Repo you will find the Following:
|
||||||
- [`astro-ghostcms-catppuccin`](./packages/astro-ghostcms-catppuccin/): A dark theme made with Catppuccin and TailwindCSS for Astro-GhostCMS Integration Mode.
|
- [`astro-ghostcms-catppuccin`](./packages/astro-ghostcms-catppuccin/): A dark theme made with Catppuccin and TailwindCSS for Astro-GhostCMS Integration Mode.
|
||||||
- [`astro-ghostcms-brutalbyelian`](./packages/astro-ghostcms-brutalbyelian/): [ElianCodes](https://www.elian.codes/) Brutal theme modified to work with Astro-GhostCMS
|
- [`astro-ghostcms-brutalbyelian`](./packages/astro-ghostcms-brutalbyelian/): [ElianCodes](https://www.elian.codes/) Brutal theme modified to work with Astro-GhostCMS
|
||||||
- [`starlight-ghostcms`](./packages/starlight-ghostcms/) A [Starlight Plugin](https://starlight.astro.build/resources/plugins/) to integrate your GhostCMS into your documentation website
|
- [`starlight-ghostcms`](./packages/starlight-ghostcms/) A [Starlight Plugin](https://starlight.astro.build/resources/plugins/) to integrate your GhostCMS into your documentation website
|
||||||
- `tsconfig`: *LOCAL* Development package for `@ts-ghost/core-api`.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
24
package.json
24
package.json
|
@ -6,26 +6,26 @@
|
||||||
"node": ">=18.19.0"
|
"node": ">=18.19.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm --filter playground dev",
|
"astro:dev": "pnpm --filter astro-playground dev",
|
||||||
"starlight:dev": "pnpm --filter starlight-playground dev",
|
"starlight:dev": "pnpm --filter starlight-playground dev",
|
||||||
"lint": "biome check .",
|
"lint": "biome check .",
|
||||||
"lint:fix": "biome check --apply .",
|
"lint:fix": "biome check --apply .",
|
||||||
"ci:version": "pnpm changeset version",
|
"ci:version": "pnpm changeset version",
|
||||||
"ci:publish": "pnpm changeset publish",
|
"ci:publish": "pnpm changeset publish",
|
||||||
"ci:test:api": "pnpm --filter astro-ghostcms test:ci",
|
"ci:test:integration": "pnpm --filter astro-ghostcms test:ci",
|
||||||
"test:api": "pnpm --filter astro-ghostcms test",
|
"test:integration": "pnpm --filter astro-ghostcms test",
|
||||||
"test:api:watch": "pnpm --filter astro-ghostcms test:watch",
|
"test:integration:watch": "pnpm --filter astro-ghostcms test:watch",
|
||||||
"test:api:coverage": "pnpm --filter astro-ghostcms test:coverage",
|
"test:integration:coverage": "pnpm --filter astro-ghostcms test:coverage",
|
||||||
"test:create": "pnpm --filter create-astro-ghostcms test",
|
"test:create-utility": "pnpm --filter create-astro-ghostcms test",
|
||||||
"test:slg": "pnpm --filter starlight-ghostcms test",
|
"test:starlight": "pnpm --filter starlight-ghostcms test",
|
||||||
"test:slg:watch": "pnpm --filter starlight-ghostcms test:watch",
|
"test:starlight:watch": "pnpm --filter starlight-ghostcms test:watch",
|
||||||
"test:slg:coverage": "pnpm --filter starlight-ghostcms test:coverage"
|
"test:starlight:coverage": "pnpm --filter starlight-ghostcms test:coverage"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.5.3",
|
"@biomejs/biome": "1.5.3",
|
||||||
"@changesets/cli": "^2.27.1",
|
"@changesets/cli": "^2.27.1",
|
||||||
"vitest": "^1.3.0",
|
"@vitest/ui": "^1.3.1",
|
||||||
"vitest-fetch-mock": "^0.2.2",
|
"vitest": "^1.3.1",
|
||||||
"@vitest/ui": "1.3.0"
|
"vitest-fetch-mock": "^0.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
CONTENT_API_KEY=a33da3965a3a9fb2c6b3f63b48
|
|
||||||
CONTENT_API_URL=https://ghostdemo.matthiesen.xyz
|
|
|
@ -59,7 +59,31 @@ import GhostCMS from '@matthiesenxyz/astro-ghostcms';
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: "https://YOUR-DOMAIN-HERE.com"
|
site: "https://YOUR-DOMAIN-HERE.com"
|
||||||
integrations: [GhostCMS()],
|
integrations: [
|
||||||
|
GhostCMS({
|
||||||
|
// Config Options
|
||||||
|
ghostURL: "http://example.com", // Recommended to set here, Can also set in .env as CONTENT_API_URL
|
||||||
|
ThemeProvider: { // Allows you to pass config options to our ThemeProvider if enabled.
|
||||||
|
disableThemeProvider: false, // OPTIONAL - Default False
|
||||||
|
theme: "@matthiesenxyz/astro-ghostcms-theme-default", // OPTIONAL - Default Theme shown.
|
||||||
|
};
|
||||||
|
disableDefault404: false, // Allows the user to disable the default `/404 page, to be able to create their own under `/src/pages/404.astro`.
|
||||||
|
enableRSSFeed: true, // Allows the user to Enable or disable RSS Feed Generation. Default: true
|
||||||
|
enableOGImages: true, // Allows the user to Enable or disable OG Image Generation. Default: true
|
||||||
|
verbose: false, // Show the full Log output from All parts of Astro-GhostCMS
|
||||||
|
Integrations: {
|
||||||
|
// This allows user config passthrough from Astro-GhostCMS to the Included Integrations
|
||||||
|
robotsTxt: {
|
||||||
|
// OPTIONAL
|
||||||
|
// ADVANCED USAGE - https://www.npmjs.com/package/astro-robots-txt#configuration
|
||||||
|
},
|
||||||
|
sitemap: {
|
||||||
|
// OPTIONAL
|
||||||
|
// ADVANCED USAGE - https://docs.astro.build/en/guides/integrations-guide/sitemap
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
],
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -113,4 +137,3 @@ For more information and to see the docs please check our website: [Astro-GhostC
|
||||||
# Foot Notes & Credits
|
# Foot Notes & Credits
|
||||||
|
|
||||||
[^1]: Ghost.org, Ghost.io, Ghost are all trademarks of [The Ghost Foundation](https://ghost.org/). This project is Open Source and not directly related to or provided by The Ghost Foundation and is intended to help create a easier method to utilize their provided JavaScript tools to link a Headless GhostCMS install in to your Astro project.
|
[^1]: Ghost.org, Ghost.io, Ghost are all trademarks of [The Ghost Foundation](https://ghost.org/). This project is Open Source and not directly related to or provided by The Ghost Foundation and is intended to help create a easier method to utilize their provided JavaScript tools to link a Headless GhostCMS install in to your Astro project.
|
||||||
|
|
||||||
|
|
|
@ -1,359 +0,0 @@
|
||||||
import path from "node:path";
|
|
||||||
import { fileURLToPath } from "node:url";
|
|
||||||
import type { AstroIntegration } from "astro";
|
|
||||||
import type { SafeParseError, SafeParseSuccess } from "astro/zod";
|
|
||||||
import fse from "fs-extra";
|
|
||||||
import { loadEnv } from "vite";
|
|
||||||
import ghostRobots from "./src/integrations/robots-txt";
|
|
||||||
import ghostSitemap from "./src/integrations/sitemap";
|
|
||||||
import { UserConfigSchema } from "./src/schemas";
|
|
||||||
import { addVirtualImport } from "./src/utils/add-virtual-import";
|
|
||||||
import latestVersion from "./src/utils/latestVersion.js";
|
|
||||||
import { fromZodError } from "./src/utils/zod-validation/fromZodError.js";
|
|
||||||
import type { UserConfig } from "./types";
|
|
||||||
|
|
||||||
/** INTERNAL CONSTANTS */
|
|
||||||
const IC = {
|
|
||||||
/** INTERNAL PACKAGE NAME */
|
|
||||||
PKG: "@matthiesenxyz/astro-ghostcms",
|
|
||||||
/** INTERNAL PACKAGE NAME (THEME) */
|
|
||||||
DT: "@matthiesenxyz/astro-ghostcms-theme-default",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
CHECK_ENV: "Checking for Environment Variables...",
|
|
||||||
/** SET ENV GRABBER MODE */
|
|
||||||
MODE: "all",
|
|
||||||
/** SET ENV GRABBER PREFIXES */
|
|
||||||
PREFIXES: "CONTENT_API",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
KEY_MISSING: "CONTENT_API_KEY Missing from .env/environment variables",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
URL_MISSING:
|
|
||||||
"CONTENT_API_URL Missing from .env/environment variables or ghostURL under the integration settings in `astro.config.mjs`",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
IT: "Injecting Theme: ",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
IDR: "Injecting Default Routes...",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
ITR: "Injecting Default Theme Routes...",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
IRD: "Route Injection Disabled - Skipping...",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
IIR: "Injecting Integration Route: ",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
II: "Injecting Integration: ",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
AIbU: "Already Imported by User: ",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
CF: "Checking for ",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
CONFSETUPDONE: "Step Complete",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
F0FR: "Inject `/404` Route",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
RSS: "Injecting `/rss.xml` Route and `@astrojs/rss` Integration",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
NOURL:
|
|
||||||
"No Ghost URL defined in User Config: Falling back to environment variables.",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
id404: "404 Injection Disabled",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
idRSS: "RSS Injection Disabled",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
satori_e: "Injecting Satori-OpenGraph Generator",
|
|
||||||
/** INTERNAL STRING */
|
|
||||||
satori_d: "Satori Injection disabled",
|
|
||||||
};
|
|
||||||
|
|
||||||
/** CONTENT API ENVIRONMENT VARIABLES */
|
|
||||||
const ENV = loadEnv(IC.MODE, process.cwd(), IC.PREFIXES);
|
|
||||||
|
|
||||||
/** Astro-GhostCMS Integration
|
|
||||||
* @ For more information and to see the docs check
|
|
||||||
* @see https://astro-ghostcms.xyz
|
|
||||||
*/
|
|
||||||
export default function GhostCMS(options: UserConfig): AstroIntegration {
|
|
||||||
return {
|
|
||||||
name: "astro-ghostcms",
|
|
||||||
hooks: {
|
|
||||||
"astro:config:setup": async ({
|
|
||||||
injectRoute,
|
|
||||||
config,
|
|
||||||
updateConfig,
|
|
||||||
logger,
|
|
||||||
}) => {
|
|
||||||
// DEFINE LOGGERS
|
|
||||||
const logConfigCheck = logger.fork("astro-ghostcms/config:check");
|
|
||||||
const logConfigSetup = logger.fork("astro-ghostcms/config:setup");
|
|
||||||
|
|
||||||
// CHECK USER CONFIG AND MAKE AVAILBLE TO INTEGRATIONS
|
|
||||||
logConfigCheck.info("Checking Config...");
|
|
||||||
const GhostUserConfig = UserConfigSchema.safeParse(
|
|
||||||
options || {},
|
|
||||||
) as SafeParseSuccess<UserConfig>;
|
|
||||||
if (!GhostUserConfig.success) {
|
|
||||||
const validationError = fromZodError(
|
|
||||||
(GhostUserConfig as unknown as SafeParseError<UserConfig>).error,
|
|
||||||
);
|
|
||||||
logConfigCheck.error(`Config Error - ${validationError}`);
|
|
||||||
throw Error("");
|
|
||||||
}
|
|
||||||
const GhostConfig = GhostUserConfig.data;
|
|
||||||
const GCD = {
|
|
||||||
theme: GhostConfig.theme,
|
|
||||||
dRI: GhostConfig.disableRouteInjection,
|
|
||||||
dCO: GhostConfig.disableConsoleOutput,
|
|
||||||
SM: GhostConfig.sitemap,
|
|
||||||
RTXT: GhostConfig.robotstxt,
|
|
||||||
gSite: GhostConfig.ghostURL,
|
|
||||||
dRSS: GhostConfig.disableRSS,
|
|
||||||
d404: GhostConfig.disable404,
|
|
||||||
dOG: GhostConfig.disableSatoriOG,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check For ENV Variables
|
|
||||||
if (!GCD.dCO) {
|
|
||||||
logConfigCheck.info(IC.CHECK_ENV);
|
|
||||||
}
|
|
||||||
if (ENV.CONTENT_API_KEY === undefined) {
|
|
||||||
logConfigCheck.error(IC.KEY_MISSING);
|
|
||||||
throw IC.KEY_MISSING;
|
|
||||||
}
|
|
||||||
if (GCD.gSite === undefined) {
|
|
||||||
logConfigCheck.warn(IC.NOURL);
|
|
||||||
if (ENV.CONTENT_API_URL === undefined) {
|
|
||||||
logConfigCheck.error(IC.URL_MISSING);
|
|
||||||
throw IC.URL_MISSING;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!GCD.dRI) {
|
|
||||||
// THEME SELECTOR
|
|
||||||
if (GCD.theme === IC.DT) {
|
|
||||||
if (!GCD.dCO) {
|
|
||||||
logConfigCheck.info(IC.IT + IC.DT);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!GCD.dCO) {
|
|
||||||
logConfigCheck.info(IC.IT + GCD.theme);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// INJECT ROUTES
|
|
||||||
//// DEFAULT PROGRAM ROUTES
|
|
||||||
if (!GCD.dCO) {
|
|
||||||
logConfigSetup.info(IC.IDR);
|
|
||||||
}
|
|
||||||
if (!GCD.d404) {
|
|
||||||
if (!GCD.dCO) {
|
|
||||||
logConfigSetup.info(IC.F0FR);
|
|
||||||
}
|
|
||||||
injectRoute({
|
|
||||||
pattern: "/404",
|
|
||||||
entrypoint: `${IC.PKG}/404.astro`,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (!GCD.dCO) {
|
|
||||||
logConfigSetup.info(IC.id404);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!GCD.dRSS) {
|
|
||||||
if (!GCD.dCO) {
|
|
||||||
logConfigSetup.info(IC.RSS);
|
|
||||||
}
|
|
||||||
injectRoute({
|
|
||||||
pattern: "/rss-style.xsl",
|
|
||||||
entrypoint: `${IC.PKG}/rss-style.xsl.ts`,
|
|
||||||
});
|
|
||||||
injectRoute({
|
|
||||||
pattern: "/rss.xml",
|
|
||||||
entrypoint: `${IC.PKG}/rss.xml.ts`,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (!GCD.dCO) {
|
|
||||||
logConfigSetup.info(IC.idRSS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!GCD.dOG) {
|
|
||||||
if (!GCD.dCO) {
|
|
||||||
logConfigSetup.info(IC.satori_e);
|
|
||||||
}
|
|
||||||
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`,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (!GCD.dCO) {
|
|
||||||
logConfigSetup.info(IC.satori_d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// THEME ROUTES
|
|
||||||
if (!GCD.dCO) {
|
|
||||||
logConfigSetup.info(IC.ITR);
|
|
||||||
}
|
|
||||||
|
|
||||||
injectRoute({
|
|
||||||
pattern: "/",
|
|
||||||
entrypoint: `${GCD.theme}/index.astro`,
|
|
||||||
});
|
|
||||||
injectRoute({
|
|
||||||
pattern: "/[slug]",
|
|
||||||
entrypoint: `${GCD.theme}/[slug].astro`,
|
|
||||||
});
|
|
||||||
injectRoute({
|
|
||||||
pattern: "/tags",
|
|
||||||
entrypoint: `${GCD.theme}/tags.astro`,
|
|
||||||
});
|
|
||||||
injectRoute({
|
|
||||||
pattern: "/authors",
|
|
||||||
entrypoint: `${GCD.theme}/authors.astro`,
|
|
||||||
});
|
|
||||||
injectRoute({
|
|
||||||
pattern: "/tag/[slug]",
|
|
||||||
entrypoint: `${GCD.theme}/tag/[slug].astro`,
|
|
||||||
});
|
|
||||||
injectRoute({
|
|
||||||
pattern: "/author/[slug]",
|
|
||||||
entrypoint: `${GCD.theme}/author/[slug].astro`,
|
|
||||||
});
|
|
||||||
injectRoute({
|
|
||||||
pattern: "/archives/[...page]",
|
|
||||||
entrypoint: `${GCD.theme}/archives/[...page].astro`,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (!GCD.dCO) {
|
|
||||||
logConfigSetup.info(IC.IRD);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IMPORT INTEGRATIONS & INTEGRATION ROUTES
|
|
||||||
const integrations = [...config.integrations];
|
|
||||||
|
|
||||||
// IMPORT INTEGRATION: @ASTROJS/SITEMAP
|
|
||||||
if (!GCD.dCO) {
|
|
||||||
logConfigSetup.info(`${IC.CF}@astrojs/sitemap`);
|
|
||||||
}
|
|
||||||
if (!integrations.find(({ name }) => name === "@astrojs/sitemap")) {
|
|
||||||
if (!GCD.dCO) {
|
|
||||||
logConfigSetup.info(`${IC.II}@astrojs/sitemap`);
|
|
||||||
}
|
|
||||||
integrations.push(ghostSitemap(GCD.SM));
|
|
||||||
} else {
|
|
||||||
if (!GCD.dCO) {
|
|
||||||
logConfigSetup.info(`${IC.AIbU}@astrojs/sitemap`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IMPORT INTEGRATION: ASTRO-ROBOTS-TXT
|
|
||||||
if (!GCD.dCO) {
|
|
||||||
logConfigSetup.info(`${IC.CF}astro-robots-txt`);
|
|
||||||
}
|
|
||||||
if (!integrations.find(({ name }) => name === "astro-robots-txt")) {
|
|
||||||
if (!GCD.dCO) {
|
|
||||||
logConfigSetup.info(`${IC.II}astro-robots-txt`);
|
|
||||||
}
|
|
||||||
integrations.push(ghostRobots(GCD.RTXT));
|
|
||||||
} else {
|
|
||||||
if (!GCD.dCO) {
|
|
||||||
logConfigSetup.info(`${IC.AIbU}astro-robots-txt`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FINAL STEP TO KEEP INTEGRATION LIVE
|
|
||||||
try {
|
|
||||||
updateConfig({
|
|
||||||
// UPDATE ASTRO CONFIG WITH INTEGRATED INTEGRATIONS
|
|
||||||
integrations: [ghostSitemap(GCD.SM), ghostRobots(GCD.RTXT)],
|
|
||||||
vite: {
|
|
||||||
optimizeDeps: { exclude: ["@resvg/resvg-js"] },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
logConfigSetup.error(e as string);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
addVirtualImport({
|
|
||||||
name: "virtual:@matthiesenxyz/astro-ghostcms/config",
|
|
||||||
content: `export default ${JSON.stringify(GhostUserConfig.data)}`,
|
|
||||||
updateConfig,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
"astro:config:done": async ({ logger }) => {
|
|
||||||
// Config Done
|
|
||||||
const logConfigDone = logger.fork("astro-ghostcms/config:done");
|
|
||||||
const pJSON = await fse.readJson(
|
|
||||||
path.resolve(fileURLToPath(import.meta.url), "..", "package.json"),
|
|
||||||
);
|
|
||||||
const pkgVer = pJSON.version;
|
|
||||||
logConfigDone.info(`Config Done. Current Version: v${pkgVer}`);
|
|
||||||
},
|
|
||||||
"astro:server:setup": async ({ logger }) => {
|
|
||||||
// Dev Server Start
|
|
||||||
const logServerSetup = logger.fork("astro-ghostcms/server:setup");
|
|
||||||
const logCurrentVersion = logger.fork("astro-ghostcms/current-version");
|
|
||||||
const logNpmVersion = logger.fork("astro-ghostcms/npm-pub-version");
|
|
||||||
const logCheck = logger.fork("astro-ghostcms/check");
|
|
||||||
const pJSON = await fse.readJson(
|
|
||||||
path.resolve(fileURLToPath(import.meta.url), "..", "package.json"),
|
|
||||||
);
|
|
||||||
const pkgVer = pJSON.version;
|
|
||||||
const npmVER = await latestVersion(IC.PKG);
|
|
||||||
if (pkgVer !== npmVER) {
|
|
||||||
logCurrentVersion.warn(`Current Installed Version is v${pkgVer}`);
|
|
||||||
logNpmVersion.warn(`Latest Published Version is v${npmVER}`);
|
|
||||||
logCheck.warn("Please consider updating.");
|
|
||||||
}
|
|
||||||
logServerSetup.info(
|
|
||||||
"Setting up Astro-GhostCMS server for Development!",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
"astro:server:start": async ({ logger }) => {
|
|
||||||
// Server Start
|
|
||||||
const logServerStart = logger.fork("astro-ghostcms/server:start");
|
|
||||||
logServerStart.info("Astro-GhostCMS Integration Ready!");
|
|
||||||
},
|
|
||||||
"astro:build:done": async ({ logger }) => {
|
|
||||||
// Build Done
|
|
||||||
const logBuildDone = logger.fork("astro-ghostcms/build:done");
|
|
||||||
const logCurrentVersion = logger.fork("astro-ghostcms/current-version");
|
|
||||||
const logNpmVersion = logger.fork("astro-ghostcms/npm-pub-version");
|
|
||||||
const logCheck = logger.fork("astro-ghostcms/check");
|
|
||||||
const pJSON = await fse.readJson(
|
|
||||||
path.resolve(fileURLToPath(import.meta.url), "..", "package.json"),
|
|
||||||
);
|
|
||||||
const pkgVer = pJSON.version;
|
|
||||||
const npmVER = await latestVersion(IC.PKG);
|
|
||||||
if (pkgVer !== npmVER) {
|
|
||||||
logCurrentVersion.warn(`Current Installed Version is v${pkgVer}`);
|
|
||||||
logNpmVersion.warn(`Latest Published Version is v${npmVER}`);
|
|
||||||
logCheck.warn("Please consider updating.");
|
|
||||||
}
|
|
||||||
logBuildDone.info(
|
|
||||||
`Build Complete, Integration Now ready for Production. Astro-GhostCMS v${pkgVer}`,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,111 +1,85 @@
|
||||||
{
|
{
|
||||||
"name": "@matthiesenxyz/astro-ghostcms",
|
"name": "@matthiesenxyz/astro-ghostcms",
|
||||||
"description": "Astro GhostCMS integration to allow easier importing of GhostCMS Content",
|
"description": "Astro GhostCMS integration to allow easier importing of GhostCMS Content",
|
||||||
"version": "3.2.9",
|
"version": "3.2.9",
|
||||||
"homepage": "https://astro-ghostcms.xyz/",
|
"homepage": "https://astro-ghostcms.xyz/",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
|
||||||
"sideEffects": false,
|
|
||||||
"author": {
|
|
||||||
"email": "adam@matthiesen.xyz",
|
|
||||||
"name": "Adam Matthiesen - MatthiesenXYZ",
|
|
||||||
"url": "https://matthiesen.xyz"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"astro-component",
|
|
||||||
"astro-integration",
|
|
||||||
"withastro",
|
|
||||||
"astro",
|
|
||||||
"blog",
|
|
||||||
"content",
|
|
||||||
"integration",
|
|
||||||
"ghost",
|
|
||||||
"ghostcms",
|
|
||||||
"ghostcms-theme",
|
|
||||||
"ghost-theme",
|
|
||||||
"astro-theme"
|
|
||||||
],
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/MatthiesenXYZ/astro-ghostcms.git"
|
|
||||||
},
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/MatthiesenXYZ/astro-ghostcms/issues",
|
|
||||||
"email": "issues@astro-ghostcms.xyz"
|
|
||||||
},
|
|
||||||
"main": "index.ts",
|
|
||||||
"types": "types.d.ts",
|
|
||||||
"files": [
|
|
||||||
"src",
|
|
||||||
".env.demo",
|
|
||||||
"index.ts",
|
|
||||||
"tsconfig.json",
|
|
||||||
"types.d.ts"
|
|
||||||
],
|
|
||||||
"exports": {
|
|
||||||
".": "./index.ts",
|
|
||||||
"./api": {
|
|
||||||
"types": "./src/api/index.ts",
|
|
||||||
"default": "./src/api/index.ts"
|
|
||||||
},
|
},
|
||||||
"./api-core": "./src/api/content-api/index.ts",
|
"sideEffects": false,
|
||||||
"./config": {
|
"author": {
|
||||||
"types": "./src/integrations/virtual.d.ts",
|
"email": "adam@matthiesen.xyz",
|
||||||
"default": "./src/integrations/virtual-config.ts"
|
"name": "Adam Matthiesen - MatthiesenXYZ",
|
||||||
|
"url": "https://matthiesen.xyz"
|
||||||
},
|
},
|
||||||
"./satoriOG": "./src/integrations/satori.ts",
|
"keywords": [
|
||||||
"./404.astro": "./src/default-routes/404/404.astro",
|
"astro-component",
|
||||||
"./rss.xml.ts": "./src/default-routes/rss.xml.ts",
|
"astro-integration",
|
||||||
"./rss-style.xsl.ts": "./src/default-routes/rss-style.xsl.ts",
|
"withastro",
|
||||||
"./open-graph/index.png.ts": "./src/default-routes/open-graph/index.png.ts",
|
"astro",
|
||||||
"./open-graph/authors.png.ts": "./src/default-routes/open-graph/authors.png.ts",
|
"blog",
|
||||||
"./open-graph/tags.png.ts": "./src/default-routes/open-graph/tags.png.ts",
|
"content",
|
||||||
"./open-graph/[slug].png.ts": "./src/default-routes/open-graph/[slug].png.ts",
|
"integration",
|
||||||
"./open-graph/author/[slug].png.ts": "./src/default-routes/open-graph/author/[slug].png.ts",
|
"ghost",
|
||||||
"./open-graph/tag/[slug].png.ts": "./src/default-routes/open-graph/tag/[slug].png.ts"
|
"ghostcms",
|
||||||
},
|
"ghostcms-theme",
|
||||||
"scripts": {
|
"ghost-theme",
|
||||||
"test": "vitest run",
|
"astro-theme"
|
||||||
"test:watch": "vitest",
|
],
|
||||||
"test:coverage": "vitest run --coverage",
|
"repository": {
|
||||||
"test:ci": "vitest run --coverage.enabled --coverage.reporter='text-summary'"
|
"type": "git",
|
||||||
},
|
"url": "git+https://github.com/MatthiesenXYZ/astro-ghostcms.git"
|
||||||
"devDependencies": {
|
},
|
||||||
"@astrojs/check": "^0.5.4",
|
"bugs": {
|
||||||
"@ts-ghost/core-api": "*",
|
"url": "https://github.com/MatthiesenXYZ/astro-ghostcms/issues",
|
||||||
"@ts-ghost/tsconfig": "*",
|
"email": "issues@astro-ghostcms.xyz"
|
||||||
"@matthiesenxyz/astro-ghostcms-theme-default": "*",
|
},
|
||||||
"@matthiesenxyz/astro-ghostcms-catppuccin": "*",
|
"files": [
|
||||||
"@types/fs-extra": "^11.0.1",
|
"src",
|
||||||
"@types/node": "^20.11.19",
|
"CHANGELOG.md",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.0.1",
|
"LICENSE",
|
||||||
"@typescript-eslint/parser": "^7.0.1",
|
"README.md"
|
||||||
"eslint": "^8.56.0",
|
],
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"exports": {
|
||||||
"eslint-plugin-astro": "^0.31.4",
|
".": "./src/index.ts",
|
||||||
"prettier": "^3.2.5",
|
"./api": "./src/api/index.ts",
|
||||||
"prettier-plugin-astro": "^0.13.0",
|
"./satoriOG": "./src/integrations/satoriog/satori.ts"
|
||||||
"typescript": "^5.3.3",
|
},
|
||||||
"vitest": "^1.3.0",
|
"scripts": {
|
||||||
"vitest-fetch-mock": "^0.2.2"
|
"test": "vitest run",
|
||||||
},
|
"test:watch": "vitest",
|
||||||
"dependencies": {
|
"test:coverage": "vitest run --coverage",
|
||||||
"@matthiesenxyz/astro-ghostcms-theme-default": "^0.1.13",
|
"test:ci": "vitest run --coverage.enabled --coverage.reporter='text-summary'"
|
||||||
"@astrojs/rss": "^4.0.5",
|
},
|
||||||
"@astrojs/sitemap": "^3.0.5",
|
"enginesStrict": {
|
||||||
"@resvg/resvg-js": "^2.6.0",
|
"node": ">=18.19.0"
|
||||||
"@ts-ghost/core-api": "^5.1.2",
|
},
|
||||||
"astro": "^4.4.0",
|
"peerDependencies": {
|
||||||
"astro-robots-txt": "^1.0.0",
|
"astro": ">=4.4.1"
|
||||||
"fs-extra": "^11.1.0",
|
},
|
||||||
"package-json": "9.0.0",
|
"devDependencies": {
|
||||||
"satori": "^0.10.11",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"satori-html": "^0.3.2",
|
"@types/node": "^20.11.24",
|
||||||
"vite": "^5.1.3",
|
"vitest": "^1.3.1",
|
||||||
"vite-tsconfig-paths": "^4.2.2",
|
"vitest-fetch-mock": "^0.2.2"
|
||||||
"zod": "^3.22.4"
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/rss": "^4.0.5",
|
||||||
|
"@astrojs/sitemap": "^3.1.1",
|
||||||
|
"@matthiesenxyz/astro-ghostcms-theme-default": "^0.1.13",
|
||||||
|
"@resvg/resvg-js": "^2.6.0",
|
||||||
|
"@ts-ghost/core-api": "^6.0.0",
|
||||||
|
"@ts-ghost/content-api": "^4.0.12",
|
||||||
|
"astro-integration-kit": "^0.5.1",
|
||||||
|
"astro-robots-txt": "^1.0.0",
|
||||||
|
"fs-extra": "^11.2.0",
|
||||||
|
"package-json": "^10.0.0",
|
||||||
|
"picocolors": "^1.0.0",
|
||||||
|
"satori": "^0.10.13",
|
||||||
|
"satori-html": "^0.3.2",
|
||||||
|
"vite": "^5.1.4"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
|
@ -1,116 +0,0 @@
|
||||||
import {
|
|
||||||
APIComposer,
|
|
||||||
BasicFetcher,
|
|
||||||
HTTPClient,
|
|
||||||
contentAPICredentialsSchema,
|
|
||||||
slugOrIdSchema,
|
|
||||||
} from "@ts-ghost/core-api";
|
|
||||||
|
|
||||||
import {
|
|
||||||
authorsIncludeSchema,
|
|
||||||
authorsSchema,
|
|
||||||
pagesIncludeSchema,
|
|
||||||
pagesSchema,
|
|
||||||
postsIncludeSchema,
|
|
||||||
postsSchema,
|
|
||||||
settingsSchema,
|
|
||||||
tagsIncludeSchema,
|
|
||||||
tagsSchema,
|
|
||||||
tiersIncludeSchema,
|
|
||||||
tiersSchema,
|
|
||||||
} from "./schemas";
|
|
||||||
|
|
||||||
export type { ContentAPICredentials, APIVersions } from "@ts-ghost/core-api";
|
|
||||||
|
|
||||||
export enum BrowseEndpointType {
|
|
||||||
authors = "authors",
|
|
||||||
tiers = "tiers",
|
|
||||||
posts = "posts",
|
|
||||||
pages = "pages",
|
|
||||||
tags = "tags",
|
|
||||||
settings = "settings",
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class TS_API<Version extends `v5.${string}` = any> {
|
|
||||||
private httpClient: HTTPClient;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected readonly url: string,
|
|
||||||
protected readonly key: string,
|
|
||||||
protected readonly version: Version,
|
|
||||||
) {
|
|
||||||
const apiCredentials = contentAPICredentialsSchema.parse({
|
|
||||||
key,
|
|
||||||
version,
|
|
||||||
url,
|
|
||||||
});
|
|
||||||
this.httpClient = new HTTPClient({
|
|
||||||
...apiCredentials,
|
|
||||||
endpoint: "content",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get authors() {
|
|
||||||
return new APIComposer(
|
|
||||||
"authors",
|
|
||||||
{
|
|
||||||
schema: authorsSchema,
|
|
||||||
identitySchema: slugOrIdSchema,
|
|
||||||
include: authorsIncludeSchema,
|
|
||||||
},
|
|
||||||
this.httpClient,
|
|
||||||
).access(["read", "browse"]);
|
|
||||||
}
|
|
||||||
get tiers() {
|
|
||||||
return new APIComposer(
|
|
||||||
"tiers",
|
|
||||||
{
|
|
||||||
schema: tiersSchema,
|
|
||||||
identitySchema: slugOrIdSchema,
|
|
||||||
include: tiersIncludeSchema,
|
|
||||||
},
|
|
||||||
this.httpClient,
|
|
||||||
).access(["browse", "read"]);
|
|
||||||
}
|
|
||||||
get posts() {
|
|
||||||
return new APIComposer(
|
|
||||||
"posts",
|
|
||||||
{
|
|
||||||
schema: postsSchema,
|
|
||||||
identitySchema: slugOrIdSchema,
|
|
||||||
include: postsIncludeSchema,
|
|
||||||
},
|
|
||||||
this.httpClient,
|
|
||||||
).access(["browse", "read"]);
|
|
||||||
}
|
|
||||||
get pages() {
|
|
||||||
return new APIComposer(
|
|
||||||
"pages",
|
|
||||||
{
|
|
||||||
schema: pagesSchema,
|
|
||||||
identitySchema: slugOrIdSchema,
|
|
||||||
include: pagesIncludeSchema,
|
|
||||||
},
|
|
||||||
this.httpClient,
|
|
||||||
).access(["browse", "read"]);
|
|
||||||
}
|
|
||||||
get tags() {
|
|
||||||
return new APIComposer(
|
|
||||||
"tags",
|
|
||||||
{
|
|
||||||
schema: tagsSchema,
|
|
||||||
identitySchema: slugOrIdSchema,
|
|
||||||
include: tagsIncludeSchema,
|
|
||||||
},
|
|
||||||
this.httpClient,
|
|
||||||
).access(["browse", "read"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
get settings() {
|
|
||||||
return new BasicFetcher(
|
|
||||||
"settings",
|
|
||||||
{ output: settingsSchema },
|
|
||||||
this.httpClient,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
export { default as TS_API } from "./content-api";
|
|
||||||
export * from "./schemas";
|
|
||||||
|
|
||||||
export type {
|
|
||||||
InferFetcherDataShape,
|
|
||||||
InferResponseDataShape,
|
|
||||||
BrowseParams,
|
|
||||||
} from "@ts-ghost/core-api";
|
|
|
@ -1 +0,0 @@
|
||||||
export * from "./authors";
|
|
|
@ -1 +0,0 @@
|
||||||
export * from "./socials";
|
|
|
@ -1 +0,0 @@
|
||||||
export * from "./pages";
|
|
|
@ -1 +0,0 @@
|
||||||
export * from "./posts";
|
|
|
@ -1 +0,0 @@
|
||||||
export * from "./settings";
|
|
|
@ -1 +0,0 @@
|
||||||
export * from "./tags";
|
|
|
@ -1 +0,0 @@
|
||||||
export * from "./tiers";
|
|
|
@ -1,11 +1,15 @@
|
||||||
import { assert, beforeEach, describe, expect, test } from "vitest";
|
import { assert, beforeEach, describe, expect, test } from "vitest";
|
||||||
|
|
||||||
import TS_API from "./content-api";
|
import { TSGhostContentAPI } from "@ts-ghost/content-api";
|
||||||
|
|
||||||
describe("content-api", () => {
|
describe("content-api", () => {
|
||||||
let api: TS_API;
|
let api: TSGhostContentAPI;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
api = new TS_API("https://ghost.org", "59d4bf56c73c04a18c867dc3ba", "v5.0");
|
api = new TSGhostContentAPI(
|
||||||
|
"https://ghost.org",
|
||||||
|
"59d4bf56c73c04a18c867dc3ba",
|
||||||
|
"v5.0",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("content-api", () => {
|
test("content-api", () => {
|
||||||
|
@ -14,21 +18,25 @@ describe("content-api", () => {
|
||||||
|
|
||||||
test("content-api shouldn't instantiate with an incorrect url", () => {
|
test("content-api shouldn't instantiate with an incorrect url", () => {
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
const api = new TS_API("ghost.org", "59d4bf56c73c04a18c867dc3ba", "v5.0");
|
const api = new TSGhostContentAPI(
|
||||||
|
"ghost.org",
|
||||||
|
"59d4bf56c73c04a18c867dc3ba",
|
||||||
|
"v5.0",
|
||||||
|
);
|
||||||
api.settings;
|
api.settings;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("content-api shouldn't instantiate with an incorrect key", () => {
|
test("content-api shouldn't instantiate with an incorrect key", () => {
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
const api = new TS_API("https://ghost.org", "a", "v5.0");
|
const api = new TSGhostContentAPI("https://ghost.org", "a", "v5.0");
|
||||||
api.settings;
|
api.settings;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("content-api shouldn't instantiate with an incorrect version", () => {
|
test("content-api shouldn't instantiate with an incorrect version", () => {
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
const api = new TS_API(
|
const api = new TSGhostContentAPI(
|
||||||
"https://ghost.org",
|
"https://ghost.org",
|
||||||
"1efedd9db174adee2d23d982:4b74dca0219bad629852191af326a45037346c2231240e0f7aec1f9371cc14e8",
|
"1efedd9db174adee2d23d982:4b74dca0219bad629852191af326a45037346c2231240e0f7aec1f9371cc14e8",
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
|
@ -1,23 +1,24 @@
|
||||||
import { TS_API } from "./content-api";
|
import { TSGhostContentAPI } from "@ts-ghost/content-api";
|
||||||
import type { Page, Post } from "./content-api/schemas";
|
import type { Page, Post } from "../schemas/api";
|
||||||
|
|
||||||
// LOAD ENVIRONMENT VARIABLES
|
// LOAD ENVIRONMENT VARIABLES
|
||||||
import { loadEnv } from "vite";
|
import { loadEnv } from "vite";
|
||||||
|
|
||||||
import config from "../integrations/virtual-config";
|
|
||||||
|
|
||||||
const CONF_URL = config.ghostURL;
|
|
||||||
|
|
||||||
const { CONTENT_API_KEY, CONTENT_API_URL } = loadEnv(
|
const { CONTENT_API_KEY, CONTENT_API_URL } = loadEnv(
|
||||||
"all",
|
"all",
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
"CONTENT_",
|
"CONTENT_",
|
||||||
);
|
);
|
||||||
|
|
||||||
const ghostApiKey = CONTENT_API_KEY;
|
// LOAD CONFIG
|
||||||
const ghostUrl = CONF_URL ? CONF_URL : CONTENT_API_URL;
|
import config from "virtual:@matthiesenxyz/astro-ghostcms/config";
|
||||||
|
const CONF_URL = config.ghostURL;
|
||||||
|
|
||||||
|
// SETUP GHOST API
|
||||||
|
const ghostApiKey = CONTENT_API_KEY || "";
|
||||||
|
const ghostUrl = CONF_URL || CONTENT_API_URL || "";
|
||||||
const version = "v5.0";
|
const version = "v5.0";
|
||||||
const api = new TS_API(ghostUrl, ghostApiKey, version);
|
const api = new TSGhostContentAPI(ghostUrl, ghostApiKey, version);
|
||||||
|
|
||||||
export const getAllAuthors = async () => {
|
export const getAllAuthors = async () => {
|
||||||
const results = await api.authors
|
const results = await api.authors
|
|
@ -1,3 +1,3 @@
|
||||||
export * from "./api-functions";
|
export * from "./ghostAPI";
|
||||||
export * from "./content-api/schemas";
|
|
||||||
export * from "./invariant";
|
export * from "./invariant";
|
||||||
|
export * from "../schemas/api/index";
|
||||||
|
|
|
@ -20,7 +20,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
*/
|
*/
|
||||||
const tinyinvariant = "merged";
|
|
||||||
const isProduction: boolean = process.env.NODE_ENV === "production";
|
const isProduction: boolean = process.env.NODE_ENV === "production";
|
||||||
const prefix: string = "Invariant failed";
|
const prefix: string = "Invariant failed";
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,303 @@
|
||||||
|
import fse from "fs-extra";
|
||||||
|
import { createResolver, defineIntegration } from "astro-integration-kit";
|
||||||
|
import { corePlugins } from "astro-integration-kit/plugins";
|
||||||
|
import { AstroError } from "astro/errors";
|
||||||
|
import type { AstroIntegration } from "astro";
|
||||||
|
import c from "picocolors";
|
||||||
|
import { loadEnv } from "vite";
|
||||||
|
import sitemap from "@astrojs/sitemap";
|
||||||
|
import robotsTxt from "astro-robots-txt";
|
||||||
|
|
||||||
|
// Internal Imports
|
||||||
|
import { GhostUserConfigSchema } from "./schemas/userconfig";
|
||||||
|
import ghostRSS from "./integrations/rssfeed";
|
||||||
|
import ghostOGImages from "./integrations/satoriog";
|
||||||
|
import ghostThemeProvider from "./integrations/themeprovider";
|
||||||
|
import latestVersion from "./utils/latestVersion";
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
|
const ENV = loadEnv("all", process.cwd(), "CONTENT_API");
|
||||||
|
|
||||||
|
/** Astro-GhostCMS Integration
|
||||||
|
* @description This integration allows you to use GhostCMS as a headless CMS for your Astro project
|
||||||
|
* @see https://astro-ghostcms.xyz for the most up-to-date documentation!
|
||||||
|
*/
|
||||||
|
export default defineIntegration({
|
||||||
|
name: "@matthiesenxyz/astro-ghostcms",
|
||||||
|
optionsSchema: GhostUserConfigSchema,
|
||||||
|
plugins: [...corePlugins],
|
||||||
|
setup({ options, name }) {
|
||||||
|
const { resolve } = createResolver(import.meta.url);
|
||||||
|
|
||||||
|
return {
|
||||||
|
"astro:config:setup": ({
|
||||||
|
watchIntegration,
|
||||||
|
hasIntegration,
|
||||||
|
addIntegration,
|
||||||
|
addVirtualImports,
|
||||||
|
injectRoute,
|
||||||
|
logger,
|
||||||
|
}) => {
|
||||||
|
// Set up verbose logging
|
||||||
|
const verbose = options.verbose;
|
||||||
|
|
||||||
|
// Configure Loggers
|
||||||
|
const GhostLogger = logger.fork(c.bold(c.blue("👻 Astro-GhostCMS")));
|
||||||
|
|
||||||
|
const loggerTagged = (message: string) => {
|
||||||
|
return logger.fork(`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.blue(message)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure ENV Logger
|
||||||
|
const GhostENVLogger = loggerTagged("ENV Check");
|
||||||
|
|
||||||
|
// Configure Integration Loggers & verbose logging
|
||||||
|
const GhostIntegrationLogger = loggerTagged("Integrations");
|
||||||
|
|
||||||
|
// Configure Route Logger & verbose logging
|
||||||
|
const GhostRouteLogger = loggerTagged("Router");
|
||||||
|
|
||||||
|
// Log Info Helper
|
||||||
|
const intLogInfo = (message:string) => {
|
||||||
|
if (verbose) {
|
||||||
|
GhostIntegrationLogger.info(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Log Route Info Helper
|
||||||
|
const routeLogInfo = (message:string) => {
|
||||||
|
if (verbose) {
|
||||||
|
GhostRouteLogger.info(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Local Integration Helper
|
||||||
|
const localIntegration = (enabled: boolean, name: string, integration: AstroIntegration) => {
|
||||||
|
if (enabled) {
|
||||||
|
addIntegration(integration);
|
||||||
|
} else {
|
||||||
|
intLogInfo(c.gray(`${name} integration is disabled`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check External Integration Helper
|
||||||
|
const checkIntegration = (name: string, integration: AstroIntegration) => {
|
||||||
|
if (!hasIntegration(name)) {
|
||||||
|
intLogInfo(c.bold(c.magenta(`Adding ${c.blue(name)} integration`)));
|
||||||
|
addIntegration(integration);
|
||||||
|
} else {
|
||||||
|
intLogInfo(c.gray(`${name} integration already exists, skipping...`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject Route Helper
|
||||||
|
const routeHelper = (routename: string, enabled: boolean, pattern: string, entrypoint: string) => {
|
||||||
|
if (enabled) {
|
||||||
|
routeLogInfo(c.bold(c.cyan(`Setting up ${routename} route`)));
|
||||||
|
injectRoute({
|
||||||
|
pattern: pattern,
|
||||||
|
entrypoint: resolve(`./routes${entrypoint}`),
|
||||||
|
prerender: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
routeLogInfo(c.gray(`${routename} route is disabled, Skipping...`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup Watch Integration for Hot Reload during DEV
|
||||||
|
watchIntegration(resolve());
|
||||||
|
GhostLogger.info("Initializing @matthiesenxyz/astro-ghostcms...");
|
||||||
|
|
||||||
|
|
||||||
|
// Check for GhostCMS environment variables
|
||||||
|
GhostENVLogger.info(
|
||||||
|
c.bold(
|
||||||
|
c.yellow(
|
||||||
|
"Checking for GhostCMS environment variables & user configuration",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check for GhostCMS API Key
|
||||||
|
if (ENV.CONTENT_API_KEY === undefined) {
|
||||||
|
GhostENVLogger.error(
|
||||||
|
c.bgRed(
|
||||||
|
c.bold(
|
||||||
|
c.white("CONTENT_API_KEY is not set in environment variables"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
throw new AstroError(
|
||||||
|
`${name} CONTENT_API_KEY is not set in environment variables`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for GhostCMS URL
|
||||||
|
if (options.ghostURL === undefined) {
|
||||||
|
GhostENVLogger.warn(
|
||||||
|
c.bgYellow(
|
||||||
|
c.bold(
|
||||||
|
c.black(
|
||||||
|
"ghostURL is not set in user configuration falling back to environment variable",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (ENV.CONTENT_API_URL === undefined) {
|
||||||
|
GhostENVLogger.error(
|
||||||
|
c.bgRed(
|
||||||
|
c.bold(
|
||||||
|
c.white(
|
||||||
|
"CONTENT_API_URL is not set in environment variables",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
throw new AstroError(
|
||||||
|
`${name} CONTENT_API_URL is not set in environment variables`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GhostENVLogger.info(
|
||||||
|
c.bold(c.green("GhostCMS environment variables are set")),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up Astro-GhostCMS Integrations
|
||||||
|
GhostIntegrationLogger.info(
|
||||||
|
c.bold(c.magenta("Configuring Enabled Integrations")),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Setup GhostCMS Theme Provider
|
||||||
|
localIntegration(
|
||||||
|
!options.ThemeProvider.disableThemeProvider,
|
||||||
|
"Theme Provider",
|
||||||
|
ghostThemeProvider({
|
||||||
|
theme: options.ThemeProvider.theme,
|
||||||
|
verbose,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Setup GhostCMS OG Image Provider
|
||||||
|
localIntegration(
|
||||||
|
options.enableOGImages,
|
||||||
|
"Satori OG Images",
|
||||||
|
ghostOGImages({ verbose })
|
||||||
|
);
|
||||||
|
|
||||||
|
// Setup GhostCMS RSS Feed Provider
|
||||||
|
localIntegration(
|
||||||
|
options.enableRSSFeed,
|
||||||
|
"RSS Feed",
|
||||||
|
ghostRSS({ verbose })
|
||||||
|
);
|
||||||
|
|
||||||
|
// Setup @astrojs/sitemap Integration
|
||||||
|
checkIntegration(
|
||||||
|
"@astrojs/sitemap",
|
||||||
|
sitemap(options.Integrations.sitemap)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Setup astro-robots-txt Integration
|
||||||
|
checkIntegration(
|
||||||
|
"astro-robots-txt",
|
||||||
|
robotsTxt(options.Integrations.robotsTxt)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Setup Default 404 Page
|
||||||
|
routeHelper(
|
||||||
|
"Default 404 Page",
|
||||||
|
!options.disableDefault404,
|
||||||
|
"/404",
|
||||||
|
"/404.astro"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add virtual imports for user configuration
|
||||||
|
addVirtualImports({
|
||||||
|
"virtual:@matthiesenxyz/astro-ghostcms/config": `export default ${JSON.stringify(
|
||||||
|
options,
|
||||||
|
)}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
"astro:config:done": ({ logger }) => {
|
||||||
|
// Configure Loggers
|
||||||
|
const GhostLogger = logger.fork(
|
||||||
|
`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.green(
|
||||||
|
"CONFIG",
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Log Configuration Complete
|
||||||
|
GhostLogger.info(
|
||||||
|
c.bold(c.green("Integration Setup & Configuration Complete")),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
"astro:server:start": async ({ logger }) => {
|
||||||
|
const loggerTagged = (message: string) => {
|
||||||
|
return logger.fork(`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.green(message)}`)
|
||||||
|
}
|
||||||
|
// Configure Loggers
|
||||||
|
const GhostLogger = loggerTagged("DEV");
|
||||||
|
|
||||||
|
const GhostUpdateLogger = loggerTagged("VERSION CHECK");
|
||||||
|
|
||||||
|
// Start the DEV server
|
||||||
|
GhostLogger.info(
|
||||||
|
c.bold(c.magenta("Running Astro-GhostCMS in Deveopment mode 🚀")),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check for updates
|
||||||
|
|
||||||
|
// Get the latest version of Astro-GhostCMS
|
||||||
|
const currentNPMVersion = await latestVersion(
|
||||||
|
"@matthiesenxyz/astro-ghostcms",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get the local version of Astro-GhostCMS
|
||||||
|
const packageJson = await fse.readJson(resolve("../package.json"));
|
||||||
|
const localVersion = packageJson.version;
|
||||||
|
|
||||||
|
// Log the version check
|
||||||
|
if (currentNPMVersion !== localVersion) {
|
||||||
|
GhostUpdateLogger.warn(
|
||||||
|
`\n${c.bgYellow(
|
||||||
|
c.bold(
|
||||||
|
c.black(
|
||||||
|
" There is a new version of Astro-GhostCMS available! ",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)}\n${
|
||||||
|
c.bold(c.white(" Current Installed Version: ")) +
|
||||||
|
c.bold(c.red(`${localVersion} `))
|
||||||
|
} \n ${c.bold(c.white("New Available Version: "))} ${c.green(
|
||||||
|
currentNPMVersion,
|
||||||
|
)} \n ${c.bold(
|
||||||
|
c.white(
|
||||||
|
"Please consider updating to the latest version by running: ",
|
||||||
|
),
|
||||||
|
)} ${c.bold(
|
||||||
|
c.green("npm i @matthiesenxyz/astro-ghostcms@latest"),
|
||||||
|
)} \n`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
GhostUpdateLogger.info(
|
||||||
|
c.bold(c.green(`Astro-GhostCMS is up to date! v${localVersion}`)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"astro:build:done": ({ logger }) => {
|
||||||
|
// Configure Loggers
|
||||||
|
const GhostLogger = logger.fork(
|
||||||
|
`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.bold(
|
||||||
|
c.green("BUILD"),
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Log Build Complete
|
||||||
|
GhostLogger.info(
|
||||||
|
c.bold(c.magenta("Running Astro-GhostCMS in Production mode 🚀")),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
import astroGhostCMS from "./astro-ghostcms";
|
||||||
|
|
||||||
|
export default astroGhostCMS;
|
|
@ -1,28 +0,0 @@
|
||||||
import robotsTxt, { type RobotsTxtOptions } from "astro-robots-txt";
|
|
||||||
import type { UserConfig } from "../schemas";
|
|
||||||
|
|
||||||
export function getRobotsTxtConfig(
|
|
||||||
opts: UserConfig["robotstxt"],
|
|
||||||
): RobotsTxtOptions {
|
|
||||||
const robotsConfig: RobotsTxtOptions = {};
|
|
||||||
if (opts?.host) {
|
|
||||||
robotsConfig.host = opts.host;
|
|
||||||
}
|
|
||||||
if (opts?.policy) {
|
|
||||||
robotsConfig.policy = opts.policy;
|
|
||||||
}
|
|
||||||
if (opts?.sitemap) {
|
|
||||||
robotsConfig.sitemap = opts.sitemap;
|
|
||||||
}
|
|
||||||
if (opts?.sitemapBaseFileName) {
|
|
||||||
robotsConfig.sitemapBaseFileName = opts.sitemapBaseFileName;
|
|
||||||
}
|
|
||||||
return robotsConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A wrapped version of the `astro-robots-txt` integration for GhostCMS.
|
|
||||||
*/
|
|
||||||
export default function ghostRobots(opts: UserConfig["robotstxt"]) {
|
|
||||||
return robotsTxt(getRobotsTxtConfig(opts));
|
|
||||||
}
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { createResolver, defineIntegration } from "astro-integration-kit";
|
||||||
|
import { corePlugins } from "astro-integration-kit/plugins";
|
||||||
|
import { z } from "astro/zod";
|
||||||
|
import c from "picocolors";
|
||||||
|
|
||||||
|
export default defineIntegration({
|
||||||
|
name: "@matthiesenxyz/astro-ghostcms-rss",
|
||||||
|
optionsSchema: z.object({
|
||||||
|
verbose: z.coerce.boolean().optional(),
|
||||||
|
}),
|
||||||
|
plugins: [...corePlugins],
|
||||||
|
setup({ options }) {
|
||||||
|
const { resolve } = createResolver(import.meta.url);
|
||||||
|
|
||||||
|
return {
|
||||||
|
"astro:config:setup": ({ watchIntegration, injectRoute, logger }) => {
|
||||||
|
watchIntegration(resolve());
|
||||||
|
const RSSLogger = logger.fork(
|
||||||
|
`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.blue(
|
||||||
|
"RSSGenerator",
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
RSSLogger.info(c.bold(c.magenta("RSS Feed Enabled. Setting up...")));
|
||||||
|
|
||||||
|
injectRoute({
|
||||||
|
pattern: "/rss-style.xsl",
|
||||||
|
entrypoint: resolve("./routes/rss-style.xsl.ts"),
|
||||||
|
});
|
||||||
|
|
||||||
|
injectRoute({
|
||||||
|
pattern: "/rss.xml",
|
||||||
|
entrypoint: resolve("./routes/rss.xml.ts"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
"astro:config:done": ({ logger }) => {
|
||||||
|
const RSSLogger = logger.fork(
|
||||||
|
`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.blue(
|
||||||
|
"RSSGenerator",
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
const verboseLogsInfo = (message:string) => {
|
||||||
|
if (options.verbose) {
|
||||||
|
RSSLogger.info(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
verboseLogsInfo(c.bold(c.green("RSS Feed Setup Complete")));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
import astroGhostcmsRSS from "./astro-ghostcms-rss";
|
||||||
|
|
||||||
|
export default astroGhostcmsRSS;
|
|
@ -1,6 +1,6 @@
|
||||||
import rss from "@astrojs/rss";
|
import rss from "@astrojs/rss";
|
||||||
import type { APIContext } from "astro";
|
import type { APIContext } from "astro";
|
||||||
import { getAllPosts, getSettings, invariant } from "../api";
|
import { getAllPosts, getSettings, invariant } from "../../../api";
|
||||||
|
|
||||||
const posts = await getAllPosts();
|
const posts = await getAllPosts();
|
||||||
const settings = await getSettings();
|
const settings = await getSettings();
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { createResolver, defineIntegration } from "astro-integration-kit";
|
||||||
|
import { corePlugins } from "astro-integration-kit/plugins";
|
||||||
|
import { z } from "astro/zod";
|
||||||
|
import c from "picocolors";
|
||||||
|
|
||||||
|
export default defineIntegration({
|
||||||
|
name: "@matthiesenxyz/astro-ghostcms-satoriog",
|
||||||
|
optionsSchema: z.object({
|
||||||
|
verbose: z.coerce.boolean().optional(),
|
||||||
|
}),
|
||||||
|
plugins: [...corePlugins],
|
||||||
|
setup({ options }) {
|
||||||
|
const { resolve } = createResolver(import.meta.url);
|
||||||
|
|
||||||
|
return {
|
||||||
|
"astro:config:setup": ({
|
||||||
|
watchIntegration,
|
||||||
|
updateConfig,
|
||||||
|
injectRoute,
|
||||||
|
logger,
|
||||||
|
}) => {
|
||||||
|
watchIntegration(resolve());
|
||||||
|
|
||||||
|
const SatoriLogger = logger.fork(
|
||||||
|
`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.blue(
|
||||||
|
"SatoriOG",
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
SatoriLogger.info(
|
||||||
|
c.bold(c.magenta("OG Image Integration Enabled. Setting up...")),
|
||||||
|
);
|
||||||
|
|
||||||
|
injectRoute({
|
||||||
|
pattern: "/open-graph/[slug].png",
|
||||||
|
entrypoint: resolve("./routes/[slug].png.ts"),
|
||||||
|
});
|
||||||
|
|
||||||
|
injectRoute({
|
||||||
|
pattern: "/open-graph/index.png",
|
||||||
|
entrypoint: resolve("./routes/index.png.ts"),
|
||||||
|
});
|
||||||
|
|
||||||
|
injectRoute({
|
||||||
|
pattern: "/open-graph/authors.png",
|
||||||
|
entrypoint: resolve("./routes/authors.png.ts"),
|
||||||
|
});
|
||||||
|
|
||||||
|
injectRoute({
|
||||||
|
pattern: "/open-graph/author/[slug].png",
|
||||||
|
entrypoint: resolve("./routes/author/[slug].png.ts"),
|
||||||
|
});
|
||||||
|
|
||||||
|
injectRoute({
|
||||||
|
pattern: "/open-graph/tags.png",
|
||||||
|
entrypoint: resolve("./routes/tags.png.ts"),
|
||||||
|
});
|
||||||
|
|
||||||
|
injectRoute({
|
||||||
|
pattern: "/open-graph/tag/[slug].png",
|
||||||
|
entrypoint: resolve("./routes/tag/[slug].png.ts"),
|
||||||
|
});
|
||||||
|
|
||||||
|
updateConfig({
|
||||||
|
vite: { optimizeDeps: { exclude: ["@resvg/resvg-js"] } },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
"astro:config:done": ({ logger }) => {
|
||||||
|
const SatoriLogger = logger.fork(
|
||||||
|
`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.blue(
|
||||||
|
"SatoriOG",
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
const verboseLogsInfo = (message:string) => {
|
||||||
|
if (options.verbose) {
|
||||||
|
SatoriLogger.info(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
verboseLogsInfo(c.bold(c.green("OG Image Integration Setup Complete")));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
import astroGhostcmsSatoriog from "./astro-ghostcms-satoriog";
|
||||||
|
|
||||||
|
export default astroGhostcmsSatoriog;
|
|
@ -5,13 +5,8 @@ import type {
|
||||||
InferGetStaticPropsType,
|
InferGetStaticPropsType,
|
||||||
} from "astro";
|
} from "astro";
|
||||||
import { html } from "satori-html";
|
import { html } from "satori-html";
|
||||||
import {
|
import { getAllPages, getAllPosts, getSettings, invariant } from "../../../api";
|
||||||
getAllPages,
|
import satoriOG from "../satori";
|
||||||
getAllPosts,
|
|
||||||
getSettings,
|
|
||||||
invariant,
|
|
||||||
} from "../../api/index.js";
|
|
||||||
import satoriOG from "../../integrations/satori.js";
|
|
||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths = async () => {
|
export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
const result: GetStaticPathsItem[] = [];
|
const result: GetStaticPathsItem[] = [];
|
|
@ -10,8 +10,8 @@ import {
|
||||||
getAllPosts,
|
getAllPosts,
|
||||||
getSettings,
|
getSettings,
|
||||||
invariant,
|
invariant,
|
||||||
} from "../../../api/index.js";
|
} from "../../../../api";
|
||||||
import satoriOG from "../../../integrations/satori.js";
|
import satoriOG from "../../satori";
|
||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths = async () => {
|
export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
const result: GetStaticPathsItem[] = [];
|
const result: GetStaticPathsItem[] = [];
|
|
@ -5,13 +5,8 @@ import type {
|
||||||
InferGetStaticPropsType,
|
InferGetStaticPropsType,
|
||||||
} from "astro";
|
} from "astro";
|
||||||
import { html } from "satori-html";
|
import { html } from "satori-html";
|
||||||
import {
|
import { getAllPages, getAllPosts, getSettings, invariant } from "../../../api";
|
||||||
getAllPages,
|
import satoriOG from "../satori";
|
||||||
getAllPosts,
|
|
||||||
getSettings,
|
|
||||||
invariant,
|
|
||||||
} from "../../api/index.js";
|
|
||||||
import satoriOG from "../../integrations/satori.js";
|
|
||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths = async () => {
|
export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
const result: GetStaticPathsItem[] = [];
|
const result: GetStaticPathsItem[] = [];
|
|
@ -5,13 +5,8 @@ import type {
|
||||||
InferGetStaticPropsType,
|
InferGetStaticPropsType,
|
||||||
} from "astro";
|
} from "astro";
|
||||||
import { html } from "satori-html";
|
import { html } from "satori-html";
|
||||||
import {
|
import { getAllPages, getAllPosts, getSettings, invariant } from "../../../api";
|
||||||
getAllPages,
|
import satoriOG from "../satori";
|
||||||
getAllPosts,
|
|
||||||
getSettings,
|
|
||||||
invariant,
|
|
||||||
} from "../../api/index.js";
|
|
||||||
import satoriOG from "../../integrations/satori.js";
|
|
||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths = async () => {
|
export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
const result: GetStaticPathsItem[] = [];
|
const result: GetStaticPathsItem[] = [];
|
|
@ -10,8 +10,8 @@ import {
|
||||||
getAllTags,
|
getAllTags,
|
||||||
getSettings,
|
getSettings,
|
||||||
invariant,
|
invariant,
|
||||||
} from "../../../api/index.js";
|
} from "../../../../api";
|
||||||
import satoriOG from "../../../integrations/satori.js";
|
import satoriOG from "../../satori";
|
||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths = async () => {
|
export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
const result: GetStaticPathsItem[] = [];
|
const result: GetStaticPathsItem[] = [];
|
|
@ -5,13 +5,8 @@ import type {
|
||||||
InferGetStaticPropsType,
|
InferGetStaticPropsType,
|
||||||
} from "astro";
|
} from "astro";
|
||||||
import { html } from "satori-html";
|
import { html } from "satori-html";
|
||||||
import {
|
import { getAllPages, getAllPosts, getSettings, invariant } from "../../../api";
|
||||||
getAllPages,
|
import satoriOG from "../satori";
|
||||||
getAllPosts,
|
|
||||||
getSettings,
|
|
||||||
invariant,
|
|
||||||
} from "../../api/index.js";
|
|
||||||
import satoriOG from "../../integrations/satori.js";
|
|
||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths = async () => {
|
export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
const result: GetStaticPathsItem[] = [];
|
const result: GetStaticPathsItem[] = [];
|
|
@ -5,7 +5,7 @@ import type {
|
||||||
ToImageOptions,
|
ToImageOptions,
|
||||||
ToResponseOptions,
|
ToResponseOptions,
|
||||||
ToSvgOptions,
|
ToSvgOptions,
|
||||||
} from "../../types.js";
|
} from "./types";
|
||||||
|
|
||||||
const satoriOG = ({ width, height, template }: SatoriAstroOGOptions) => {
|
const satoriOG = ({ width, height, template }: SatoriAstroOGOptions) => {
|
||||||
return {
|
return {
|
|
@ -1,24 +1,5 @@
|
||||||
import type { Resvg } from "@resvg/resvg-js";
|
import type { Resvg } from "@resvg/resvg-js";
|
||||||
import type satori from "satori";
|
import type satori from "satori";
|
||||||
|
|
||||||
export type { UserConfig, GhostUserConfig } from "./src/schemas";
|
|
||||||
|
|
||||||
export type {
|
|
||||||
Author,
|
|
||||||
AuthorsIncludeSchema,
|
|
||||||
Page,
|
|
||||||
PagesIncludeSchema,
|
|
||||||
Post,
|
|
||||||
PostsIncludeSchema,
|
|
||||||
Settings,
|
|
||||||
Tag,
|
|
||||||
TagsIncludeSchema,
|
|
||||||
Tier,
|
|
||||||
TiersIncludeSchema,
|
|
||||||
} from "./src/api/index.ts";
|
|
||||||
|
|
||||||
export type { ContentAPICredentials, APIVersions } from "@ts-ghost/core-api";
|
|
||||||
|
|
||||||
type SatoriParameters = Parameters<typeof satori>;
|
type SatoriParameters = Parameters<typeof satori>;
|
||||||
type SatoriOptions = SatoriParameters[1];
|
type SatoriOptions = SatoriParameters[1];
|
||||||
type ResvgOptions = NonNullable<ConstructorParameters<typeof Resvg>[1]>;
|
type ResvgOptions = NonNullable<ConstructorParameters<typeof Resvg>[1]>;
|
|
@ -1,38 +0,0 @@
|
||||||
import sitemap, { type SitemapOptions } from "@astrojs/sitemap";
|
|
||||||
import type { UserConfig } from "../schemas";
|
|
||||||
|
|
||||||
export function getSitemapConfig(opts: UserConfig["sitemap"]): SitemapOptions {
|
|
||||||
const sitemapConfig: SitemapOptions = {};
|
|
||||||
if (opts?.filter) {
|
|
||||||
sitemapConfig.filter = opts.filter;
|
|
||||||
}
|
|
||||||
if (opts?.changefreq) {
|
|
||||||
sitemapConfig.changefreq = opts.changefreq;
|
|
||||||
}
|
|
||||||
if (opts?.entryLimit) {
|
|
||||||
sitemapConfig.entryLimit = opts.entryLimit;
|
|
||||||
}
|
|
||||||
if (opts?.customPages) {
|
|
||||||
sitemapConfig.customPages = opts.customPages;
|
|
||||||
}
|
|
||||||
if (opts?.i18n) {
|
|
||||||
sitemapConfig.i18n = opts.i18n;
|
|
||||||
}
|
|
||||||
if (opts?.lastmod) {
|
|
||||||
sitemapConfig.lastmod = opts.lastmod;
|
|
||||||
}
|
|
||||||
if (opts?.priority) {
|
|
||||||
sitemapConfig.priority = opts.priority;
|
|
||||||
}
|
|
||||||
if (opts?.serialize) {
|
|
||||||
sitemapConfig.serialize = opts.serialize;
|
|
||||||
}
|
|
||||||
return sitemapConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A wrapped version of the `@astrojs/sitemap` integration for GhostCMS.
|
|
||||||
*/
|
|
||||||
export default function ghostSitemap(opts: UserConfig["sitemap"]) {
|
|
||||||
return sitemap(getSitemapConfig(opts));
|
|
||||||
}
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
import { createResolver, defineIntegration } from "astro-integration-kit";
|
||||||
|
import { corePlugins } from "astro-integration-kit/plugins";
|
||||||
|
import { z } from "astro/zod";
|
||||||
|
import c from "picocolors";
|
||||||
|
|
||||||
|
export default defineIntegration({
|
||||||
|
name: "@matthiesenxyz/astro-ghostcms-themeprovider",
|
||||||
|
optionsSchema: z.object({
|
||||||
|
theme: z.string(),
|
||||||
|
verbose: z.coerce.boolean().optional(),
|
||||||
|
}),
|
||||||
|
plugins: [...corePlugins],
|
||||||
|
setup({ options }) {
|
||||||
|
const { resolve } = createResolver(import.meta.url);
|
||||||
|
|
||||||
|
const DEFAULT_THEME = "@matthiesenxyz/astro-ghostcms-theme-default";
|
||||||
|
|
||||||
|
return {
|
||||||
|
"astro:config:setup": ({ watchIntegration, injectRoute, logger }) => {
|
||||||
|
watchIntegration(resolve());
|
||||||
|
|
||||||
|
const themeLogger = logger.fork(
|
||||||
|
`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.blue(
|
||||||
|
"Theme Provider",
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
const verboseLogsInfo = (message:string) => {
|
||||||
|
if (options.verbose) {
|
||||||
|
themeLogger.info(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
themeLogger.info(
|
||||||
|
c.bold(c.magenta("Theme Provider enabled. Setting up...")),
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
if (options.theme === DEFAULT_THEME) {
|
||||||
|
verboseLogsInfo(
|
||||||
|
c.blue("No theme is set, injecting default theme"),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
verboseLogsInfo(`${c.bold(c.cyan("Injecting Theme:"))} ${c.bold(c.underline(c.magenta(options.theme)))}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
injectRoute({
|
||||||
|
pattern: "/",
|
||||||
|
entrypoint: `${options.theme}/index.astro`,
|
||||||
|
});
|
||||||
|
|
||||||
|
injectRoute({
|
||||||
|
pattern: "/[slug]",
|
||||||
|
entrypoint: `${options.theme}/[slug].astro`,
|
||||||
|
});
|
||||||
|
|
||||||
|
injectRoute({
|
||||||
|
pattern: "/tags",
|
||||||
|
entrypoint: `${options.theme}/tags.astro`,
|
||||||
|
});
|
||||||
|
|
||||||
|
injectRoute({
|
||||||
|
pattern: "/authors",
|
||||||
|
entrypoint: `${options.theme}/authors.astro`,
|
||||||
|
});
|
||||||
|
|
||||||
|
injectRoute({
|
||||||
|
pattern: "/tag/[slug]",
|
||||||
|
entrypoint: `${options.theme}/tag/[slug].astro`,
|
||||||
|
});
|
||||||
|
|
||||||
|
injectRoute({
|
||||||
|
pattern: "/author/[slug]",
|
||||||
|
entrypoint: `${options.theme}/author/[slug].astro`,
|
||||||
|
});
|
||||||
|
|
||||||
|
injectRoute({
|
||||||
|
pattern: "/archives/[...page]",
|
||||||
|
entrypoint: `${options.theme}/archives/[...page].astro`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
"astro:config:done": ({ logger }) => {
|
||||||
|
const themeLogger = logger.fork(
|
||||||
|
`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.blue(
|
||||||
|
"Theme Provider",
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
const verboseLogsInfo = (message:string) => {
|
||||||
|
if (options.verbose) {
|
||||||
|
themeLogger.info(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
verboseLogsInfo(c.bold(c.green("Provider Setup Complete")));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
import astroGhostcmsThemeProvider from "./astro-ghostcms-themeprovider";
|
||||||
|
|
||||||
|
export default astroGhostcmsThemeProvider;
|
|
@ -1,6 +0,0 @@
|
||||||
import type { GhostUserConfig } from "../schemas";
|
|
||||||
import config from "virtual:@matthiesenxyz/astro-ghostcms/config";
|
|
||||||
|
|
||||||
const UserConfig = config as GhostUserConfig;
|
|
||||||
|
|
||||||
export default UserConfig;
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
import './404.css';
|
import './404.css';
|
||||||
import { getSettings, invariant } from '../../api';
|
import { getSettings, invariant } from '../api';
|
||||||
const settings = await getSettings();
|
const settings = await getSettings();
|
||||||
invariant(settings, "Settings not found");
|
invariant(settings, "Settings not found");
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||||
import createFetchMock from "vitest-fetch-mock";
|
import createFetchMock from "vitest-fetch-mock";
|
||||||
|
|
||||||
import TS_API from "../../content-api";
|
import { TSGhostContentAPI } from "@ts-ghost/content-api";
|
||||||
|
|
||||||
const fetchMocker = createFetchMock(vi);
|
const fetchMocker = createFetchMock(vi);
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ describe("authors api .browse() Args Type-safety", () => {
|
||||||
const url = process.env.VITE_GHOST_URL || "https://my-ghost-blog.com";
|
const url = process.env.VITE_GHOST_URL || "https://my-ghost-blog.com";
|
||||||
const key =
|
const key =
|
||||||
process.env.VITE_GHOST_CONTENT_API_KEY || "59d4bf56c73c04a18c867dc3ba";
|
process.env.VITE_GHOST_CONTENT_API_KEY || "59d4bf56c73c04a18c867dc3ba";
|
||||||
const api = new TS_API(url, key, "v5.0");
|
const api = new TSGhostContentAPI(url, key, "v5.0");
|
||||||
test(".browse() params shouldnt accept invalid params", () => {
|
test(".browse() params shouldnt accept invalid params", () => {
|
||||||
// @ts-expect-error - shouldnt accept invalid params
|
// @ts-expect-error - shouldnt accept invalid params
|
||||||
const browse = api.authors.browse({ pp: 2 });
|
const browse = api.authors.browse({ pp: 2 });
|
||||||
|
@ -95,10 +95,10 @@ describe("authors api .browse() Args Type-safety", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("authors resource mocked", () => {
|
describe("authors resource mocked", () => {
|
||||||
let api: TS_API;
|
let api: TSGhostContentAPI;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
api = new TS_API(
|
api = new TSGhostContentAPI(
|
||||||
"https://my-ghost-blog.com",
|
"https://my-ghost-blog.com",
|
||||||
"59d4bf56c73c04a18c867dc3ba",
|
"59d4bf56c73c04a18c867dc3ba",
|
||||||
"v5.0",
|
"v5.0",
|
|
@ -1,7 +1,7 @@
|
||||||
export * from "./authors";
|
export * from "./authors";
|
||||||
export * from "./helpers";
|
|
||||||
export * from "./pages";
|
export * from "./pages";
|
||||||
export * from "./posts";
|
export * from "./posts";
|
||||||
export * from "./settings";
|
export * from "./settings";
|
||||||
|
export * from "./socials";
|
||||||
export * from "./tags";
|
export * from "./tags";
|
||||||
export * from "./tiers";
|
export * from "./tiers";
|
|
@ -7,8 +7,8 @@ import {
|
||||||
} from "@ts-ghost/core-api";
|
} from "@ts-ghost/core-api";
|
||||||
import { z } from "astro/zod";
|
import { z } from "astro/zod";
|
||||||
|
|
||||||
import { authorsSchema } from "../authors";
|
import { authorsSchema } from "./authors";
|
||||||
import { tagsSchema } from "../tags";
|
import { tagsSchema } from "./tags";
|
||||||
|
|
||||||
const postsAuthorSchema = authorsSchema.extend({
|
const postsAuthorSchema = authorsSchema.extend({
|
||||||
url: z.string().nullish(),
|
url: z.string().nullish(),
|
|
@ -1,14 +1,14 @@
|
||||||
import { describe, expect, test } from "vitest";
|
import { describe, expect, test } from "vitest";
|
||||||
|
|
||||||
import TS_API from "../../content-api";
|
import { TSGhostContentAPI } from "@ts-ghost/content-api";
|
||||||
import type { Post } from "./posts";
|
import type { Post } from "./index";
|
||||||
|
|
||||||
const url = process.env.VITE_GHOST_URL || "https://my-ghost-blog.com";
|
const url = process.env.VITE_GHOST_URL || "https://my-ghost-blog.com";
|
||||||
const key =
|
const key =
|
||||||
process.env.VITE_GHOST_CONTENT_API_KEY || "59d4bf56c73c04a18c867dc3ba";
|
process.env.VITE_GHOST_CONTENT_API_KEY || "59d4bf56c73c04a18c867dc3ba";
|
||||||
|
|
||||||
describe("posts api .browse() Args Type-safety", () => {
|
describe("posts api .browse() Args Type-safety", () => {
|
||||||
const api = new TS_API(url, key, "v5.0");
|
const api = new TSGhostContentAPI(url, key, "v5.0");
|
||||||
test(".browse() params shouldnt accept invalid params", () => {
|
test(".browse() params shouldnt accept invalid params", () => {
|
||||||
// @ts-expect-error - shouldnt accept invalid params
|
// @ts-expect-error - shouldnt accept invalid params
|
||||||
const browse = api.posts.browse({ pp: 2 });
|
const browse = api.posts.browse({ pp: 2 });
|
||||||
|
@ -21,8 +21,7 @@ describe("posts api .browse() Args Type-safety", () => {
|
||||||
foo: true,
|
foo: true,
|
||||||
} satisfies { [k in keyof Post]?: true | undefined };
|
} satisfies { [k in keyof Post]?: true | undefined };
|
||||||
|
|
||||||
// biome-ignore lint/style/useConst: <explanation>
|
const test = api.posts
|
||||||
let test = api.posts
|
|
||||||
.browse()
|
.browse()
|
||||||
// @ts-expect-error - shouldnt accept invalid params
|
// @ts-expect-error - shouldnt accept invalid params
|
||||||
.fields(outputFields);
|
.fields(outputFields);
|
||||||
|
@ -45,8 +44,7 @@ describe("posts api .browse() Args Type-safety", () => {
|
||||||
title: true,
|
title: true,
|
||||||
} satisfies { [k in keyof Post]?: true | undefined };
|
} satisfies { [k in keyof Post]?: true | undefined };
|
||||||
|
|
||||||
// biome-ignore lint/style/useConst: <explanation>
|
const test = api.posts.browse().fields(outputFields);
|
||||||
let test = api.posts.browse().fields(outputFields);
|
|
||||||
expect(test.getOutputFields()).toEqual(["slug", "title"]);
|
expect(test.getOutputFields()).toEqual(["slug", "title"]);
|
||||||
|
|
||||||
// @ts-expect-error - shouldnt accept invalid params
|
// @ts-expect-error - shouldnt accept invalid params
|
|
@ -7,8 +7,8 @@ import {
|
||||||
} from "@ts-ghost/core-api";
|
} from "@ts-ghost/core-api";
|
||||||
import { z } from "astro/zod";
|
import { z } from "astro/zod";
|
||||||
|
|
||||||
import { authorsSchema } from "../authors";
|
import { authorsSchema } from "./authors";
|
||||||
import { tagsSchema } from "../tags";
|
import { tagsSchema } from "./tags";
|
||||||
|
|
||||||
const postsAuthorSchema = authorsSchema.extend({
|
const postsAuthorSchema = authorsSchema.extend({
|
||||||
url: z.string().nullish(),
|
url: z.string().nullish(),
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { beforeEach, describe, expect, test } from "vitest";
|
||||||
|
|
||||||
|
import { TSGhostContentAPI } from "@ts-ghost/content-api";
|
||||||
|
|
||||||
|
const url = process.env.VITE_GHOST_URL || "https://my-ghost-blog.com";
|
||||||
|
const key =
|
||||||
|
process.env.VITE_GHOST_CONTENT_API_KEY || "59d4bf56c73c04a18c867dc3ba";
|
||||||
|
|
||||||
|
describe("settings integration tests browse", () => {
|
||||||
|
let api: TSGhostContentAPI;
|
||||||
|
beforeEach(() => {
|
||||||
|
api = new TSGhostContentAPI(url, key, "v5.0");
|
||||||
|
});
|
||||||
|
test("settings.fetch()", async () => {
|
||||||
|
const result = await api.settings.fetch();
|
||||||
|
expect(result).not.toBeUndefined();
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
if (!result.success) {
|
||||||
|
expect(result.errors).toBeDefined();
|
||||||
|
expect(result.errors).toHaveLength(1);
|
||||||
|
} else {
|
||||||
|
expect(result.data).toBeDefined();
|
||||||
|
const settings = result.data;
|
||||||
|
expect(settings).toBeDefined();
|
||||||
|
expect(settings.title).toBe("Astro Starter");
|
||||||
|
expect(settings.description).toBe("Thoughts, stories and ideas.");
|
||||||
|
expect(settings.logo).toBeNull();
|
||||||
|
expect(settings.cover_image).toBe(
|
||||||
|
"https://static.ghost.org/v4.0.0/images/publication-cover.jpg",
|
||||||
|
);
|
||||||
|
expect(settings.icon).toBeNull();
|
||||||
|
expect(settings.lang).toBe("en");
|
||||||
|
expect(settings.timezone).toBe("Etc/UTC");
|
||||||
|
expect(settings.codeinjection_head).toBeNull();
|
||||||
|
expect(settings.codeinjection_foot).toBeNull();
|
||||||
|
expect(settings.members_support_address).toBe("noreply");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,3 +0,0 @@
|
||||||
export * from "./robots";
|
|
||||||
export * from "./sitemap";
|
|
||||||
export * from "./userconfig";
|
|
|
@ -1,37 +0,0 @@
|
||||||
import { z } from "astro/zod";
|
|
||||||
|
|
||||||
const RobotsPolicySchema = z.object({
|
|
||||||
/** You must provide a name of the automatic client (search engine crawler).
|
|
||||||
* Wildcards are allowed.
|
|
||||||
*/
|
|
||||||
userAgent: z.string(),
|
|
||||||
/** Allowed paths for crawling */
|
|
||||||
allow: z.string().optional(),
|
|
||||||
/** Disallowed paths for crawling */
|
|
||||||
disallow: z.string().optional(),
|
|
||||||
/** Indicates that the page's URL contains parameters that should be ignored during crawling.
|
|
||||||
* Maximum string length is limited to 500.
|
|
||||||
*/
|
|
||||||
cleanParam: z.string().optional(),
|
|
||||||
/** Minimum interval (in secs) for the crawler to wait after loading one page, before starting other */
|
|
||||||
crawlDelay: z.number().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const RobotsTxtSchema = z.object({
|
|
||||||
/** @example host: true
|
|
||||||
* // Automatically resolve using the site option from Astro config
|
|
||||||
* @example host: 'example.com'
|
|
||||||
*/
|
|
||||||
host: z.string().optional(),
|
|
||||||
/** @example sitemap: "https://example.com/sitemap-0.xml"
|
|
||||||
* @example sitemap: ['https://example.com/sitemap-0.xml','https://example.com/sitemap-1.xml']
|
|
||||||
* @example sitemap: false - If you want to get the robots.txt file without the Sitemap: ... entry, set the sitemap parameter to false.
|
|
||||||
*/
|
|
||||||
sitemap: z.union([z.string(), z.string().array(), z.boolean()]).optional(),
|
|
||||||
/** astrojs/sitemap and astro-robots-txt integrations have the sitemap-index.xml as their primary output. That is why the default value of sitemapBaseFileName is set to sitemap-index.
|
|
||||||
* @example sitemapBaseFileName: 'custom-sitemap'
|
|
||||||
*/
|
|
||||||
sitemapBaseFileName: z.string().optional(),
|
|
||||||
/** SET POLICY RULES */
|
|
||||||
policy: RobotsPolicySchema.array().optional(),
|
|
||||||
});
|
|
|
@ -1,38 +0,0 @@
|
||||||
import { z } from "astro/zod";
|
|
||||||
|
|
||||||
const localeKeySchema = z.string().min(1);
|
|
||||||
enum EnumChangefreq {
|
|
||||||
DAILY = "daily",
|
|
||||||
MONTHLY = "monthly",
|
|
||||||
ALWAYS = "always",
|
|
||||||
HOURLY = "hourly",
|
|
||||||
WEEKLY = "weekly",
|
|
||||||
YEARLY = "yearly",
|
|
||||||
NEVER = "never",
|
|
||||||
}
|
|
||||||
export const SitemapSchema = z.object({
|
|
||||||
filter: z.function().args(z.string()).returns(z.boolean()).optional(),
|
|
||||||
customPages: z.string().url().array().optional(),
|
|
||||||
i18n: z
|
|
||||||
.object({
|
|
||||||
defaultLocale: localeKeySchema,
|
|
||||||
locales: z.record(
|
|
||||||
localeKeySchema,
|
|
||||||
z
|
|
||||||
.string()
|
|
||||||
.min(2)
|
|
||||||
.regex(/^[a-zA-Z\-]+$/gm, {
|
|
||||||
message: "Only English alphabet symbols and hyphen allowed",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
.refine((val) => !val || val.locales[val.defaultLocale], {
|
|
||||||
message: "`defaultLocale` must exist in `locales` keys",
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
entryLimit: z.number().nonnegative().optional(),
|
|
||||||
serialize: z.function().args(z.any()).returns(z.any()).optional(),
|
|
||||||
changefreq: z.nativeEnum(EnumChangefreq).optional(),
|
|
||||||
lastmod: z.date().optional(),
|
|
||||||
priority: z.number().min(0).max(1).optional(),
|
|
||||||
});
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { GhostUserConfigSchema } from "./userconfig";
|
||||||
|
|
||||||
|
describe("GhostUserConfigSchema", () => {
|
||||||
|
it("should validate a valid user config", () => {
|
||||||
|
const validConfig = {
|
||||||
|
ghostURL: "https://ghostdemo.matthiesen.xyz",
|
||||||
|
ThemeProvider: {
|
||||||
|
disableThemeProvider: true,
|
||||||
|
theme: "@matthiesenxyz/astro-ghostcms-theme-default",
|
||||||
|
},
|
||||||
|
disableDefault404: false,
|
||||||
|
enableRSSFeed: true,
|
||||||
|
enableOGImages: true,
|
||||||
|
verbose: false,
|
||||||
|
Integrations: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = GhostUserConfigSchema.safeParse(validConfig);
|
||||||
|
|
||||||
|
expect(result.success).to.be.true;
|
||||||
|
// @ts-expect-error
|
||||||
|
expect(result.data).to.deep.equal(validConfig);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should invalidate an invalid user config", () => {
|
||||||
|
const invalidConfig = {
|
||||||
|
ghostURL: "invalid-url",
|
||||||
|
ThemeProvider: {
|
||||||
|
disableThemeProvider: "true",
|
||||||
|
theme: 123,
|
||||||
|
},
|
||||||
|
disableDefault404: "false",
|
||||||
|
enableRSSFeed: "true",
|
||||||
|
enableOGImages: "true",
|
||||||
|
Integrations: {
|
||||||
|
sitemap: {
|
||||||
|
// invalid sitemap configuration
|
||||||
|
},
|
||||||
|
robotstxt: {
|
||||||
|
// invalid robotstxt configuration
|
||||||
|
},
|
||||||
|
},
|
||||||
|
verbose: "false",
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = GhostUserConfigSchema.safeParse(invalidConfig);
|
||||||
|
|
||||||
|
expect(result.success).to.be.false;
|
||||||
|
expect(!result.success).to.exist;
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,7 +1,8 @@
|
||||||
|
import type { SitemapOptions } from "@astrojs/sitemap";
|
||||||
|
import type { RobotsTxtOptions } from "astro-robots-txt";
|
||||||
import { z } from "astro/zod";
|
import { z } from "astro/zod";
|
||||||
import { RobotsTxtSchema, SitemapSchema } from "./index";
|
|
||||||
|
|
||||||
export const UserConfigSchema = z.object({
|
export const GhostUserConfigSchema = z.object({
|
||||||
/** OPTIONAL - Either set the URL in your .env or put it here
|
/** OPTIONAL - Either set the URL in your .env or put it here
|
||||||
* @example
|
* @example
|
||||||
* // https://astro.build/config
|
* // https://astro.build/config
|
||||||
|
@ -12,46 +13,59 @@ export const UserConfigSchema = z.object({
|
||||||
* })
|
* })
|
||||||
* ],
|
* ],
|
||||||
* }); */
|
* }); */
|
||||||
ghostURL: z.string().url().optional(),
|
ghostURL: z.coerce.string().url("Must be a URL").optional(),
|
||||||
/** OPTIONAL - Allows the user to disable the `/rss.xml` injection */
|
/** OPTIONAL - Configure the Theme Provider
|
||||||
disableRSS: z.boolean().default(false),
|
* @ This option allows the user to configure the Theme Provider
|
||||||
/** OPTIONAL - Allows the user to disable the `/open-graph/*` route injection
|
|
||||||
* @ This feature uses `satori` to generate OpenGraph Images
|
|
||||||
*/
|
*/
|
||||||
disableSatoriOG: z.boolean().default(false),
|
ThemeProvider: z
|
||||||
/** OPTIONAL - Allows the user to disable the `/404` injection */
|
.object({
|
||||||
disable404: z.boolean().default(false),
|
/** OPTIONAL - Disable the theme provider
|
||||||
/** OPTIONAL - Disable Route Injector
|
* @default false
|
||||||
* This option allows the user to disable the route injection system and utilize just the integraions other functions. Such as API, sitemap and robotstxt integrations. */
|
*/
|
||||||
disableRouteInjection: z.boolean().default(false),
|
disableThemeProvider: z.coerce.boolean(),
|
||||||
/** OPTIONAL - (Default: true) Allows the user to disable "info" console output */
|
/** OPTIONAL - Set the theme you want to use
|
||||||
disableConsoleOutput: z.boolean().default(true),
|
* @default "@matthiesenxyz/astro-ghostcms-theme-default"
|
||||||
/** OPTIONAL - Theme Selector
|
*/
|
||||||
* This option allows the user to replace the included theme with an external npm module
|
theme: z.string(),
|
||||||
* @example
|
})
|
||||||
* // https://astro.build/config
|
.optional()
|
||||||
* export default defineConfig({
|
.default({
|
||||||
* integrations: [
|
disableThemeProvider: false,
|
||||||
* ghostcms({
|
theme: "@matthiesenxyz/astro-ghostcms-theme-default"
|
||||||
* theme: "@matthiesenxyz/astro-ghostcms-theme-default"
|
}),
|
||||||
* })
|
/** Allows the user to disable the provided 404 page */
|
||||||
* ],
|
disableDefault404: z.coerce.boolean().optional(),
|
||||||
* }); */
|
/** Allows the user to disable the provided RSS Feed */
|
||||||
theme: z.string().default("@matthiesenxyz/astro-ghostcms-theme-default"),
|
enableRSSFeed: z.coerce.boolean().optional().default(true),
|
||||||
/** OPTIONAL - astrojs/sitemap
|
/** Allows the user to Enable or Disable the default Satori OG Image Generation
|
||||||
* This option allows the user to configure the included integration
|
* @default true
|
||||||
|
*/
|
||||||
|
enableOGImages: z.coerce.boolean().optional().default(true),
|
||||||
|
/** Allows the user to turn on/off Full Console Logs
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
verbose: z.coerce.boolean().optional(),
|
||||||
|
/** OPTIONAL - Integrations Configuration
|
||||||
|
* This option allows the user to configure the included integrations
|
||||||
* Options shown are the availble options
|
* Options shown are the availble options
|
||||||
* REFERENCE https://docs.astro.build/en/guides/integrations-guide/sitemap
|
|
||||||
*/
|
*/
|
||||||
sitemap: SitemapSchema.optional(),
|
Integrations: z
|
||||||
/** OPTIONAL - astro-robots-txt
|
.object({
|
||||||
* This option allows the user to configure the included integration
|
/** Optional - astro-robots-txt
|
||||||
* Options shown are the availble options
|
* This option allows the user to configure the included integration
|
||||||
* REFERENCE https://www.npmjs.com/package/astro-robots-txt#configuration
|
* Options shown are the availble options
|
||||||
*/
|
* @see https://www.npmjs.com/package/astro-robots-txt#configuration
|
||||||
robotstxt: RobotsTxtSchema.optional(),
|
*/
|
||||||
|
robotsTxt: z.custom<RobotsTxtOptions>().optional(),
|
||||||
|
/** OPTIONAL - astrojs/sitemap
|
||||||
|
* This option allows the user to configure the included integration
|
||||||
|
* Options shown are the availble options
|
||||||
|
* @see https://docs.astro.build/en/guides/integrations-guide/sitemap
|
||||||
|
*/
|
||||||
|
sitemap: z.custom<SitemapOptions>().optional(),
|
||||||
|
})
|
||||||
|
.optional().default({}),
|
||||||
});
|
});
|
||||||
|
|
||||||
/** USER CONFIGURATION SCHEMA */
|
/** USER CONFIGURATION SCHEMA */
|
||||||
export type UserConfig = z.infer<typeof UserConfigSchema>;
|
export type GhostUserConfig = z.infer<typeof GhostUserConfigSchema>;
|
||||||
export type GhostUserConfig = z.infer<typeof UserConfigSchema>;
|
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
/** MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2024 Florian Lefebvre
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE. */
|
|
||||||
import { type Mock, afterEach, describe, expect, test, vi } from "vitest";
|
|
||||||
import { addVirtualImport } from "./add-virtual-import.js";
|
|
||||||
import { addVitePlugin } from "./add-vite-plugin.js";
|
|
||||||
|
|
||||||
vi.mock("./add-vite-plugin");
|
|
||||||
|
|
||||||
const pluginNameStub = <T extends string>(name: T): `vite-plugin-${T}` =>
|
|
||||||
`vite-plugin-${name}`;
|
|
||||||
|
|
||||||
describe("add-virtual-import", () => {
|
|
||||||
const name = "test-module";
|
|
||||||
const content = "export default {}";
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
vi.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("It should call `addVitePlugin`", () => {
|
|
||||||
const updateConfig = vi.fn();
|
|
||||||
|
|
||||||
addVirtualImport({
|
|
||||||
name,
|
|
||||||
content,
|
|
||||||
updateConfig,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(addVitePlugin).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("`addVitePlugin` should get called with the correct plugin name", () => {
|
|
||||||
const updateConfig = vi.fn();
|
|
||||||
|
|
||||||
addVirtualImport({
|
|
||||||
name,
|
|
||||||
content,
|
|
||||||
updateConfig,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expectedName = pluginNameStub(name);
|
|
||||||
|
|
||||||
const { plugin } = (addVitePlugin as Mock).mock.lastCall[0];
|
|
||||||
|
|
||||||
expect(plugin.name).toEqual(expectedName);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Virtual module should resolve correct name", () => {
|
|
||||||
const updateConfig = vi.fn();
|
|
||||||
|
|
||||||
addVirtualImport({
|
|
||||||
name,
|
|
||||||
content,
|
|
||||||
updateConfig,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { plugin } = (addVitePlugin as Mock).mock.lastCall[0];
|
|
||||||
|
|
||||||
const resolvedVirtualModuleId = plugin.resolveId(name);
|
|
||||||
|
|
||||||
expect(resolvedVirtualModuleId).toEqual(`\0${name}`);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,93 +0,0 @@
|
||||||
/** MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2024 Florian Lefebvre
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE. */
|
|
||||||
import type { HookParameters } from "astro";
|
|
||||||
import type { Plugin } from "vite";
|
|
||||||
import { addVitePlugin } from "./add-vite-plugin.js";
|
|
||||||
|
|
||||||
const resolveVirtualModuleId = <T extends string>(id: T): `\0${T}` => {
|
|
||||||
return `\0${id}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const createVirtualModule = (name: string, content: string): Plugin => {
|
|
||||||
const pluginName = `vite-plugin-${name}`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: pluginName,
|
|
||||||
resolveId(id) {
|
|
||||||
if (id === name) {
|
|
||||||
return resolveVirtualModuleId(id);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
load(id) {
|
|
||||||
if (id === resolveVirtualModuleId(name)) {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Vite virtual module and updates the Astro config.
|
|
||||||
* Virtual imports are useful for passing things like config options, or data computed within the integration.
|
|
||||||
*
|
|
||||||
* @param {object} params
|
|
||||||
* @param {string} params.name
|
|
||||||
* @param {string} params.content
|
|
||||||
* @param {import("astro").HookParameters<"astro:config:setup">["updateConfig"]} params.updateConfig
|
|
||||||
*
|
|
||||||
* @see https://astro-integration-kit.netlify.app/utilities/add-virtual-import/
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* // my-integration/index.ts
|
|
||||||
* import { addVirtualImport } from "astro-integration-kit";
|
|
||||||
*
|
|
||||||
* addVirtualImport(
|
|
||||||
* name: 'virtual:my-integration/config',
|
|
||||||
* content: `export default ${ JSON.stringify({foo: "bar"}) }`,
|
|
||||||
* updateConfig
|
|
||||||
* );
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* This is then readable anywhere else in your integration:
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* // myIntegration/src/component/layout.astro
|
|
||||||
* import config from "virtual:my-integration/config";
|
|
||||||
*
|
|
||||||
* console.log(config.foo) // "bar"
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const addVirtualImport = ({
|
|
||||||
name,
|
|
||||||
content,
|
|
||||||
updateConfig,
|
|
||||||
}: {
|
|
||||||
name: string;
|
|
||||||
content: string;
|
|
||||||
updateConfig: HookParameters<"astro:config:setup">["updateConfig"];
|
|
||||||
}) => {
|
|
||||||
addVitePlugin({
|
|
||||||
plugin: createVirtualModule(name, content),
|
|
||||||
updateConfig,
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,69 +0,0 @@
|
||||||
import type { Plugin } from "vite";
|
|
||||||
import { describe, expect, test, vi } from "vitest";
|
|
||||||
import { addVitePlugin } from "./add-vite-plugin.js";
|
|
||||||
|
|
||||||
describe("addVitePlugin", () => {
|
|
||||||
test("Should run", () => {
|
|
||||||
const updateConfig = vi.fn();
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
addVitePlugin({
|
|
||||||
plugin: null,
|
|
||||||
updateConfig,
|
|
||||||
}),
|
|
||||||
).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Should call updateConfig", () => {
|
|
||||||
const updateConfig = vi.fn();
|
|
||||||
|
|
||||||
addVitePlugin({
|
|
||||||
plugin: null,
|
|
||||||
updateConfig,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(updateConfig).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Should add vite plugin", () => {
|
|
||||||
let plugin: Plugin;
|
|
||||||
const pluginName = "test-plugin";
|
|
||||||
|
|
||||||
const updateConfig = vi.fn((config) => {
|
|
||||||
plugin = config.vite.plugins[0];
|
|
||||||
});
|
|
||||||
|
|
||||||
const expectedPlugin = {
|
|
||||||
name: pluginName,
|
|
||||||
};
|
|
||||||
|
|
||||||
addVitePlugin({
|
|
||||||
plugin: expectedPlugin,
|
|
||||||
updateConfig,
|
|
||||||
});
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
expect(plugin).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Plugin name should match", () => {
|
|
||||||
let plugin: Plugin;
|
|
||||||
const pluginName = "test-plugin";
|
|
||||||
|
|
||||||
const updateConfig = vi.fn((config) => {
|
|
||||||
plugin = config.vite.plugins[0];
|
|
||||||
});
|
|
||||||
|
|
||||||
const expectedPlugin = {
|
|
||||||
name: pluginName,
|
|
||||||
};
|
|
||||||
|
|
||||||
addVitePlugin({
|
|
||||||
plugin: expectedPlugin,
|
|
||||||
updateConfig,
|
|
||||||
});
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
expect(plugin.name).toBe(pluginName);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,55 +0,0 @@
|
||||||
/** MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2024 Florian Lefebvre
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE. */
|
|
||||||
import type { HookParameters } from "astro";
|
|
||||||
import type { PluginOption } from "vite";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a [vite plugin](https://vitejs.dev/guide/using-plugins) to the
|
|
||||||
* Astro config.
|
|
||||||
*
|
|
||||||
* @param {Params} params
|
|
||||||
* @param {import("vite").Plugin} params.plugin
|
|
||||||
* @param {import("astro").HookParameters<"astro:config:setup">["updateConfig"]} params.updateConfig
|
|
||||||
*
|
|
||||||
* @see https://astro-integration-kit.netlify.app/utilities/add-vite-plugin/
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* addVitePlugin({
|
|
||||||
* plugin,
|
|
||||||
* updateConfig
|
|
||||||
* })
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const addVitePlugin = ({
|
|
||||||
plugin,
|
|
||||||
updateConfig,
|
|
||||||
}: {
|
|
||||||
plugin: PluginOption;
|
|
||||||
updateConfig: HookParameters<"astro:config:setup">["updateConfig"];
|
|
||||||
}) => {
|
|
||||||
updateConfig({
|
|
||||||
vite: {
|
|
||||||
plugins: [plugin],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,22 +0,0 @@
|
||||||
import * as zod from "zod";
|
|
||||||
export class ValidationError extends Error {
|
|
||||||
name;
|
|
||||||
details;
|
|
||||||
constructor(message, options) {
|
|
||||||
super(message, options);
|
|
||||||
this.name = "ZodValidationError";
|
|
||||||
this.details = getIssuesFromErrorOptions(options);
|
|
||||||
}
|
|
||||||
toString() {
|
|
||||||
return this.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function getIssuesFromErrorOptions(options) {
|
|
||||||
if (options) {
|
|
||||||
const cause = options.cause;
|
|
||||||
if (cause instanceof zod.ZodError) {
|
|
||||||
return cause.issues;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
export const ISSUE_SEPARATOR = "; ";
|
|
||||||
export const MAX_ISSUES_IN_MESSAGE = 99;
|
|
||||||
export const PREFIX = "Validation error";
|
|
||||||
export const PREFIX_SEPARATOR = ": ";
|
|
||||||
export const UNION_SEPARATOR = ", or ";
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { fromZodIssue } from "./fromZodIssue";
|
|
||||||
export const errorMap = (issue, ctx) => {
|
|
||||||
const error = fromZodIssue({
|
|
||||||
...issue,
|
|
||||||
message: issue.message ?? ctx.defaultError,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
message: error.message,
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,37 +0,0 @@
|
||||||
import { ValidationError } from "./ValidationError";
|
|
||||||
import {
|
|
||||||
ISSUE_SEPARATOR,
|
|
||||||
MAX_ISSUES_IN_MESSAGE,
|
|
||||||
PREFIX,
|
|
||||||
PREFIX_SEPARATOR,
|
|
||||||
UNION_SEPARATOR,
|
|
||||||
} from "./config";
|
|
||||||
import { getMessageFromZodIssue } from "./fromZodIssue";
|
|
||||||
import { prefixMessage } from "./prefixMessage";
|
|
||||||
export function fromZodError(zodError, options = {}) {
|
|
||||||
const {
|
|
||||||
maxIssuesInMessage = MAX_ISSUES_IN_MESSAGE,
|
|
||||||
issueSeparator = ISSUE_SEPARATOR,
|
|
||||||
unionSeparator = UNION_SEPARATOR,
|
|
||||||
prefixSeparator = PREFIX_SEPARATOR,
|
|
||||||
prefix = PREFIX,
|
|
||||||
includePath = true,
|
|
||||||
} = options;
|
|
||||||
const zodIssues = zodError.errors;
|
|
||||||
const reason =
|
|
||||||
zodIssues.length === 0
|
|
||||||
? zodError.message
|
|
||||||
: zodIssues
|
|
||||||
.slice(0, maxIssuesInMessage)
|
|
||||||
.map((issue) =>
|
|
||||||
getMessageFromZodIssue({
|
|
||||||
issue,
|
|
||||||
issueSeparator,
|
|
||||||
unionSeparator,
|
|
||||||
includePath,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.join(issueSeparator);
|
|
||||||
const message = prefixMessage(reason, prefix, prefixSeparator);
|
|
||||||
return new ValidationError(message, { cause: zodError });
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
import * as zod from "zod";
|
|
||||||
import { ValidationError } from "./ValidationError";
|
|
||||||
import {
|
|
||||||
ISSUE_SEPARATOR,
|
|
||||||
PREFIX,
|
|
||||||
PREFIX_SEPARATOR,
|
|
||||||
UNION_SEPARATOR,
|
|
||||||
} from "./config";
|
|
||||||
import { prefixMessage } from "./prefixMessage";
|
|
||||||
import { isNonEmptyArray } from "./utils/NonEmptyArray";
|
|
||||||
import { joinPath } from "./utils/joinPath";
|
|
||||||
export function getMessageFromZodIssue(props) {
|
|
||||||
const { issue, issueSeparator, unionSeparator, includePath } = props;
|
|
||||||
if (issue.code === "invalid_union") {
|
|
||||||
return issue.unionErrors
|
|
||||||
.reduce((acc, zodError) => {
|
|
||||||
const newIssues = zodError.issues
|
|
||||||
.map((issue) =>
|
|
||||||
getMessageFromZodIssue({
|
|
||||||
issue,
|
|
||||||
issueSeparator,
|
|
||||||
unionSeparator,
|
|
||||||
includePath,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.join(issueSeparator);
|
|
||||||
if (!acc.includes(newIssues)) {
|
|
||||||
acc.push(newIssues);
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, [])
|
|
||||||
.join(unionSeparator);
|
|
||||||
}
|
|
||||||
if (includePath && isNonEmptyArray(issue.path)) {
|
|
||||||
if (issue.path.length === 1) {
|
|
||||||
const identifier = issue.path[0];
|
|
||||||
if (typeof identifier === "number") {
|
|
||||||
return `${issue.message} at index ${identifier}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return `${issue.message} at "${joinPath(issue.path)}"`;
|
|
||||||
}
|
|
||||||
return issue.message;
|
|
||||||
}
|
|
||||||
export function fromZodIssue(issue, options = {}) {
|
|
||||||
const {
|
|
||||||
issueSeparator = ISSUE_SEPARATOR,
|
|
||||||
unionSeparator = UNION_SEPARATOR,
|
|
||||||
prefixSeparator = PREFIX_SEPARATOR,
|
|
||||||
prefix = PREFIX,
|
|
||||||
includePath = true,
|
|
||||||
} = options;
|
|
||||||
const reason = getMessageFromZodIssue({
|
|
||||||
issue,
|
|
||||||
issueSeparator,
|
|
||||||
unionSeparator,
|
|
||||||
includePath,
|
|
||||||
});
|
|
||||||
const message = prefixMessage(reason, prefix, prefixSeparator);
|
|
||||||
return new ValidationError(message, { cause: new zod.ZodError([issue]) });
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
export { ValidationError } from "./ValidationError";
|
|
||||||
export { isValidationError } from "./isValidationError";
|
|
||||||
export { isValidationErrorLike } from "./isValidationErrorLike";
|
|
||||||
export { errorMap } from "./errorMap";
|
|
||||||
export { fromZodIssue } from "./fromZodIssue";
|
|
||||||
export { fromZodError } from "./fromZodError";
|
|
||||||
export { toValidationError } from "./toValidationError";
|
|
|
@ -1,4 +0,0 @@
|
||||||
import { ValidationError } from "./ValidationError";
|
|
||||||
export function isValidationError(err) {
|
|
||||||
return err instanceof ValidationError;
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
export function isValidationErrorLike(err) {
|
|
||||||
return err instanceof Error && err.name === "ZodValidationError";
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
import { PREFIX } from "./config";
|
|
||||||
export function prefixMessage(message, prefix, prefixSeparator) {
|
|
||||||
if (prefix !== null) {
|
|
||||||
if (message.length > 0) {
|
|
||||||
return [prefix, message].join(prefixSeparator);
|
|
||||||
}
|
|
||||||
return prefix;
|
|
||||||
}
|
|
||||||
if (message.length > 0) {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
return PREFIX;
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
import * as zod from "zod";
|
|
||||||
import { ValidationError } from "./ValidationError";
|
|
||||||
import { fromZodError } from "./fromZodError";
|
|
||||||
export const toValidationError =
|
|
||||||
(options = {}) =>
|
|
||||||
(err) => {
|
|
||||||
if (err instanceof zod.ZodError) {
|
|
||||||
return fromZodError(err, options);
|
|
||||||
}
|
|
||||||
if (err instanceof Error) {
|
|
||||||
return new ValidationError(err.message, { cause: err });
|
|
||||||
}
|
|
||||||
return new ValidationError("Unknown error");
|
|
||||||
};
|
|
|
@ -1,3 +0,0 @@
|
||||||
export function isNonEmptyArray(value) {
|
|
||||||
return value.length !== 0;
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
const identifierRegex = /[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*/u;
|
|
||||||
export function joinPath(path) {
|
|
||||||
if (path.length === 1) {
|
|
||||||
return path[0].toString();
|
|
||||||
}
|
|
||||||
return path.reduce((acc, item) => {
|
|
||||||
if (typeof item === "number") {
|
|
||||||
return `${acc}[${item.toString()}]`;
|
|
||||||
}
|
|
||||||
if (item.includes('"')) {
|
|
||||||
return `${acc}["${escapeQuotes(item)}"]`;
|
|
||||||
}
|
|
||||||
if (!identifierRegex.test(item)) {
|
|
||||||
return `${acc}["${item}"]`;
|
|
||||||
}
|
|
||||||
const separator = acc.length === 0 ? "" : ".";
|
|
||||||
return acc + separator + item;
|
|
||||||
}, "");
|
|
||||||
}
|
|
||||||
function escapeQuotes(str) {
|
|
||||||
return str.replace(/"/g, '\\"');
|
|
||||||
}
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"extends": "astro/tsconfigs/strictest",
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "preserve"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
declare module "virtual:@matthiesenxyz/astro-ghostcms/config" {
|
declare module "virtual:@matthiesenxyz/astro-ghostcms/config" {
|
||||||
const Config: import("../schemas/index").GhostUserConfig;
|
const Config: import("./src/schemas/userconfig").GhostUserConfig;
|
||||||
export default Config;
|
export default Config;
|
||||||
}
|
}
|
|
@ -1,11 +1,8 @@
|
||||||
/// <reference types="vitest" />
|
/// <reference types="vitest" />
|
||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
import tsconfigPaths from "vite-tsconfig-paths";
|
|
||||||
import { defineProject } from "vitest/config";
|
import { defineProject } from "vitest/config";
|
||||||
|
|
||||||
export default defineProject({
|
export default defineProject({
|
||||||
plugins: [tsconfigPaths()],
|
|
||||||
test: {
|
test: {
|
||||||
globals: true,
|
globals: true,
|
||||||
include: ["./**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
|
include: ["./**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
|
||||||
|
|
|
@ -46,17 +46,7 @@
|
||||||
"@types/gunzip-maybe": "^1.4.0",
|
"@types/gunzip-maybe": "^1.4.0",
|
||||||
"@types/node": "^20.11.19",
|
"@types/node": "^20.11.19",
|
||||||
"@types/tar-fs": "^2.0.1",
|
"@types/tar-fs": "^2.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.0.1",
|
"typescript": "^5.3.3"
|
||||||
"@typescript-eslint/parser": "^7.0.1",
|
|
||||||
"eslint": "^8.56.0",
|
|
||||||
"eslint-config-prettier": "^9.1.0",
|
|
||||||
"eslint-import-resolver-node": "^0.3.7",
|
|
||||||
"eslint-import-resolver-typescript": "^3.5.3",
|
|
||||||
"prettier": "^3.2.5",
|
|
||||||
"typescript": "^5.3.3",
|
|
||||||
"vitest": "^1.3.0",
|
|
||||||
"vite": "^5.1.3",
|
|
||||||
"vite-tsconfig-paths": "^4.2.2"
|
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^4.2.8",
|
"astro": "^4.4.0",
|
||||||
"@matthiesenxyz/astro-ghostcms": "^3.1.5",
|
"@matthiesenxyz/astro-ghostcms": "^3.3.0",
|
||||||
"@matthiesenxyz/astro-ghostcms-theme-default": "^0.1.5"
|
"@matthiesenxyz/astro-ghostcms-theme-default": "^0.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/check": "^0.4.1",
|
"@astrojs/check": "^0.4.1",
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
|
import astroGhostCMS from "@matthiesenxyz/astro-ghostcms";
|
||||||
import { defineConfig } from "astro/config";
|
import { defineConfig } from "astro/config";
|
||||||
import ghostcms from "@matthiesenxyz/astro-ghostcms";
|
import UnoCSS from "unocss/astro";
|
||||||
import UnoCSS from 'unocss/astro';
|
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: "https://example.xyz/",
|
site: "https://example.xyz/",
|
||||||
trailingSlash: 'ignore',
|
trailingSlash: "ignore",
|
||||||
integrations: [
|
integrations: [
|
||||||
UnoCSS({ injectReset: true }),
|
UnoCSS({ injectReset: true }),
|
||||||
ghostcms({
|
astroGhostCMS({
|
||||||
theme: "@matthiesenxyz/astro-ghostcms-brutalbyelian",
|
|
||||||
ghostURL: "https://ghostdemo.matthiesen.xyz",
|
ghostURL: "https://ghostdemo.matthiesen.xyz",
|
||||||
})
|
ThemeProvider: {
|
||||||
|
theme: "@matthiesenxyz/astro-ghostcms-brutal",
|
||||||
|
},
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^4.3.2",
|
"astro": "^4.4.0",
|
||||||
"@matthiesenxyz/astro-ghostcms": "^3.2.2",
|
"@matthiesenxyz/astro-ghostcms": "^3.3.0",
|
||||||
"@matthiesenxyz/astro-ghostcms-brutalbyelian": "^0.0.5",
|
"@matthiesenxyz/astro-ghostcms-brutalbyelian": "^0.0.11",
|
||||||
"@unocss/astro": "^0.57.7"
|
"@unocss/astro": "^0.57.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/// <reference types="astro/client" />
|
/// <reference types="astro/client" />
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly CONTENT_API_KEY: string
|
readonly CONTENT_API_KEY: string;
|
||||||
readonly CONTENT_API_URL: string
|
readonly CONTENT_API_URL: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
readonly env: ImportMetaEnv;
|
readonly env: ImportMetaEnv;
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import brutalTheme from '@matthiesenxyz/astro-ghostcms-brutalbyelian';
|
import brutalTheme from "@matthiesenxyz/astro-ghostcms-brutalbyelian";
|
||||||
import { defineConfig } from 'unocss';
|
import { defineConfig } from "unocss";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
presets: [ brutalTheme() ],
|
presets: [brutalTheme()],
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import tailwind from "@astrojs/tailwind";
|
import tailwind from "@astrojs/tailwind";
|
||||||
import ghostcms from "@matthiesenxyz/astro-ghostcms";
|
import astroGhostCMS from "@matthiesenxyz/astro-ghostcms";
|
||||||
import { defineConfig } from "astro/config";
|
import { defineConfig } from "astro/config";
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
|
@ -7,9 +7,11 @@ export default defineConfig({
|
||||||
site: "https://example.xyz/",
|
site: "https://example.xyz/",
|
||||||
integrations: [
|
integrations: [
|
||||||
tailwind(),
|
tailwind(),
|
||||||
ghostcms({
|
astroGhostCMS({
|
||||||
theme: "@matthiesenxyz/astro-ghostcms-catppuccin",
|
|
||||||
ghostURL: "https://ghostdemo.matthiesen.xyz",
|
ghostURL: "https://ghostdemo.matthiesen.xyz",
|
||||||
|
ThemeProvider: {
|
||||||
|
theme: "@matthiesenxyz/astro-ghostcms-catppuccin",
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^4.2.8",
|
"astro": "^4.4.0",
|
||||||
"@matthiesenxyz/astro-ghostcms": "^3.1.8",
|
"@matthiesenxyz/astro-ghostcms": "^3.3.0",
|
||||||
"@matthiesenxyz/astro-ghostcms-catppuccin": "^0.0.3",
|
"@matthiesenxyz/astro-ghostcms-catppuccin": "^0.0.8",
|
||||||
"@astrojs/tailwind": "^5.1.0",
|
"@astrojs/tailwind": "^5.1.0",
|
||||||
"tailwindcss": "^3.3.5"
|
"tailwindcss": "^3.3.5"
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,10 +9,13 @@ export default defineConfig({
|
||||||
// Includes GhostCMS API, @astrojs/rss, @astrojs/sitemap, and astro-robots-txt
|
// Includes GhostCMS API, @astrojs/rss, @astrojs/sitemap, and astro-robots-txt
|
||||||
GhostCMS({
|
GhostCMS({
|
||||||
ghostURL: "https://ghostdemo.matthiesen.xyz",
|
ghostURL: "https://ghostdemo.matthiesen.xyz",
|
||||||
// This Option Disables all default theme injection and allows DIY mode.
|
ThemeProvider: {
|
||||||
disableRouteInjection: true,
|
disableThemeProvider: true,
|
||||||
// Enable this to disable the extra console logs
|
},
|
||||||
disableConsoleOutput: false,
|
disableDefault404: true,
|
||||||
|
enableRSSFeed: false,
|
||||||
|
enableOGImages: false,
|
||||||
|
verbose: false,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.4.1",
|
"@astrojs/check": "^0.4.1",
|
||||||
"@matthiesenxyz/astro-ghostcms": "^3.1.4",
|
"@matthiesenxyz/astro-ghostcms": "^3.3.0",
|
||||||
"astro": "^4.2.8",
|
"astro": "^4.4.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"astro-font": "^0.0.77"
|
"astro-font": "^0.0.77"
|
||||||
},
|
},
|
||||||
|
|
|
@ -48,7 +48,9 @@ import starlightGhostCMS from '@matthiesenxyz/starlight-ghostcms';
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
integrations: [
|
integrations: [
|
||||||
starlight({
|
starlight({
|
||||||
plugins: [starlightGhostCMS()],
|
plugins: [
|
||||||
|
starlightGhostCMS()
|
||||||
|
],
|
||||||
title: 'My Docs',
|
title: 'My Docs',
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,91 +1,117 @@
|
||||||
import type { StarlightPlugin, StarlightUserConfig } from '@astrojs/starlight/types'
|
import type {
|
||||||
import type { AstroIntegrationLogger } from 'astro'
|
StarlightPlugin,
|
||||||
import { type StarlightGhostConfig, validateConfig } from './src/schemas/config'
|
StarlightUserConfig,
|
||||||
import { vitePluginStarlightGhostConfig } from './src/integrations/vite'
|
} from "@astrojs/starlight/types";
|
||||||
import { facebook, getSettings, invariant, twitter } from './src/utils/api'
|
import type { AstroIntegrationLogger } from "astro";
|
||||||
|
import {
|
||||||
|
type StarlightGhostConfig,
|
||||||
|
validateConfig,
|
||||||
|
} from "./src/schemas/config";
|
||||||
|
import { facebook, getSettings, invariant, twitter } from "./src/utils/api";
|
||||||
|
import starlightGhostcms from "./src/integrations/starlight-ghostcms";
|
||||||
|
|
||||||
const settings = await getSettings()
|
const settings = await getSettings();
|
||||||
|
|
||||||
export type { StarlightGhostConfig }
|
export type { StarlightGhostConfig };
|
||||||
|
|
||||||
export default function starlightGhostCMS(userConfig?: StarlightGhostConfig): StarlightPlugin {
|
export default function starlightGhostCMS(
|
||||||
const config: StarlightGhostConfig = validateConfig(userConfig)
|
userConfig?: StarlightGhostConfig,
|
||||||
invariant(settings, "Settings not available... check your api key/url")
|
): StarlightPlugin {
|
||||||
|
const config: StarlightGhostConfig = validateConfig(userConfig);
|
||||||
|
invariant(settings, "Settings not available... check your api key/url");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: '@matthiesenxyz/starlight-ghostcms-plugin',
|
name: "@matthiesenxyz/starlight-ghostcms-plugin",
|
||||||
hooks: {
|
hooks: {
|
||||||
setup({ astroConfig, addIntegration, config: starlightConfig, logger, updateConfig: updateStarlightConfig }) {
|
setup({
|
||||||
updateStarlightConfig({
|
astroConfig,
|
||||||
social: {
|
addIntegration,
|
||||||
...starlightConfig.social,
|
config: starlightConfig,
|
||||||
rss: `${astroConfig.site}/rss.xml`,
|
logger,
|
||||||
twitter: twitter(settings.twitter?settings.twitter:""),
|
updateConfig: updateStarlightConfig,
|
||||||
facebook: facebook(settings.facebook?settings.facebook:""),
|
}) {
|
||||||
},
|
// Add the Starlight-GhostCMS integration
|
||||||
components: {
|
addIntegration(starlightGhostcms(config));
|
||||||
...starlightConfig.components,
|
|
||||||
...overrideStarlightComponent(starlightConfig.components, logger, 'MarkdownContent'),
|
|
||||||
...overrideStarlightComponent(starlightConfig.components, logger, 'Sidebar'),
|
|
||||||
...overrideStarlightComponent(starlightConfig.components, logger, "SiteTitle"),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
addIntegration({
|
// Update the Starlight config with the GhostCMS config
|
||||||
name: '@matthiesenxyz/starlight-ghostcms',
|
updateStarlightConfig({
|
||||||
hooks: {
|
social: {
|
||||||
'astro:config:setup': ({ injectRoute, updateConfig }) => {
|
...starlightConfig.social,
|
||||||
injectRoute({
|
...overrideRSS(starlightConfig.social, astroConfig.site),
|
||||||
pattern: '/blog',
|
...overrideTwitter(starlightConfig.social),
|
||||||
entrypoint: '@matthiesenxyz/starlight-ghostcms/routes/index.astro',
|
...overrideFacebook(starlightConfig.social),
|
||||||
prerender: true,
|
},
|
||||||
})
|
components: {
|
||||||
injectRoute({
|
...starlightConfig.components,
|
||||||
pattern: '/blog/[slug]',
|
...overrideStarlightComponent(
|
||||||
entrypoint: '@matthiesenxyz/starlight-ghostcms/routes/[slug].astro',
|
starlightConfig.components,
|
||||||
prerender: true,
|
logger,
|
||||||
})
|
"MarkdownContent",
|
||||||
injectRoute({
|
),
|
||||||
pattern: '/blog/about',
|
...overrideStarlightComponent(
|
||||||
entrypoint: '@matthiesenxyz/starlight-ghostcms/routes/about.astro',
|
starlightConfig.components,
|
||||||
prerender: true,
|
logger,
|
||||||
})
|
"Sidebar",
|
||||||
injectRoute({
|
),
|
||||||
pattern: '/blog/authors',
|
...overrideStarlightComponent(
|
||||||
entrypoint: '@matthiesenxyz/starlight-ghostcms/routes/authors.astro',
|
starlightConfig.components,
|
||||||
})
|
logger,
|
||||||
injectRoute({
|
"SiteTitle",
|
||||||
pattern: '/rss.xml',
|
),
|
||||||
entrypoint: '@matthiesenxyz/starlight-ghostcms/routes/rss.xml.ts'
|
},
|
||||||
})
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
updateConfig({
|
function overrideRSS(
|
||||||
vite: {
|
socials: StarlightUserConfig["social"],
|
||||||
plugins: [vitePluginStarlightGhostConfig(config)],
|
url: string | undefined
|
||||||
},
|
) {
|
||||||
})
|
if (socials?.rss) { return {}; }
|
||||||
}
|
if (url === undefined) { return undefined; }
|
||||||
}
|
return { rss: `${url}/rss.xml` };
|
||||||
})
|
}
|
||||||
}
|
|
||||||
},
|
function overrideTwitter(
|
||||||
}
|
socials: StarlightUserConfig["social"],
|
||||||
|
) {
|
||||||
|
if (socials?.twitter) { return {}; }
|
||||||
|
if (settings?.twitter) {
|
||||||
|
return { twitter: twitter(settings.twitter), }
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function overrideFacebook(
|
||||||
|
socials: StarlightUserConfig["social"],
|
||||||
|
) {
|
||||||
|
if (socials?.facebook) { return {}; }
|
||||||
|
if (settings?.facebook) {
|
||||||
|
return { facebook: facebook(settings.facebook), }
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function overrideStarlightComponent(
|
function overrideStarlightComponent(
|
||||||
components: StarlightUserConfig['components'],
|
components: StarlightUserConfig["components"],
|
||||||
logger: AstroIntegrationLogger,
|
logger: AstroIntegrationLogger,
|
||||||
component: keyof NonNullable<StarlightUserConfig['components']>,
|
component: keyof NonNullable<StarlightUserConfig["components"]>,
|
||||||
) {
|
) {
|
||||||
if (components?.[component]) {
|
if (components?.[component]) {
|
||||||
logger.warn(`It looks like you already have a \`${component}\` component override in your Starlight configuration.`)
|
logger.warn(
|
||||||
logger.warn(`To use \`starlight-ghostcms\`, remove the override for the \`${component}\` component.\n`)
|
`It looks like you already have a \`${component}\` component override in your Starlight configuration.`,
|
||||||
logger.warn("This Warning can be ignored if you know what your doing ;)")
|
);
|
||||||
|
logger.warn(
|
||||||
|
`To use \`starlight-ghostcms\`, remove the override for the \`${component}\` component.\n`,
|
||||||
|
);
|
||||||
|
logger.warn("This Warning can be ignored if you know what your doing ;)");
|
||||||
|
|
||||||
return {}
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
[component]: `@matthiesenxyz/starlight-ghostcms/overrides/${component}.astro`,
|
[component]: `@matthiesenxyz/starlight-ghostcms/overrides/${component}.astro`,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,21 +36,15 @@
|
||||||
"types": "index.ts",
|
"types": "index.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
|
"CHANGELOG.md",
|
||||||
"index.ts",
|
"index.ts",
|
||||||
"tsconfig.json",
|
"LICENSE",
|
||||||
"types.d.ts"
|
"package.json",
|
||||||
|
"README.md"
|
||||||
],
|
],
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./index.ts",
|
".": "./index.ts",
|
||||||
"./overrides/MarkdownContent.astro": "./src/overrides/MarkdownContent.astro",
|
"./overrides/*": "./src/overrides/*"
|
||||||
"./overrides/Sidebar.astro": "./src/overrides/Sidebar.astro",
|
|
||||||
"./overrides/SiteTitle.astro": "./src/overrides/SiteTitle.astro",
|
|
||||||
"./routes/index.astro": "./src/routes/index.astro",
|
|
||||||
"./routes/about.astro": "./src/routes/about.astro",
|
|
||||||
"./routes/authors.astro": "./src/routes/authors.astro",
|
|
||||||
"./routes/[slug].astro": "./src/routes/[slug].astro",
|
|
||||||
"./routes/rss.xml.ts": "./src/routes/rss.xml.ts",
|
|
||||||
"./schema": "./src/schemas/config.ts"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
|
@ -60,19 +54,20 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/starlight": "^0.19.0",
|
"@astrojs/starlight": "^0.19.0",
|
||||||
"@ts-ghost/tsconfig": "workspace:*",
|
"astro": "^4.4.1",
|
||||||
"astro": "^4.4.0",
|
"vitest": "^1.3.1",
|
||||||
"vitest": "^1.2.2",
|
|
||||||
"vitest-fetch-mock": "^0.2.2"
|
"vitest-fetch-mock": "^0.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/rss": "^4.0.5",
|
"@astrojs/rss": "^4.0.5",
|
||||||
"@ts-ghost/core-api": "^5.1.2",
|
"@ts-ghost/core-api": "^6.0.0",
|
||||||
"vite": "^5.1.2",
|
"@ts-ghost/content-api": "^4.0.12",
|
||||||
"vite-tsconfig-paths": "^4.2.2"
|
"astro-integration-kit": "^0.5.1",
|
||||||
|
"vite": "^5.1.4",
|
||||||
|
"vite-tsconfig-paths": "^4.3.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@astrojs/starlight": ">=0.19.0",
|
"@astrojs/starlight": ">=0.19.0",
|
||||||
"astro": ">=4.3.7"
|
"astro": ">=4.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import {
|
||||||
|
createResolver,
|
||||||
|
defineIntegration,
|
||||||
|
} from "astro-integration-kit";
|
||||||
|
import { corePlugins } from "astro-integration-kit/plugins";
|
||||||
|
import { z } from "astro/zod";
|
||||||
|
import { type StarlightGhostConfig } from "../schemas/config";
|
||||||
|
|
||||||
|
export default defineIntegration({
|
||||||
|
name: "@matthiesenxyz/starlight-ghostcms",
|
||||||
|
optionsSchema: z.custom<StarlightGhostConfig>(),
|
||||||
|
plugins: [...corePlugins],
|
||||||
|
setup({ options }) {
|
||||||
|
const { resolve } = createResolver(import.meta.url);
|
||||||
|
|
||||||
|
return {
|
||||||
|
"astro:config:setup": ({
|
||||||
|
watchIntegration,
|
||||||
|
addVirtualImports,
|
||||||
|
injectRoute
|
||||||
|
}) => {
|
||||||
|
watchIntegration(resolve());
|
||||||
|
|
||||||
|
addVirtualImports({
|
||||||
|
'virtual:starlight-ghostcms/config': `export default ${JSON.stringify(options)}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const makeRoute = (endpoint: string, entrypoint: string) => {
|
||||||
|
injectRoute({
|
||||||
|
pattern: `/${endpoint}`,
|
||||||
|
entrypoint: resolve(`../routes/${entrypoint}`),
|
||||||
|
prerender: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const sanitisedRoute = options.route
|
||||||
|
.replace(/^\//, '')
|
||||||
|
.replace(/\/$/, '');
|
||||||
|
|
||||||
|
makeRoute(`${sanitisedRoute}`,
|
||||||
|
"index.astro");
|
||||||
|
makeRoute(`${sanitisedRoute}/[slug]`,
|
||||||
|
"[slug].astro");
|
||||||
|
makeRoute(`${sanitisedRoute}/about`,
|
||||||
|
"about.astro");
|
||||||
|
makeRoute(`${sanitisedRoute}/authors`,
|
||||||
|
"authors.astro");
|
||||||
|
makeRoute("rss.xml",
|
||||||
|
"rss.xml.ts");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -1,22 +0,0 @@
|
||||||
import type { ViteUserConfig } from 'astro'
|
|
||||||
|
|
||||||
import type { StarlightGhostConfig } from '../schemas/config.ts'
|
|
||||||
|
|
||||||
// Expose the starlight-blog plugin configuration.
|
|
||||||
export function vitePluginStarlightGhostConfig(config: StarlightGhostConfig): VitePlugin {
|
|
||||||
const moduleId = 'virtual:starlight-ghost-config'
|
|
||||||
const resolvedModuleId = `\0${moduleId}`
|
|
||||||
const moduleContent = `export default ${JSON.stringify(config)}`
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: 'vite-plugin-starlight-ghost-config',
|
|
||||||
load(id) {
|
|
||||||
return id === resolvedModuleId ? moduleContent : undefined
|
|
||||||
},
|
|
||||||
resolveId(id) {
|
|
||||||
return id === moduleId ? resolvedModuleId : undefined
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type VitePlugin = NonNullable<ViteUserConfig['plugins']>[number]
|
|
|
@ -1,22 +1,27 @@
|
||||||
---
|
---
|
||||||
import StarlightMarkdownContent from '@astrojs/starlight/components/MarkdownContent.astro'
|
import StarlightMarkdownContent from '@astrojs/starlight/components/MarkdownContent.astro'
|
||||||
import type { Props } from '@astrojs/starlight/props'
|
import type { Props } from '@astrojs/starlight/props'
|
||||||
|
import config from 'virtual:starlight-ghostcms/config'
|
||||||
import { isAnyBlogPostPage } from '../utils/page'
|
import { isAnyBlogPostPage } from '../utils/page'
|
||||||
import Metadata from '../components/Metadata.astro'
|
import Metadata from '../components/Metadata.astro'
|
||||||
import type { Post } from '../schemas/posts'
|
import type { Post } from '../schemas/posts'
|
||||||
|
|
||||||
|
export function checkpath(path: string){
|
||||||
|
return path.split('/').includes(config.route) ? true : false
|
||||||
|
}
|
||||||
|
|
||||||
|
const isBlog = checkpath(Astro.url.pathname)
|
||||||
const isBlogPost = isAnyBlogPostPage(Astro.props.slug)
|
const isBlogPost = isAnyBlogPostPage(Astro.props.slug)
|
||||||
let blogEntry: Post | undefined = undefined
|
let blogEntry: Post | undefined = undefined
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<StarlightMarkdownContent {...Astro.props}>
|
<StarlightMarkdownContent {...Astro.props}>
|
||||||
{isBlogPost && blogEntry ? <Metadata entry={blogEntry} /> : null}
|
{isBlog && blogEntry ? <Metadata entry={blogEntry} /> : null}
|
||||||
<slot />
|
<slot />
|
||||||
|
|
||||||
{
|
{
|
||||||
isBlogPost && blogEntry ? (
|
isBlog && blogEntry ? (
|
||||||
<div class="post-footer">
|
<div class="post-footer">
|
||||||
</div>
|
</div>
|
||||||
) : null
|
) : null
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
---
|
---
|
||||||
import StarlightSidebar from '@astrojs/starlight/components/Sidebar.astro'
|
import StarlightSidebar from '@astrojs/starlight/components/Sidebar.astro'
|
||||||
import type { Props } from '@astrojs/starlight/props'
|
import type { Props } from '@astrojs/starlight/props'
|
||||||
import config from 'virtual:starlight-ghost-config'
|
import config from 'virtual:starlight-ghostcms/config'
|
||||||
import { isBlogPostPage, isBlogRoot } from '../utils/page'
|
import { isBlogPostPage, isBlogRoot } from '../utils/page'
|
||||||
import { getAllPages, getAllPosts, getSluggedPage } from '../utils/api/api-functions.js'
|
import { getAllPages, getAllPosts, getSluggedPage } from '../utils/api'
|
||||||
import type { SidebarEntry } from './sidebartypes'
|
import type { SidebarEntry } from './sidebartypes'
|
||||||
|
|
||||||
export async function getRecentBlogEntries(){
|
export async function getRecentBlogEntries(){
|
||||||
|
@ -17,19 +17,17 @@ export async function getBlogPageEntries(){
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkpath(path: string){
|
export function checkpath(path: string){
|
||||||
if ( path.slice(0, 5) === "/blog" ){
|
return path.split('/').includes(config.route) ? true : false
|
||||||
return true
|
|
||||||
} else { return false }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAbout(path: string){
|
export function isAbout(path: string){
|
||||||
if ( path === "/blog/about" ){
|
if ( path === `/${config.route}/about` ){
|
||||||
return true
|
return true
|
||||||
} else { return false }
|
} else { return false }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAuthors(path: string){
|
export function isAuthors(path: string){
|
||||||
if ( path === "/blog/authors" ){
|
if ( path === `/${config.route}/authors` ){
|
||||||
return true
|
return true
|
||||||
} else { return false }
|
} else { return false }
|
||||||
}
|
}
|
||||||
|
@ -38,7 +36,7 @@ const recentEntries = isBlog ? await getRecentBlogEntries() : []
|
||||||
const aboutPage = await getSluggedPage("about");
|
const aboutPage = await getSluggedPage("about");
|
||||||
const AboutEntry:SidebarEntry = {
|
const AboutEntry:SidebarEntry = {
|
||||||
attrs: {}, badge: undefined,
|
attrs: {}, badge: undefined,
|
||||||
href: '/blog/about',
|
href: `/${config.route}/about`,
|
||||||
isCurrent: isAbout(Astro.url.pathname),
|
isCurrent: isAbout(Astro.url.pathname),
|
||||||
type: 'link',
|
type: 'link',
|
||||||
label: aboutPage?.post?.title
|
label: aboutPage?.post?.title
|
||||||
|
@ -55,7 +53,7 @@ const blogSidebar: Props['sidebar'] = isBlog
|
||||||
{
|
{
|
||||||
attrs: {},
|
attrs: {},
|
||||||
badge: undefined,
|
badge: undefined,
|
||||||
href: '/blog/authors',
|
href: `/${config.route}/authors`,
|
||||||
isCurrent: isAuthors(Astro.url.pathname),
|
isCurrent: isAuthors(Astro.url.pathname),
|
||||||
label: 'Our Authors',
|
label: 'Our Authors',
|
||||||
type: 'link',
|
type: 'link',
|
||||||
|
@ -63,7 +61,7 @@ const blogSidebar: Props['sidebar'] = isBlog
|
||||||
{
|
{
|
||||||
attrs: {},
|
attrs: {},
|
||||||
badge: undefined,
|
badge: undefined,
|
||||||
href: '/blog',
|
href: `/${config.route}`,
|
||||||
isCurrent: isBlogRoot(Astro.props.slug),
|
isCurrent: isBlogRoot(Astro.props.slug),
|
||||||
label: 'All posts',
|
label: 'All posts',
|
||||||
type: 'link',
|
type: 'link',
|
||||||
|
@ -74,8 +72,8 @@ const blogSidebar: Props['sidebar'] = isBlog
|
||||||
entries: recentEntries.map((blogEntry) => ({
|
entries: recentEntries.map((blogEntry) => ({
|
||||||
attrs: {},
|
attrs: {},
|
||||||
badge: blogEntry.featured?({text: "⭐", variant: "note"}):undefined,
|
badge: blogEntry.featured?({text: "⭐", variant: "note"}):undefined,
|
||||||
href: `/blog/${blogEntry.slug}`,
|
href: `/${config.route}/${blogEntry.slug}`,
|
||||||
isCurrent: isBlogPostPage(Astro.props.slug, `blog/${blogEntry.slug}`),
|
isCurrent: isBlogPostPage(Astro.props.slug, `${config.route}/${blogEntry.slug}`),
|
||||||
label: blogEntry.title,
|
label: blogEntry.title,
|
||||||
type: 'link',
|
type: 'link',
|
||||||
})),
|
})),
|
||||||
|
@ -89,7 +87,7 @@ const blogSidebar: Props['sidebar'] = isBlog
|
||||||
{
|
{
|
||||||
!isBlog && (
|
!isBlog && (
|
||||||
<div class="md:sl-hidden">
|
<div class="md:sl-hidden">
|
||||||
<a href="/blog">Blog</a>
|
<a href={`/${config.route}`}>Blog</a>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
---
|
---
|
||||||
import type { Props } from "@astrojs/starlight/props";
|
import type { Props } from "@astrojs/starlight/props";
|
||||||
import AstrolightSiteTitle from "@astrojs/starlight/components/SiteTitle.astro";
|
import AstrolightSiteTitle from "@astrojs/starlight/components/SiteTitle.astro";
|
||||||
|
import config from 'virtual:starlight-ghostcms/config'
|
||||||
---
|
---
|
||||||
|
|
||||||
<AstrolightSiteTitle {...Astro.props} />
|
<AstrolightSiteTitle {...Astro.props} />
|
||||||
<div>
|
<div>
|
||||||
<a href="/blog">Blog</a>
|
<a href={`/${config.route}`}>{config.linkName}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
import { z } from 'astro/zod';
|
import type { AstroBuiltinAttributes } from "astro";
|
||||||
import type { AstroBuiltinAttributes } from 'astro';
|
import type { HTMLAttributes } from "astro/types";
|
||||||
import type { HTMLAttributes } from 'astro/types';
|
import { z } from "astro/zod";
|
||||||
|
|
||||||
const linkHTMLAttributesSchema = z.record(
|
const linkHTMLAttributesSchema = z.record(
|
||||||
z.union([z.string(), z.number(), z.boolean(), z.undefined()])
|
z.union([z.string(), z.number(), z.boolean(), z.undefined()]),
|
||||||
) as z.Schema<Omit<HTMLAttributes<'a'>, keyof AstroBuiltinAttributes | 'children'>>;
|
) as z.Schema<
|
||||||
|
Omit<HTMLAttributes<"a">, keyof AstroBuiltinAttributes | "children">
|
||||||
|
>;
|
||||||
export type LinkHTMLAttributes = z.infer<typeof linkHTMLAttributesSchema>;
|
export type LinkHTMLAttributes = z.infer<typeof linkHTMLAttributesSchema>;
|
||||||
|
|
||||||
const badgeSchema = () =>
|
const badgeSchema = () =>
|
||||||
z.object({
|
z.object({
|
||||||
variant: z.enum(['note', 'danger', 'success', 'caution', 'tip', 'default']).default('default'),
|
variant: z
|
||||||
|
.enum(["note", "danger", "success", "caution", "tip", "default"])
|
||||||
|
.default("default"),
|
||||||
text: z.string(),
|
text: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -17,8 +21,8 @@ export const BadgeConfigSchema = () =>
|
||||||
z
|
z
|
||||||
.union([z.string(), badgeSchema()])
|
.union([z.string(), badgeSchema()])
|
||||||
.transform((badge) => {
|
.transform((badge) => {
|
||||||
if (typeof badge === 'string') {
|
if (typeof badge === "string") {
|
||||||
return { variant: 'default' as const, text: badge };
|
return { variant: "default" as const, text: badge };
|
||||||
}
|
}
|
||||||
return badge;
|
return badge;
|
||||||
})
|
})
|
||||||
|
@ -27,7 +31,7 @@ export const BadgeConfigSchema = () =>
|
||||||
export type Badge = z.output<ReturnType<typeof badgeSchema>>;
|
export type Badge = z.output<ReturnType<typeof badgeSchema>>;
|
||||||
|
|
||||||
export interface Link {
|
export interface Link {
|
||||||
type: 'link';
|
type: "link";
|
||||||
label: string;
|
label: string;
|
||||||
href: string;
|
href: string;
|
||||||
isCurrent: boolean;
|
isCurrent: boolean;
|
||||||
|
@ -36,7 +40,7 @@ export interface Link {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Group {
|
interface Group {
|
||||||
type: 'group';
|
type: "group";
|
||||||
label: string;
|
label: string;
|
||||||
entries: (Link | Group)[];
|
entries: (Link | Group)[];
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue