Compare commits

..

No commits in common. "main" and "@matthiesenxyz/starlight-ghostcms@0.0.4" have entirely different histories.

226 changed files with 3923 additions and 4588 deletions

View File

@ -7,5 +7,5 @@
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["astro-playground", "starlight-playground"]
"ignore": ["playground","starlight-playground"]
}

View File

@ -1,18 +0,0 @@
---
name: New Chore
about: Used to create a new Chore for a needed task
title: "Chore: [NAME]"
labels: chore
---
**Is your chore related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -1,38 +0,0 @@
branchName: 'issue-${issue.number}-${issue.title[0,32]}'
gitReplaceChars: '/<>'
autoLinkIssue: true
openDraftPR: true
prSkipCI: true
copyIssueDescriptionToPR: true
copyIssueLabelsToPR: true
copyIssueAssigneeToPR: true
copyIssueProjectsToPR: true
copyIssueMilestoneToPR: true
conventionalPrTitles: true
conventionalLabels:
fix:
bug: '🐛'
dependencies: '⬆️'
security: '🔒'
feat:
enhancement: '✨'
new-stuff: '🚀'
build:
build: '🔧'
chore:
chore: '♻️'
ci:
ci: '👷'
docs:
documentation: '📝'
style:
style: '💎'
refactor:
refactor: '♻️'
perf:
performance: '⚡️'
test:
test: '✅'
breaking:
breaking-change: '💥'
breaking change: '💥'

View File

@ -1,7 +1,8 @@
name: "Changesets: Build Changesets for Dependabot"
on: pull_request
#on: pull_request
on:
workflow_dispatch:
permissions:
contents: write

View File

@ -3,9 +3,6 @@ on:
push:
branches:
- main
permissions:
pull-requests: write
contents: write
env:
CI: true
jobs:
@ -20,19 +17,19 @@ jobs:
- name: setup node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 18
- name: install pnpm
run: npm i pnpm@latest -g
- name: Setup npmrc
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc
- name: install dependencies
run: pnpm install --no-frozen-lockfile
run: pnpm install
- name: create and publish versions
uses: matthiesenxyz/changeset@v1
uses: matthiesenxyz/changeset-action@v1
with:
version: pnpm ci:version
commit: "chore: update versions"
title: "👷 [ci]: Ready for Release"
title: "chore: update versions"
publish: pnpm ci:publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.ACTION_TOKEN }}

View File

@ -1,17 +0,0 @@
name: Write coauthors to a pull request
permissions:
pull-requests: write
on:
issue_comment:
types:
- created
jobs:
generate-coauthors:
name: Generate Coauthor
if: ${{ github.event.issue.pull_request }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: kevinzunigacuellar/coauthor-action@v0.1.2

View File

@ -12,7 +12,7 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: MatthiesenXYZ/git-sync-action@v1.1
- uses: jauderho/git-repo-sync@63782025e80e84c48b25a1ee6bb9a22a3bd570d3
with:
# Such as https://github.com/wangchucheng/git-repo-sync.git
target-url: ${{ secrets.GITLAB_URL }}

View File

@ -11,7 +11,6 @@ Don't like GitHub, and prefer GitLab? This Repo is Auto Cloned to Gitlab! Check
- [Default Theme](https://demo.astro-ghostcms.xyz) - ( [Github Repo](https://github.com/MatthiesenXYZ/demo-astroghostcms-themedefault) )
- [Catppuccin Theme](https://catppuccindark-demo.astro-ghostcms.xyz/) - ( [Github Repo](https://github.com/MatthiesenXYZ/demo-astroghostcms-catppuccin) )
- [Brutal by Elian Theme](https://brutal-demo.astro-ghostcms.xyz) - ( [Github Repo](https://github.com/MatthiesenXYZ/demo-astroghostcms-brutalbyelian) )
- [Starlight GhostCMS](https://starlightdemo.astro-ghostcms.xyz/) - ( [Github Repo](https://github.com/MatthiesenXYZ/demo-starlightghostcms) )
## Quick Start
@ -30,16 +29,16 @@ This repo is structured as a `pnpm` monorepo. All of our packages can be found
In this Repo you will find the Following:
- `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.
- `playground`: Development and Testing
- `packages/`:
- [`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-rendercontent`](./packages/astro-ghostcms-rendercontent/): Allows rendering of remote GhostCMS HTML with Custom Astro Components
- [`astro-ghostcms-theme-default`](./packages/astro-ghostcms-theme-default/): The Default theme in 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
- [`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

View File

@ -6,26 +6,26 @@
"node": ">=18.19.0"
},
"scripts": {
"astro:dev": "pnpm --filter astro-playground dev",
"dev": "pnpm --filter playground dev",
"starlight:dev": "pnpm --filter starlight-playground dev",
"lint": "biome check .",
"lint:fix": "biome check --apply .",
"ci:version": "pnpm changeset version",
"ci:publish": "pnpm changeset publish",
"ci:test:integration": "pnpm --filter astro-ghostcms test:ci",
"test:integration": "pnpm --filter astro-ghostcms test",
"test:integration:watch": "pnpm --filter astro-ghostcms test:watch",
"test:integration:coverage": "pnpm --filter astro-ghostcms test:coverage",
"test:create-utility": "pnpm --filter create-astro-ghostcms test",
"test:starlight": "pnpm --filter starlight-ghostcms test",
"test:starlight:watch": "pnpm --filter starlight-ghostcms test:watch",
"test:starlight:coverage": "pnpm --filter starlight-ghostcms test:coverage"
"ci:test:api": "pnpm --filter astro-ghostcms test:ci",
"test:api": "pnpm --filter astro-ghostcms test",
"test:api:watch": "pnpm --filter astro-ghostcms test:watch",
"test:api:coverage": "pnpm --filter astro-ghostcms test:coverage",
"test:create": "pnpm --filter create-astro-ghostcms test",
"test:slg": "pnpm --filter starlight-ghostcms test",
"test:slg:watch": "pnpm --filter starlight-ghostcms test:watch",
"test:slg:coverage": "pnpm --filter starlight-ghostcms test:coverage"
},
"devDependencies": {
"@biomejs/biome": "1.6.1",
"@biomejs/biome": "1.5.3",
"@changesets/cli": "^2.27.1",
"@vitest/ui": "^1.4.0",
"vitest": "^1.4.0",
"vitest-fetch-mock": "^0.2.2"
"vitest": "^1.3.0",
"vitest-fetch-mock": "^0.2.2",
"@vitest/ui": "1.3.0"
}
}

View File

@ -1,83 +1,5 @@
# @matthiesenxyz/astro-ghostcms-brutalbyelian
## 0.0.18
### Patch Changes
- f82035b: Bump dependencies:
- astro-integration-kit from to
- @unocss/astro from to
- @unocss/reset from to
- astro-font from to
- unocss from to
- sass from to
- @matthiesenxyz/astro-gists from to
- vite-tsconfig-paths from to
- astro from to
- Updated dependencies [f82035b]
- @matthiesenxyz/astro-ghostcms@3.3.5
## 0.0.17
### Patch Changes
- f097c6a: Adds a toggleswitch to allow users to disable astro-remote usage for rendering ghost-content
- Updated dependencies [f097c6a]
- @matthiesenxyz/astro-ghostcms@3.3.4
## 0.0.16
### Patch Changes
- b0218e1: fix:
- Removed CodeSlot Component that sometimes would break with some <pre> components
- Will be adding at a latter time a custom component to replace this, but at this time this is now fixed.
## 0.0.15
### Patch Changes
- 4c1002a: fix getAllTags bug for themes and bump other packages deps
- Updated dependencies [4c1002a]
- @matthiesenxyz/astro-ghostcms@3.3.2
## 0.0.14
### Patch Changes
- 746fcc5: Fix html rendering to allow custom components
## 0.0.13
### Patch Changes
- 2724119: Migrate from `astro-ghostcms-rendercontent` to `astro-remote` for internal processing of GhostCMS HTML. no user changes required.
## 0.0.12
### Patch Changes
- f921005: Bump dependencies:
- vite from to
- @eliancodes/brutal-ui from to
- typescript from to
- ultrahtml from to
- @fontsource-variable/inter from to
- astro-seo from to
- astro from to
- sass from to
- @astrojs/starlight from to
- sharp from to
- Updated dependencies [f921005]
- Updated dependencies [f921005]
- @matthiesenxyz/astro-ghostcms@3.3.1
- @matthiesenxyz/astro-ghostcms-rendercontent@0.0.8
## 0.0.11
### Patch Changes

View File

@ -1,7 +1,7 @@
{
"name": "@matthiesenxyz/astro-ghostcms-brutalbyelian",
"description": "ElianCodes Brutal theme modified to work with Astro-GhostCMS",
"version": "0.0.18",
"version": "0.0.11",
"homepage": "https://astro-ghostcms.xyz/",
"type": "module",
"license": "MIT",
@ -42,27 +42,29 @@
},
"scripts": {},
"peerDependencies": {
"@matthiesenxyz/astro-ghostcms": ">=3.3.5",
"astro": ">=4.4.1"
"astro": "^4.2.0"
},
"devDependencies": {
"@typescript-eslint/parser": "^7.2.0",
"eslint": "^8.57.0",
"@typescript-eslint/parser": "^7.0.1",
"eslint": "^8.56.0",
"eslint-plugin-astro": "^0.31.4",
"eslint-plugin-jsx-a11y": "^6.8.0",
"prettier-plugin-astro": "^0.13.0",
"prettier": "^3.2.5"
"prettier": "^3.2.5",
"@matthiesenxyz/astro-ghostcms": "workspace:*",
"@matthiesenxyz/astro-ghostcms-rendercontent": "workspace:*"
},
"dependencies": {
"@eliancodes/brutal-ui": "^0.2.6",
"@iconify-json/logos": "^1.1.41",
"@iconify-json/uil": "^1.1.8",
"@unocss/astro": "^0.58.6",
"@unocss/reset": "^0.58.6",
"astro-font": "^0.0.78",
"astro-remote": "0.3.2",
"typescript": "^5.4.2",
"ultrahtml": "^1.5.3",
"unocss": "^0.58.6"
"@matthiesenxyz/astro-ghostcms": "^3.2.9",
"@matthiesenxyz/astro-ghostcms-rendercontent": "^0.0.7",
"@unocss/astro": "^0.58.5",
"@unocss/reset": "^0.58.5",
"unocss": "^0.58.5",
"@eliancodes/brutal-ui": "^0.2.3",
"astro-font": "^0.0.77",
"typescript": "^5.3.3",
"ultrahtml": "^1.5.2"
}
}

View File

@ -1,11 +1,6 @@
---
import { getGhostImgPath } from "../../utils";
import type { Settings } from "@matthiesenxyz/astro-ghostcms/api";
import { Markup } from 'astro-remote';
import config from "virtual:@matthiesenxyz/astro-ghostcms/config";
const useRemote = config.ThemeProvider.astroRemote.enable;
export type Props = {
image: string;
alt?: string;
@ -27,8 +22,5 @@ const { image, alt, caption = "", settings, transitionName } = Astro.props as Pr
alt={alt}
transition:name={transitionName}
/>
{caption && (
<figcaption class="text-ctp-overlay2">
{useRemote ? <Markup content={caption} /> : <Fragment set:html={caption} />}
</figcaption>)}
{caption && <figcaption class="text-ctp-overlay2"><Fragment set:html={caption}></figcaption>}
</figure>

View File

@ -0,0 +1,12 @@
---
import { Code } from "astro/components"
import { parse } from "ultrahtml"
import { querySelector } from "ultrahtml/selector"
const html = await Astro.slots.render("default")
const ast = await parse(html)
const codetag = querySelector(ast,'code')
const { children } = codetag
const code = children[0].value
---
<Code code={code} lang={"sh"} theme={"monokai"}/>

View File

@ -4,5 +4,6 @@ export { default as H3 } from "./H3.astro";
export { default as H4 } from "./H4.astro";
export { default as H5 } from "./H5.astro";
export { default as H6 } from "./H6.astro";
export { default as CodeSlot } from "./CodeSlot.astro";
export { default as Paragraph } from "./Paragraph.astro";
export { default as astrocard } from "./astrocard.astro";

View File

@ -4,12 +4,9 @@ import { getAllPosts, getAllPages, getSettings, invariant } from "@matthiesenxyz
import type { InferGetStaticPropsType } from "astro";
import RecentBlogPosts from "../components/generic/RecentBlogPosts.astro";
import BlogPostHeader from "../components/blog/BlogPostHeader.astro";
import { Markup } from 'astro-remote';
import { GhostRender } from "@matthiesenxyz/astro-ghostcms-rendercontent";
import * as C from "../components/ghostrender";
import { Card } from "@eliancodes/brutal-ui";
import config from "virtual:@matthiesenxyz/astro-ghostcms/config";
const useRemote = config.ThemeProvider.astroRemote.enable;
export async function getStaticPaths() {
const [posts, pages, settings] = await Promise.all([getAllPosts(), await getAllPages(), await getSettings()]);
@ -33,12 +30,8 @@ invariant(settings, "Settings are required");
<div class="my-5"/>
<div id="ghost">
{useRemote ? <Markup
<GhostRender
content={post.html}
sanitize={{
allowComponents: true,
allowElements: ['a', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img', 'figure', 'figcaption', 'ul', 'ol', 'li', 'blockquote', 'pre', 'code', 'em', 'strong', 'del', 'hr', 'br', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'caption', 'div', 'span', 'script', 'astrocard'],
}}
components={{
h1: C.H1,
h2: C.H2,
@ -46,10 +39,11 @@ invariant(settings, "Settings are required");
h4: C.H4,
h5: C.H5,
h6: C.H6,
pre: C.CodeSlot,
p: C.Paragraph,
astrocard: C.astrocard,
}}
/> : <Fragment set:html={post.html} />}
/>
</div>
</article>

View File

@ -8,7 +8,7 @@ import { getAllPosts, getAllTags, getSettings, invariant } from "@matthiesenxyz/
export async function getStaticPaths() {
const posts = await getAllPosts();
const tags = await getAllTags();
const { tags } = await getAllTags();
const settings = await getSettings();
return tags.map((tag) => {

View File

@ -2,7 +2,7 @@
import Layout from '../layouts/Default.astro';
import { getAllTags, getSettings, invariant } from "@matthiesenxyz/astro-ghostcms/api";
import TagSummaryCard from '../components/generic/TagSummaryCard.astro';
const tags = await getAllTags();
const { tags } = await getAllTags();
const settings = await getSettings();
invariant(settings, 'Settings not found');
const title = settings.title;
@ -16,7 +16,7 @@ const description = settings.description;
description={description}
>
<main class='bg-pink p-6'>
{
{
tags
.filter((tag) => tag.slug && !tag.slug.startsWith("hash-"))
.map((tag) => (

View File

@ -1,62 +1,5 @@
# @matthiesenxyz/astro-ghostcms-catppuccin
## 0.0.14
### Patch Changes
- f097c6a: Adds a toggleswitch to allow users to disable astro-remote usage for rendering ghost-content
- Updated dependencies [f097c6a]
- @matthiesenxyz/astro-ghostcms@3.3.4
## 0.0.13
### Patch Changes
- b0218e1: fix:
- Removed CodeSlot Component that sometimes would break with some <pre> components
- Will be adding at a latter time a custom component to replace this, but at this time this is now fixed.
## 0.0.12
### Patch Changes
- 4c1002a: fix getAllTags bug for themes and bump other packages deps
- Updated dependencies [4c1002a]
- @matthiesenxyz/astro-ghostcms@3.3.2
## 0.0.11
### Patch Changes
- 746fcc5: Fix html rendering to allow custom components
## 0.0.10
### Patch Changes
- 2724119: Migrate to `astro-remote` for internal processing of GhostCMS HTML. No user changes required.
## 0.0.9
### Patch Changes
- f921005: Bump dependencies:
- vite from to
- @eliancodes/brutal-ui from to
- typescript from to
- ultrahtml from to
- @fontsource-variable/inter from to
- astro-seo from to
- astro from to
- sass from to
- @astrojs/starlight from to
- sharp from to
- Updated dependencies [f921005]
- @matthiesenxyz/astro-ghostcms@3.3.1
## 0.0.8
### Patch Changes

View File

@ -1,7 +1,7 @@
{
"name": "@matthiesenxyz/astro-ghostcms-catppuccin",
"description": "A dark theme made with Catppuccin and TailwindCSS for Astro-GhostCMS",
"version": "0.0.14",
"version": "0.0.8",
"homepage": "https://astro-ghostcms.xyz/",
"type": "module",
"license": "MIT",
@ -50,8 +50,7 @@
},
"scripts": {},
"peerDependencies": {
"@matthiesenxyz/astro-ghostcms": ">=3.3.5",
"astro": ">=4.4.0"
"astro": "^4.2.1"
},
"devDependencies": {
"@matthiesenxyz/astro-ghostcms": "workspace:*"
@ -59,13 +58,11 @@
"dependencies": {
"@astrojs/tailwind": "^5.1.0",
"@catppuccin/tailwindcss": "0.1.6",
"@fontsource-variable/inter": "^5.0.17",
"@matthiesenxyz/astro-ghostcms": "^3.3.5",
"@fontsource-variable/inter": "^5.0.16",
"@matthiesenxyz/astro-ghostcms": "^3.2.9",
"@tailwindcss/typography": "^0.5.10",
"astro-navbar": "^2.3.1",
"astro-remote": "^0.3.2",
"astro-seo": "^0.8.3",
"tailwindcss": "^3.3.5",
"ultrahtml": "1.5.3"
"astro-navbar": "^2.3.1",
"astro-seo": "^0.8.2"
}
}

View File

@ -3,11 +3,6 @@ import type { Settings, Post } from "@matthiesenxyz/astro-ghostcms/api";
import FeatureImage from "./FeatureImage.astro";
import AuthorList from "./AuthorList.astro";
import { formatDate } from "../utils";
import { Markup } from 'astro-remote';
import config from "virtual:@matthiesenxyz/astro-ghostcms/config";
const useRemote = config.ThemeProvider.astroRemote.enable;
export type Props = {
posts: Post[];
settings: Settings;
@ -64,15 +59,7 @@ const latestFeatured = posts[0]
</div>
<div class="divider my-4"/>
<section id="ghostimport" class="text-ctp-subtext1">
{useRemote ? (latestFeatured && <Markup
content={latestFeatured.html}
sanitize={{
allowComponents: true,
allowElements: ['a', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img', 'figure', 'figcaption', 'ul', 'ol', 'li', 'blockquote', 'pre', 'code', 'em', 'strong', 'del', 'hr', 'br', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'caption', 'div', 'span', 'script', 'astrocard'],
}}
/>) : (latestFeatured && <Fragment set:html={latestFeatured.html} />) }
{latestFeatured && <Fragment set:html={latestFeatured.html} />}
</section>
</div>
</main>

View File

@ -2,11 +2,6 @@
import PostHero from "../components/PostHero.astro";
import PostFooter from "../components/PostFooter.astro";
import { getFeaturedPosts, invariant, type Post, type Settings } from "@matthiesenxyz/astro-ghostcms/api";
import { Markup } from 'astro-remote';
import config from "virtual:@matthiesenxyz/astro-ghostcms/config";
const useRemote = config.ThemeProvider.astroRemote.enable;
export type Props = {
post: Post;
settings: Settings;
@ -19,10 +14,10 @@ async function getPostsSet(){
const featuredPosts = await getFeaturedPosts();
const fposts = posts;
if(featuredPosts.posts.length === 0){ return fposts }
const featured = featuredPosts.posts[0]
return fposts.filter((p: Post)=>p.id !== featured.id)
else {
const featured = featuredPosts.posts[0]
return fposts.filter((p: Post)=>p.id !== featured.id)
}
}
const mPosts = await getPostsSet()
@ -32,15 +27,7 @@ invariant(settings, "Settings not found");
<PostHero post={post} settings={settings} />
<div id="ghostimport" class="mt-4 text-ctp-subtext1">
{useRemote ? (
<Markup
content={post.html}
sanitize={{
allowComponents: true,
allowElements: ['a', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img', 'figure', 'figcaption', 'ul', 'ol', 'li', 'blockquote', 'pre', 'code', 'em', 'strong', 'del', 'hr', 'br', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'caption', 'div', 'span', 'script', 'astrocard'],
}}
/>
) : <Fragment set:html={post.html} /> }
<Fragment set:html={post.html} />
</div>
<PostFooter post={post} settings={settings} posts={mPosts} />

View File

@ -7,7 +7,7 @@ import PostPreview from '../../components/PostPreview.astro';
export async function getStaticPaths() {
const posts = await getAllPosts();
const tags = await getAllTags();
const { tags } = await getAllTags();
const settings = await getSettings();
return tags.map((tag) => {
@ -37,7 +37,7 @@ const description = `All of the articles we've posted and linked so far under th
<Layout title={title} description={description} settings={settings}>
<Container>
<section class="post-card post-card-large">
<div class="post-card-content">
<div class="post-card-content-link">
<header class="post-card-header">

View File

@ -6,7 +6,7 @@ import { getAllTags, getSettings, invariant } from "@matthiesenxyz/astro-ghostcm
let title = "All Tags";
let description = "All the tags used so far...";
const tags = await getAllTags();
const { tags } = await getAllTags();
const settings = await getSettings();
invariant(settings, 'Settings not found');

View File

@ -0,0 +1,37 @@
# @matthiesenxyz/astro-ghostcms-rendercontent
## 0.0.7
### Patch Changes
- 1f850db: Bump dependencies:
- astro from to
- vite from to
- astro-seo from to
- sass from to
## 0.0.6
### Patch Changes
- 3a5aea9: Updated Tests and Linted packages
## 0.0.5
### Patch Changes
- 455ad3f: Bump astro from 4.0.0 to 4.3.7
- 12be739: Depencency updates
## 0.0.4
### Patch Changes
- Fix License File
## 0.0.3
### Patch Changes
- Initialization of changeset cli

View File

@ -0,0 +1,33 @@
# Astro GhostCMS Content API HTML Processor
Render remote GhostCMS HTML in Astro with full control over the output.
Powered by [`ultrahtml`](https://github.com/natemoo-re/ultrahtml).
## Rendering Remote Content
The most basic function of `astro-ghostcms-rendercontent` is to convert a string of HTML to Astro friendly HTML. Use the `GhostRender` component.
```astro
---
import { GhostRender } from "@matthiesenxyz/astro-ghostcms-rendercontent";
---
<GhostRender content={post.html} />
```
### Customization
`GhostRender` allows full control over the rendering of output. The `components` option allows you to replace a standard HTML element with a custom component.
```astro
---
import { GhostRender } from "@matthiesenxyz/astro-ghostcms-rendercontent";
import Title from '../components/Title.astro';
---
<!-- Render <h1> as custom <Title> component -->
<GhostRender content={post.html} components={{ h1: Title }} />
```
For examples on how to setup custom components check [examples](./examples/)

View File

@ -0,0 +1,12 @@
---
import { Code } from "astro/components"
import { parse } from "ultrahtml"
import { querySelector } from "ultrahtml/selector"
const html = await Astro.slots.render("default")
const ast = await parse(html)
const codetag = querySelector(ast,'code')
const { children } = codetag
const code = children[0].value
---
<Code code={code} lang={"sh"} theme={"monokai"}/>

View File

@ -0,0 +1,5 @@
---
---
<h1 class="righteous">
<slot />
</h1>

View File

@ -0,0 +1,5 @@
---
---
<h2 class="righteous">
<slot />
</h2>

View File

@ -0,0 +1,5 @@
---
---
<h3 class="righteous">
<slot />
</h3>

View File

@ -0,0 +1,5 @@
---
---
<h4 class="righteous">
<slot />
</h4>

View File

@ -0,0 +1,5 @@
---
---
<h5 class="righteous">
<slot />
</h5>

View File

@ -0,0 +1,5 @@
---
---
<h6 class="righteous">
<slot />
</h6>

View File

@ -0,0 +1,21 @@
---
---
<p class="my-5"><slot /></p>
<style is:inline>
#ghost p a {
color: rgb(0, 123, 247);
}
#ghost ul li a {
color: rgb(0, 123, 247);
}
#ghost ul li {
padding-top: 0.5rem;
margin-left: 2rem;
list-style: circle;
}
#ghost ul {
margin-top: 1rem;
margin-bottom: 1rem;
}
</style>

View File

@ -0,0 +1,8 @@
export { default as H1 } from "./H1.astro";
export { default as H2 } from "./H2.astro";
export { default as H3 } from "./H3.astro";
export { default as H4 } from "./H4.astro";
export { default as H5 } from "./H5.astro";
export { default as H6 } from "./H6.astro";
export { default as CodeSlot } from "./CodeSlot.astro";
export { default as Paragraph } from "./Paragraph.astro";

View File

@ -0,0 +1 @@
export { default as GhostRender } from "./lib/GhostRender.astro";

View File

@ -0,0 +1,18 @@
---
import { createComponentProxy, html } from './utils';
export interface Props {
content?: string;
components?: Record<string, any>;
}
const input = Astro.props.content ?? await Astro.slots.render('default');
if (!input) {
throw new Error('Unable to render <GhostRender> without a content prop or children')
}
// @ts-ignore
const components = createComponentProxy($$result, Astro.props.components);
const content = await html(input, { components });
---
<Fragment set:html={content} />

View File

@ -0,0 +1 @@
/// <reference types="astro/client" />

View File

@ -0,0 +1,59 @@
import { jsx as h } from "astro/jsx-runtime";
import { renderJSX } from "astro/runtime/server/jsx";
import * as entities from "entities";
import { transform } from "ultrahtml";
import { __unsafeHTML } from "ultrahtml";
import swap from "ultrahtml/transformers/swap";
export function createComponentProxy(
result,
_components: Record<string, any> = {},
) {
const components = {};
for (const [key, value] of Object.entries(_components)) {
if (typeof value === "string") {
components[key] = value;
} else {
components[key] = async (props, children) => {
if (key === "CodeBlock" || key === "CodeSpan") {
props.code = entities.decode(JSON.parse(`"${props.code}"`));
}
const output = await renderJSX(
result,
h(value, { ...props, "set:html": children.value }),
);
return __unsafeHTML(output);
};
}
}
return components;
}
function getIndent(ln: string): string {
if (ln.trim() === ln) return "";
return ln.slice(0, ln.length - ln.trim().length);
}
export function dedent(str: string): string {
const lns = str.replace(/^[\r\n]+/, "").split("\n");
let indent = getIndent(lns[0]);
if (indent.length === 0 && lns.length > 1) {
indent = getIndent(lns[1]);
}
return lns
.map((ln) => (ln.startsWith(indent) ? ln.slice(indent.length) : ln))
.map((ln, i, { length }) => (i === length - 1 ? ln.trim() : ln))
.join("\n");
}
export interface HTMLOptions {
// biome-ignore lint/complexity/noBannedTypes: <explanation>
components?: {};
}
export async function html(
input: string,
opts: HTMLOptions = {},
): Promise<string> {
return transform(dedent(input), [swap(opts.components)]);
}

View File

@ -0,0 +1,47 @@
{
"name": "@matthiesenxyz/astro-ghostcms-rendercontent",
"type": "module",
"version": "0.0.7",
"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"
},
"publishConfig": {
"access": "public"
},
"sideEffects": false,
"homepage": "https://astro-ghostcms.xyz",
"files": [
"examples",
"lib",
"index.ts",
"CHANGELOG.md"
],
"types": "./index.ts",
"exports": {
".": "./index.ts",
"./lib/*": "./lib/*",
"./examples/*": "./examples/*",
"./package.json": "./package.json"
},
"keywords": [
"astro-ghostcms"
],
"author": {
"email": "adam@matthiesen.xyz",
"name": "Adam Matthiesen - MatthiesenXYZ",
"url": "https://matthiesen.xyz"
},
"license": "MIT",
"dependencies": {
"entities": "^4.5.0",
"ultrahtml": "^1.5.2"
},
"devDependencies": {
"astro": "4.4.0"
}
}

View File

@ -1,81 +1,5 @@
# @matthiesenxyz/astro-ghostcms-theme-default
## 0.1.20
### Patch Changes
- f82035b: Bump dependencies:
- astro-integration-kit from to
- @unocss/astro from to
- @unocss/reset from to
- astro-font from to
- unocss from to
- sass from to
- @matthiesenxyz/astro-gists from to
- vite-tsconfig-paths from to
- astro from to
- Updated dependencies [f82035b]
- @matthiesenxyz/astro-ghostcms@3.3.5
## 0.1.19
### Patch Changes
- f097c6a: Adds a toggleswitch to allow users to disable astro-remote usage for rendering ghost-content
- Updated dependencies [f097c6a]
- @matthiesenxyz/astro-ghostcms@3.3.4
## 0.1.18
### Patch Changes
- b0218e1: fix:
- Removed CodeSlot Component that sometimes would break with some <pre> components
- Will be adding at a latter time a custom component to replace this, but at this time this is now fixed.
## 0.1.17
### Patch Changes
- 4c1002a: fix getAllTags bug for themes and bump other packages deps
- Updated dependencies [4c1002a]
- @matthiesenxyz/astro-ghostcms@3.3.2
## 0.1.16
### Patch Changes
- 746fcc5: Fix html rendering to allow custom components
## 0.1.15
### Patch Changes
- 2724119: Migrate to `astro-remote` for internal processing of GhostCMS HTML. No user changes required.
## 0.1.14
### Patch Changes
- f921005: Bump dependencies:
- vite from to
- @eliancodes/brutal-ui from to
- typescript from to
- ultrahtml from to
- @fontsource-variable/inter from to
- astro-seo from to
- astro from to
- sass from to
- @astrojs/starlight from to
- sharp from to
- Updated dependencies [f921005]
- @matthiesenxyz/astro-ghostcms@3.3.1
## 0.1.13
### Patch Changes

View File

@ -1,7 +1,7 @@
{
"name": "@matthiesenxyz/astro-ghostcms-theme-default",
"description": "Default Theme for astro-ghostcms",
"version": "0.1.20",
"version": "0.1.13",
"homepage": "https://astro-ghostcms.xyz/",
"type": "module",
"license": "MIT",
@ -43,17 +43,15 @@
"scripts": {},
"devDependencies": {
"@matthiesenxyz/astro-ghostcms": "*",
"@astrojs/check": "^0.5.9",
"typescript": "^5.4.2"
"@astrojs/check": "^0.5.4",
"typescript": "^5.3.3"
},
"peerDependencies": {
"@matthiesenxyz/astro-ghostcms": ">=3.3.5",
"astro": ">=4.4.0"
"astro": "^4.2.1"
},
"dependencies": {
"astro-font": "^0.0.78",
"astro-remote": "^0.3.2",
"sass": "^1.72.0",
"ultrahtml": "^1.5.3"
"@matthiesenxyz/astro-ghostcms": "^3.2.9",
"astro-font": "^0.0.77",
"sass": "^1.71.0"
}
}

View File

@ -1,11 +1,6 @@
---
import FeatureImage from "../components/FeatureImage.astro";
import type { Settings, Page } from "@matthiesenxyz/astro-ghostcms/api";
import { Markup } from 'astro-remote';
import config from "virtual:@matthiesenxyz/astro-ghostcms/config";
const useRemote = config.ThemeProvider.astroRemote.enable;
export type Props = {
page: Page;
settings: Settings;
@ -29,15 +24,7 @@ const { page, settings, pageClass } = Astro.props as Props;
<section class="gh-content gh-canvas">
<h1 class="article-title">{page.title}</h1>
{useRemote ? (
<Markup
content={page.html}
sanitize={{
allowComponents: true,
allowElements: ['a', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img', 'figure', 'figcaption', 'ul', 'ol', 'li', 'blockquote', 'pre', 'code', 'em', 'strong', 'del', 'hr', 'br', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'caption', 'div', 'span', 'script', 'astrocard'],
}}
/>
) : <Fragment set:html={page.html} /> }
<Fragment set:html={page.html} />
</section>
</article>
</main>

View File

@ -2,11 +2,6 @@
import PostHero from "../components/PostHero.astro";
import PostFooter from "../components/PostFooter.astro";
import {invariant, type Post, type Settings } from "@matthiesenxyz/astro-ghostcms/api";
import { Markup } from 'astro-remote';
import config from "virtual:@matthiesenxyz/astro-ghostcms/config";
const useRemote = config.ThemeProvider.astroRemote.enable;
export type Props = {
post: Post;
settings: Settings;
@ -21,15 +16,7 @@ invariant(settings, "Settings not found");
<article class={`article post ${postClass}`}>
<PostHero post={post} settings={settings} />
<section class="gh-content gh-canvas">
{useRemote ? (
<Markup
content={post.html}
sanitize={{
allowComponents: true,
allowElements: ['a', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img', 'figure', 'figcaption', 'ul', 'ol', 'li', 'blockquote', 'pre', 'code', 'em', 'strong', 'del', 'hr', 'br', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'caption', 'div', 'span', 'script', 'astrocard'],
}}
/>
) : <Fragment set:html={post.html} /> }
<Fragment set:html={post.html} />
</section>
</article>
<PostFooter post={post} settings={settings} posts={posts} />

View File

@ -1,13 +1,13 @@
---
import { getAllPosts, getAllTags, getSettings, invariant } from "@matthiesenxyz/astro-ghostcms/api";
import type { InferGetStaticParamsType, InferGetStaticPropsType } from 'astro';
import PostPreview from "../../components/PostPreview.astro";
import DefaultPageLayout from "../../layouts/default.astro";
import PostPreview from "../../components/PostPreview.astro";
import { getAllPosts, getAllTags, getSettings, invariant } from "@matthiesenxyz/astro-ghostcms/api";
import { getGhostImgPath } from "../../utils";
export async function getStaticPaths() {
const posts = await getAllPosts();
const tags = await getAllTags();
const { tags } = await getAllTags();
const settings = await getSettings();
return tags.map((tag) => {

View File

@ -1,13 +1,13 @@
---
import { getAllTags, getSettings, invariant } from "@matthiesenxyz/astro-ghostcms/api";
import TagCard from "../components/TagCard.astro";
import DefaultPageLayout from "../layouts/default.astro";
import TagCard from "../components/TagCard.astro";
import { getSettings, getAllTags, invariant } from "@matthiesenxyz/astro-ghostcms/api";
let title = "All Tags";
let description = "All the tags used so far...";
const tags = await getAllTags();
const { tags } = await getAllTags();
const settings = await getSettings();
invariant(settings, "Settings not found");
---

View File

@ -0,0 +1,2 @@
CONTENT_API_KEY=a33da3965a3a9fb2c6b3f63b48
CONTENT_API_URL=https://ghostdemo.matthiesen.xyz

View File

@ -1,82 +1,5 @@
# @matthiesenxyz/astro-ghostcms
## 3.3.5
### Patch Changes
- f82035b: Bump dependencies:
- astro-integration-kit from to
- @unocss/astro from to
- @unocss/reset from to
- astro-font from to
- unocss from to
- sass from to
- @matthiesenxyz/astro-gists from to
- vite-tsconfig-paths from to
- astro from to
- Updated dependencies [f82035b]
- @matthiesenxyz/astro-ghostcms-theme-default@0.1.20
## 3.3.4
### Patch Changes
- f097c6a: Adds a toggleswitch to allow users to disable astro-remote usage for rendering ghost-content
- Updated dependencies [f097c6a]
- @matthiesenxyz/astro-ghostcms-theme-default@0.1.19
## 3.3.3
### Patch Changes
- b685e66: Update Deps
## 3.3.2
### Patch Changes
- 4c1002a: fix getAllTags bug for themes and bump other packages deps
- Updated dependencies [4c1002a]
- @matthiesenxyz/astro-ghostcms-theme-default@0.1.17
## 3.3.1
### Patch Changes
- f921005: Bump dependencies:
- vite from to
- @eliancodes/brutal-ui from to
- typescript from to
- ultrahtml from to
- @fontsource-variable/inter from to
- astro-seo from to
- astro from to
- sass from to
- @astrojs/starlight from to
- sharp from to
- Updated dependencies [f921005]
- @matthiesenxyz/astro-ghostcms-theme-default@0.1.14
## 3.3.0
### Minor Changes
- eebcd36: 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.
## 3.2.9
### Patch Changes

View File

@ -18,4 +18,4 @@ 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.
SOFTWARE.

View File

@ -59,34 +59,7 @@ import GhostCMS from '@matthiesenxyz/astro-ghostcms';
// https://astro.build/config
export default defineConfig({
site: "https://YOUR-DOMAIN-HERE.com"
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.
astroRemote: {
enable: true // OPTIONAL - Default True, Allows the user to switch to standard <Fragment> html rendering if they are having issues with Astro-remote
}
};
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
},
},
})
],
integrations: [GhostCMS()],
});
```
@ -140,3 +113,4 @@ For more information and to see the docs please check our website: [Astro-GhostC
# 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.

View File

@ -0,0 +1,359 @@
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}`,
);
},
},
};
}

View File

@ -1,7 +1,7 @@
{
"name": "@matthiesenxyz/astro-ghostcms",
"description": "Astro GhostCMS integration to allow easier importing of GhostCMS Content",
"version": "3.3.5",
"version": "3.2.9",
"homepage": "https://astro-ghostcms.xyz/",
"type": "module",
"license": "MIT",
@ -36,16 +36,36 @@
"url": "https://github.com/MatthiesenXYZ/astro-ghostcms/issues",
"email": "issues@astro-ghostcms.xyz"
},
"main": "index.ts",
"types": "types.d.ts",
"files": [
"src",
"CHANGELOG.md",
"LICENSE",
"README.md"
".env.demo",
"index.ts",
"tsconfig.json",
"types.d.ts"
],
"exports": {
".": "./src/index.ts",
"./api": "./src/api/index.ts",
"./satoriOG": "./src/integrations/satoriog/satori.ts"
".": "./index.ts",
"./api": {
"types": "./src/api/index.ts",
"default": "./src/api/index.ts"
},
"./api-core": "./src/api/content-api/index.ts",
"./config": {
"types": "./src/integrations/virtual.d.ts",
"default": "./src/integrations/virtual-config.ts"
},
"./satoriOG": "./src/integrations/satori.ts",
"./404.astro": "./src/default-routes/404/404.astro",
"./rss.xml.ts": "./src/default-routes/rss.xml.ts",
"./rss-style.xsl.ts": "./src/default-routes/rss-style.xsl.ts",
"./open-graph/index.png.ts": "./src/default-routes/open-graph/index.png.ts",
"./open-graph/authors.png.ts": "./src/default-routes/open-graph/authors.png.ts",
"./open-graph/tags.png.ts": "./src/default-routes/open-graph/tags.png.ts",
"./open-graph/[slug].png.ts": "./src/default-routes/open-graph/[slug].png.ts",
"./open-graph/author/[slug].png.ts": "./src/default-routes/open-graph/author/[slug].png.ts",
"./open-graph/tag/[slug].png.ts": "./src/default-routes/open-graph/tag/[slug].png.ts"
},
"scripts": {
"test": "vitest run",
@ -53,32 +73,39 @@
"test:coverage": "vitest run --coverage",
"test:ci": "vitest run --coverage.enabled --coverage.reporter='text-summary'"
},
"enginesStrict": {
"node": ">=18.19.0"
},
"peerDependencies": {
"astro": ">=4.4.1"
},
"devDependencies": {
"@types/fs-extra": "^11.0.4",
"@types/node": "^20.11.28",
"vitest": "^1.4.0",
"@astrojs/check": "^0.5.4",
"@ts-ghost/core-api": "*",
"@ts-ghost/tsconfig": "*",
"@matthiesenxyz/astro-ghostcms-theme-default": "*",
"@matthiesenxyz/astro-ghostcms-catppuccin": "*",
"@types/fs-extra": "^11.0.1",
"@types/node": "^20.11.19",
"@typescript-eslint/eslint-plugin": "^7.0.1",
"@typescript-eslint/parser": "^7.0.1",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-astro": "^0.31.4",
"prettier": "^3.2.5",
"prettier-plugin-astro": "^0.13.0",
"typescript": "^5.3.3",
"vitest": "^1.3.0",
"vitest-fetch-mock": "^0.2.2"
},
"dependencies": {
"@matthiesenxyz/astro-ghostcms-theme-default": "^0.1.13",
"@astrojs/rss": "^4.0.5",
"@astrojs/sitemap": "^3.1.1",
"@matthiesenxyz/astro-ghostcms-theme-default": "^0.1.20",
"@astrojs/sitemap": "^3.0.5",
"@resvg/resvg-js": "^2.6.0",
"@ts-ghost/core-api": "^6.0.0",
"@ts-ghost/content-api": "^4.0.12",
"astro-integration-kit": "^0.6.0",
"@ts-ghost/core-api": "^5.1.2",
"astro": "^4.4.0",
"astro-robots-txt": "^1.0.0",
"fs-extra": "^11.2.0",
"package-json": "^10.0.0",
"picocolors": "^1.0.0",
"satori": "^0.10.13",
"fs-extra": "^11.1.0",
"package-json": "9.0.0",
"satori": "^0.10.11",
"satori-html": "^0.3.2",
"vite": "^5.1.6"
"vite": "^5.1.3",
"vite-tsconfig-paths": "^4.2.2",
"zod": "^3.22.4"
}
}
}

View File

@ -1,24 +1,23 @@
import { TSGhostContentAPI } from "@ts-ghost/content-api";
import type { Page, Post, Tag } from "../schemas/api";
import { TS_API } from "./content-api";
import type { Page, Post } from "./content-api/schemas";
// LOAD ENVIRONMENT VARIABLES
import { loadEnv } from "vite";
import config from "../integrations/virtual-config";
const CONF_URL = config.ghostURL;
const { CONTENT_API_KEY, CONTENT_API_URL } = loadEnv(
"all",
process.cwd(),
"CONTENT_",
);
// LOAD CONFIG
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 ghostApiKey = CONTENT_API_KEY;
const ghostUrl = CONF_URL ? CONF_URL : CONTENT_API_URL;
const version = "v5.0";
const api = new TSGhostContentAPI(ghostUrl, ghostApiKey, version);
const api = new TS_API(ghostUrl, ghostApiKey, version);
export const getAllAuthors = async () => {
const results = await api.authors
@ -94,20 +93,17 @@ export const getSettings = async () => {
};
export const getAllTags = async () => {
const tags: Tag[] = [];
let cursor = await api.tags
.browse({
limit: 'all'
})
const results = await api.tags
.browse()
.include({ "count.posts": true })
.paginate();
if (cursor.current.success) tags.push(...cursor.current.data);
while (cursor.next) {
cursor = await cursor.next.paginate();
if (cursor.current.success) tags.push(...cursor.current.data);
.fetch();
if (!results.success) {
throw new Error(results.errors.map((e) => e.message).join(", "));
}
return tags;
return {
tags: results.data,
meta: results.meta,
};
};
export const getFeaturedPosts = async () => {

View File

@ -1,15 +1,11 @@
import { assert, beforeEach, describe, expect, test } from "vitest";
import { TSGhostContentAPI } from "@ts-ghost/content-api";
import TS_API from "./content-api";
describe("content-api", () => {
let api: TSGhostContentAPI;
let api: TS_API;
beforeEach(() => {
api = new TSGhostContentAPI(
"https://ghost.org",
"59d4bf56c73c04a18c867dc3ba",
"v5.0",
);
api = new TS_API("https://ghost.org", "59d4bf56c73c04a18c867dc3ba", "v5.0");
});
test("content-api", () => {
@ -18,25 +14,21 @@ describe("content-api", () => {
test("content-api shouldn't instantiate with an incorrect url", () => {
assert.throws(() => {
const api = new TSGhostContentAPI(
"ghost.org",
"59d4bf56c73c04a18c867dc3ba",
"v5.0",
);
const api = new TS_API("ghost.org", "59d4bf56c73c04a18c867dc3ba", "v5.0");
api.settings;
});
});
test("content-api shouldn't instantiate with an incorrect key", () => {
assert.throws(() => {
const api = new TSGhostContentAPI("https://ghost.org", "a", "v5.0");
const api = new TS_API("https://ghost.org", "a", "v5.0");
api.settings;
});
});
test("content-api shouldn't instantiate with an incorrect version", () => {
assert.throws(() => {
const api = new TSGhostContentAPI(
const api = new TS_API(
"https://ghost.org",
"1efedd9db174adee2d23d982:4b74dca0219bad629852191af326a45037346c2231240e0f7aec1f9371cc14e8",
// @ts-expect-error

View File

@ -0,0 +1,116 @@
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,
);
}
}

View File

@ -0,0 +1,8 @@
export { default as TS_API } from "./content-api";
export * from "./schemas";
export type {
InferFetcherDataShape,
InferResponseDataShape,
BrowseParams,
} from "@ts-ghost/core-api";

View File

@ -1,7 +1,7 @@
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import createFetchMock from "vitest-fetch-mock";
import { TSGhostContentAPI } from "@ts-ghost/content-api";
import TS_API from "../../content-api";
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 key =
process.env.VITE_GHOST_CONTENT_API_KEY || "59d4bf56c73c04a18c867dc3ba";
const api = new TSGhostContentAPI(url, key, "v5.0");
const api = new TS_API(url, key, "v5.0");
test(".browse() params shouldnt accept invalid params", () => {
// @ts-expect-error - shouldnt accept invalid params
const browse = api.authors.browse({ pp: 2 });
@ -95,10 +95,10 @@ describe("authors api .browse() Args Type-safety", () => {
});
describe("authors resource mocked", () => {
let api: TSGhostContentAPI;
let api: TS_API;
beforeEach(() => {
api = new TSGhostContentAPI(
api = new TS_API(
"https://my-ghost-blog.com",
"59d4bf56c73c04a18c867dc3ba",
"v5.0",

View File

@ -3,7 +3,7 @@ import {
ghostMetaSchema,
ghostMetadataSchema,
} from "@ts-ghost/core-api";
import { z } from "astro/zod";
import { z } from "zod";
export const authorsSchema = z.object({
...ghostIdentitySchema.shape,

View File

@ -0,0 +1 @@
export * from "./authors";

View File

@ -0,0 +1 @@
export * from "./socials";

View File

@ -1,7 +1,7 @@
export * from "./authors";
export * from "./helpers";
export * from "./pages";
export * from "./posts";
export * from "./settings";
export * from "./socials";
export * from "./tags";
export * from "./tiers";

View File

@ -0,0 +1 @@
export * from "./pages";

View File

@ -5,10 +5,10 @@ import {
ghostSocialMediaSchema,
ghostVisibilitySchema,
} from "@ts-ghost/core-api";
import { z } from "astro/zod";
import { z } from "zod";
import { authorsSchema } from "./authors";
import { tagsSchema } from "./tags";
import { authorsSchema } from "../authors";
import { tagsSchema } from "../tags";
const postsAuthorSchema = authorsSchema.extend({
url: z.string().nullish(),

View File

@ -0,0 +1 @@
export * from "./posts";

View File

@ -1,14 +1,14 @@
import { describe, expect, test } from "vitest";
import { TSGhostContentAPI } from "@ts-ghost/content-api";
import type { Post } from "./index";
import TS_API from "../../content-api";
import type { Post } from "./posts";
const url = process.env.VITE_GHOST_URL || "https://my-ghost-blog.com";
const key =
process.env.VITE_GHOST_CONTENT_API_KEY || "59d4bf56c73c04a18c867dc3ba";
describe("posts api .browse() Args Type-safety", () => {
const api = new TSGhostContentAPI(url, key, "v5.0");
const api = new TS_API(url, key, "v5.0");
test(".browse() params shouldnt accept invalid params", () => {
// @ts-expect-error - shouldnt accept invalid params
const browse = api.posts.browse({ pp: 2 });
@ -21,7 +21,8 @@ describe("posts api .browse() Args Type-safety", () => {
foo: true,
} satisfies { [k in keyof Post]?: true | undefined };
const test = api.posts
// biome-ignore lint/style/useConst: <explanation>
let test = api.posts
.browse()
// @ts-expect-error - shouldnt accept invalid params
.fields(outputFields);
@ -44,7 +45,8 @@ describe("posts api .browse() Args Type-safety", () => {
title: true,
} satisfies { [k in keyof Post]?: true | undefined };
const test = api.posts.browse().fields(outputFields);
// biome-ignore lint/style/useConst: <explanation>
let test = api.posts.browse().fields(outputFields);
expect(test.getOutputFields()).toEqual(["slug", "title"]);
// @ts-expect-error - shouldnt accept invalid params

View File

@ -5,10 +5,10 @@ import {
ghostSocialMediaSchema,
ghostVisibilitySchema,
} from "@ts-ghost/core-api";
import { z } from "astro/zod";
import { z } from "zod";
import { authorsSchema } from "./authors";
import { tagsSchema } from "./tags";
import { authorsSchema } from "../authors";
import { tagsSchema } from "../tags";
const postsAuthorSchema = authorsSchema.extend({
url: z.string().nullish(),

View File

@ -0,0 +1 @@
export * from "./settings";

View File

@ -1,4 +1,4 @@
import { z } from "astro/zod";
import { z } from "zod";
export const settingsSchema = z.object({
title: z.string(),

View File

@ -0,0 +1 @@
export * from "./tags";

View File

@ -5,7 +5,7 @@ import {
ghostSocialMediaSchema,
ghostVisibilitySchema,
} from "@ts-ghost/core-api";
import { z } from "astro/zod";
import { z } from "zod";
export const tagsSchema = z.object({
...ghostIdentitySchema.shape,

View File

@ -0,0 +1 @@
export * from "./tiers";

View File

@ -1,5 +1,5 @@
import { ghostIdentitySchema, ghostVisibilitySchema } from "@ts-ghost/core-api";
import { z } from "astro/zod";
import { z } from "zod";
export const tiersSchema = z.object({
...ghostIdentitySchema.shape,

View File

@ -1,3 +1,3 @@
export * from "./ghostAPI";
export * from "./api-functions";
export * from "./content-api/schemas";
export * from "./invariant";
export * from "../schemas/api/index";

View File

@ -20,6 +20,7 @@ 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.
*/
const tinyinvariant = "merged";
const isProduction: boolean = process.env.NODE_ENV === "production";
const prefix: string = "Invariant failed";

View File

@ -1,303 +0,0 @@
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 🚀")),
);
},
};
},
});

View File

@ -1,6 +1,6 @@
---
import './404.css';
import { getSettings, invariant } from '../api';
import { getSettings, invariant } from '../../api';
const settings = await getSettings();
invariant(settings, "Settings not found");

View File

@ -5,8 +5,13 @@ import type {
InferGetStaticPropsType,
} from "astro";
import { html } from "satori-html";
import { getAllPages, getAllPosts, getSettings, invariant } from "../../../api";
import satoriOG from "../satori";
import {
getAllPages,
getAllPosts,
getSettings,
invariant,
} from "../../api/index.js";
import satoriOG from "../../integrations/satori.js";
export const getStaticPaths: GetStaticPaths = async () => {
const result: GetStaticPathsItem[] = [];

View File

@ -10,8 +10,8 @@ import {
getAllPosts,
getSettings,
invariant,
} from "../../../../api";
import satoriOG from "../../satori";
} from "../../../api/index.js";
import satoriOG from "../../../integrations/satori.js";
export const getStaticPaths: GetStaticPaths = async () => {
const result: GetStaticPathsItem[] = [];

View File

@ -5,8 +5,13 @@ import type {
InferGetStaticPropsType,
} from "astro";
import { html } from "satori-html";
import { getAllPages, getAllPosts, getSettings, invariant } from "../../../api";
import satoriOG from "../satori";
import {
getAllPages,
getAllPosts,
getSettings,
invariant,
} from "../../api/index.js";
import satoriOG from "../../integrations/satori.js";
export const getStaticPaths: GetStaticPaths = async () => {
const result: GetStaticPathsItem[] = [];

View File

@ -5,8 +5,13 @@ import type {
InferGetStaticPropsType,
} from "astro";
import { html } from "satori-html";
import { getAllPages, getAllPosts, getSettings, invariant } from "../../../api";
import satoriOG from "../satori";
import {
getAllPages,
getAllPosts,
getSettings,
invariant,
} from "../../api/index.js";
import satoriOG from "../../integrations/satori.js";
export const getStaticPaths: GetStaticPaths = async () => {
const result: GetStaticPathsItem[] = [];

View File

@ -10,13 +10,13 @@ import {
getAllTags,
getSettings,
invariant,
} from "../../../../api";
import satoriOG from "../../satori";
} from "../../../api/index.js";
import satoriOG from "../../../integrations/satori.js";
export const getStaticPaths: GetStaticPaths = async () => {
const result: GetStaticPathsItem[] = [];
const posts = await getAllPosts();
const tags = await getAllTags();
const { tags } = await getAllTags();
const settings = await getSettings();
invariant(settings, "Settings are required");

View File

@ -5,8 +5,13 @@ import type {
InferGetStaticPropsType,
} from "astro";
import { html } from "satori-html";
import { getAllPages, getAllPosts, getSettings, invariant } from "../../../api";
import satoriOG from "../satori";
import {
getAllPages,
getAllPosts,
getSettings,
invariant,
} from "../../api/index.js";
import satoriOG from "../../integrations/satori.js";
export const getStaticPaths: GetStaticPaths = async () => {
const result: GetStaticPathsItem[] = [];

View File

@ -1,6 +1,6 @@
import rss from "@astrojs/rss";
import type { APIContext } from "astro";
import { getAllPosts, getSettings, invariant } from "../../../api";
import { getAllPosts, getSettings, invariant } from "../api";
const posts = await getAllPosts();
const settings = await getSettings();

View File

@ -1,3 +0,0 @@
import astroGhostCMS from "./astro-ghostcms";
export default astroGhostCMS;

View File

@ -0,0 +1,28 @@
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));
}

View File

@ -1,52 +0,0 @@
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")));
},
};
},
});

View File

@ -1,3 +0,0 @@
import astroGhostcmsRSS from "./astro-ghostcms-rss";
export default astroGhostcmsRSS;

View File

@ -5,7 +5,7 @@ import type {
ToImageOptions,
ToResponseOptions,
ToSvgOptions,
} from "./types";
} from "../../types.js";
const satoriOG = ({ width, height, template }: SatoriAstroOGOptions) => {
return {

View File

@ -1,84 +0,0 @@
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")));
},
};
},
});

View File

@ -1,3 +0,0 @@
import astroGhostcmsSatoriog from "./astro-ghostcms-satoriog";
export default astroGhostcmsSatoriog;

View File

@ -0,0 +1,38 @@
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));
}

View File

@ -1,99 +0,0 @@
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")));
},
};
},
});

View File

@ -1,3 +0,0 @@
import astroGhostcmsThemeProvider from "./astro-ghostcms-themeprovider";
export default astroGhostcmsThemeProvider;

View File

@ -0,0 +1,6 @@
import type { GhostUserConfig } from "../schemas";
import config from "virtual:@matthiesenxyz/astro-ghostcms/config";
const UserConfig = config as GhostUserConfig;
export default UserConfig;

View File

@ -1,4 +1,4 @@
declare module "virtual:@matthiesenxyz/astro-ghostcms/config" {
const Config: import("./src/schemas/userconfig").GhostUserConfig;
const Config: import("../schemas/index").GhostUserConfig;
export default Config;
}

View File

@ -1,39 +0,0 @@
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");
}
});
});

View File

@ -0,0 +1,3 @@
export * from "./robots";
export * from "./sitemap";
export * from "./userconfig";

Some files were not shown because too many files have changed in this diff Show More