Fixes and New Features...

This commit is contained in:
Adam Matthiesen 2024-01-20 04:52:55 -08:00
parent 6585fcebb8
commit 2d73445518
12 changed files with 186 additions and 67 deletions

108
index.ts
View File

@ -1,8 +1,17 @@
import type { AstroIntegration } from "astro" import type { AstroIntegration } from "astro";
import { ZodError, type SafeParseError, type SafeParseSuccess } from "astro/zod" import type { SafeParseError, SafeParseSuccess } from "astro/zod";
import { UserConfigSchema, type UserConfig } from "./src/utils/UserConfigSchema" import { UserConfigSchema, type UserConfig } from "./src/utils/UserConfigSchema";
import { ghostSitemap, ghostRobots } from "./src/integrations";
import { loadEnv } from 'vite';
import { fromZodError } from "zod-validation-error";
const pkg = '@matthiesenxyz/astro-ghostcms' // LOAD ENVIRONMENT VARIABLES
const mode = 'all';
const prefixes = 'CONTENT_API';
const env = loadEnv(mode, process.cwd(), prefixes);
// SET LOCAL PACKAGE NAME
const pkg = '@matthiesenxyz/astro-ghostcms';
export default function GhostCMS(options: UserConfig): AstroIntegration { export default function GhostCMS(options: UserConfig): AstroIntegration {
return { return {
@ -10,77 +19,108 @@ export default function GhostCMS(options: UserConfig): AstroIntegration {
hooks: { hooks: {
'astro:config:setup': async ({ 'astro:config:setup': async ({
injectRoute, injectRoute,
config,
logger, logger,
}) => { }) => {
// Check For ENV Variables
const o = UserConfigSchema.safeParse(options || {}) as SafeParseSuccess<UserConfig> logger.info("Checking for Environment Variables...")
// CHECK FOR API KEY
if(env.CONTENT_API_KEY === undefined){
logger.error("CONTENT_API_KEY Missing from .env")
}
// CHECK FOR API URL
if(env.CONTENT_API_URL === undefined){
logger.error("CONTENT_API_URL Missing from .env")
}
// CHECK USER CONFIG
logger.info("Checking Config...")
const o = UserConfigSchema.safeParse(options || {}) as SafeParseSuccess<UserConfig>;
if (!o.success) { if (!o.success) {
const validationError = fromZodError((o as unknown as SafeParseError<UserConfig>).error) const validationError = fromZodError((o as unknown as SafeParseError<UserConfig>).error);
logger.error(`Config Error - ${ validationError }`);
logger.error(`Config Error - ${ validationError }`) throw validationError;
throw validationError
} }
const entry = o.data.theme;
const entry = o.data.theme // THEME SELECTOR
if (entry === pkg) { if (entry === pkg) {
logger.info("Injecting Theme: astro-ghostcms-basetheme") logger.info("Injecting Theme: astro-ghostcms-basetheme");
} else { } else {
logger.info(`Injecting Theme: ${entry}`) logger.info(`Injecting Theme: ${entry}`);
} }
// INJECT ROUTES
logger.info("Injecting Routes...");
logger.info("Injecting Route: /")
injectRoute({ injectRoute({
pattern: '/', pattern: '/',
entrypoint: `${entry}/index.astro` entrypoint: `${entry}/index.astro`
}) });
logger.info("Injecting Route: /[slug]")
injectRoute({ injectRoute({
pattern: '/[slug]', pattern: '/[slug]',
entrypoint: `${entry}/[slug].astro` entrypoint: `${entry}/[slug].astro`
}) });
logger.info("Injecting Route: /tags")
injectRoute({ injectRoute({
pattern: '/tags', pattern: '/tags',
entrypoint: `${entry}/tags.astro` entrypoint: `${entry}/tags.astro`
}) });
logger.info("Injecting Route: /authors")
injectRoute({ injectRoute({
pattern: '/authors', pattern: '/authors',
entrypoint: `${entry}/authors.astro` entrypoint: `${entry}/authors.astro`
}) });
logger.info("Injecting Route: /tag/[slug]")
injectRoute({ injectRoute({
pattern: '/tag/[slug]', pattern: '/tag/[slug]',
entrypoint: `${entry}/tag/[slug].astro` entrypoint: `${entry}/tag/[slug].astro`
}) });
logger.info("Injecting Route: /author/[slug]")
injectRoute({ injectRoute({
pattern: '/author/[slug]', pattern: '/author/[slug]',
entrypoint: `${entry}/author/[slug].astro` entrypoint: `${entry}/author/[slug].astro`
}) });
logger.info("Injecting Route: /archives/[...page]")
injectRoute({ injectRoute({
pattern: '/archives/[...page]', pattern: '/archives/[...page]',
entrypoint: `${entry}/archives/[...page].astro` entrypoint: `${entry}/archives/[...page].astro`
}) });
// IMPORT INTEGRATIONS & INTEGRATION ROUTES
const int = [...config.integrations];
// IMPORT INTEGRATION: @ASTROJS/RSS
logger.info("Injecting Integration Route: @astrojs/rss");
injectRoute({
pattern: '/rss.xml',
entrypoint: `${pkg}/rss.xml.js`
});
// IMPORT INTEGRATION: @ASTROJS/SITEMAP
logger.info("Checking for @astrojs/sitemap");
if (!int.find(({ name }) => name === '@astrojs/sitemap')) {
logger.info("Injecting Integration: @astrojs/sitemap");
int.push(ghostSitemap());
} else {
logger.info("Already Imported by User: @astrojs/sitemap");
}
// IMPORT INTEGRATION: ASTRO-ROBOTS-TXT
logger.info("Checking for astro-robots-txt");
if (!int.find(({ name }) => name === 'astro-robots-txt')) {
logger.info("Injecting Integration: astro-robots-txt");
int.push(ghostRobots());
} else {
logger.info("Already Imported by User: astro-robots-txt");
}
}, },
'astro:config:done': async ({ logger }) => { 'astro:config:done': async ({ logger }) => {
logger.info('GhostCMS Routes Injected. Integration is now ready.') logger.info('GhostCMS Injection Complete. Integration is now ready.');
} }
} }
} }
} }
function fromZodError(error: ZodError<{ theme: string }>) {
throw new Error("Function not implemented.")
}

View File

@ -1,7 +1,7 @@
{ {
"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": "2.0.9", "version": "2.1.0",
"author": "MatthiesenXYZ (https://matthiesen.xyz)", "author": "MatthiesenXYZ (https://matthiesen.xyz)",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
@ -10,7 +10,8 @@
"url": "git+https://github.com/MatthiesenXYZ/astro-ghostcms.git" "url": "git+https://github.com/MatthiesenXYZ/astro-ghostcms.git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/MatthiesenXYZ/astro-ghostcms/issues" "url": "https://github.com/MatthiesenXYZ/astro-ghostcms/issues",
"email": "issues@astro-ghostcms.xyz"
}, },
"homepage": "https://astro-ghostcms.xyz/", "homepage": "https://astro-ghostcms.xyz/",
"exports": { "exports": {
@ -23,10 +24,10 @@
"./tag/[slug].astro": "./src/routes/tag/[slug].astro", "./tag/[slug].astro": "./src/routes/tag/[slug].astro",
"./author/[slug].astro": "./src/routes/author/[slug].astro", "./author/[slug].astro": "./src/routes/author/[slug].astro",
"./archives/[...page].astro": "./src/routes/archives/[...page].astro", "./archives/[...page].astro": "./src/routes/archives/[...page].astro",
"./DefaultLayout": "./src/layouts/default.astro" "./rss.xml.js": "./src/routes/rss.xml.js"
}, },
"main": "index.ts", "main": "index.ts",
"types": "src/api/ghosttypes.ts", "types": "src/api/tryghost-content-api.d.ts",
"files": [ "files": [
"src", "src",
"index.ts" "index.ts"
@ -56,11 +57,13 @@
"dependencies": { "dependencies": {
"@astrojs/check": "^0.3.4", "@astrojs/check": "^0.3.4",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"zod-validation-error": "^3.0.0",
"axios": "^1.0.0", "axios": "^1.0.0",
"astro-font": "^0.0.72", "astro-font": "^0.0.72",
"@astrojs/renderer-svelte": "0.5.2", "@astrojs/renderer-svelte": "0.5.2",
"@astrojs/rss": "^4.0.2", "@astrojs/rss": "^4.0.2",
"@astrojs/sitemap": "^3.0.4", "@astrojs/sitemap": "^3.0.4",
"astro-robots-txt": "^1.0.0",
"@snowpack/plugin-dotenv": "^2.2.0", "@snowpack/plugin-dotenv": "^2.2.0",
"@typescript-eslint/eslint-plugin": "^6.5.0", "@typescript-eslint/eslint-plugin": "^6.5.0",
"@typescript-eslint/parser": "^6.5.0", "@typescript-eslint/parser": "^6.5.0",

View File

@ -1,8 +1,26 @@
// IMPORT Ghost Types // IMPORT Ghost Types & Content-API
import type { PostOrPage, PostsOrPages, Authors, Tag, Tags, ArrayOrValue, IncludeParam, LimitParam, Settings, Nullable } from './ghosttypes'; import type {
PostOrPage, PostsOrPages, Authors,
Tag, Tags, ArrayOrValue, IncludeParam,
LimitParam, Settings, Nullable
} from './tryghost-content-api';
import GhostContentAPI from './tryghost-content-api';
// IMPORT Ghost API Client // LOAD ENVIRONMENT VARIABLES
import api from './interface'; import { loadEnv } from 'vite';
const {CONTENT_API_KEY, CONTENT_API_URL} = loadEnv(
'all',
process.cwd(),
'CONTENT_'
);
let key = CONTENT_API_KEY;
let url = CONTENT_API_URL;
// SETUP API
const version = "v5.0";
const api = GhostContentAPI({ key, url, version });
// SET Include params // SET Include params
const include:ArrayOrValue<IncludeParam> = ['authors', 'tags']; const include:ArrayOrValue<IncludeParam> = ['authors', 'tags'];
@ -10,54 +28,54 @@ const include:ArrayOrValue<IncludeParam> = ['authors', 'tags'];
// Get Posts (General "ALL") // Get Posts (General "ALL")
export const getGhostPosts = async () => { export const getGhostPosts = async () => {
const ghostPosts:PostsOrPages = await api.posts.browse({include,filter:'visibility:public'}) const ghostPosts:PostsOrPages = await api.posts.browse({include,filter:'visibility:public'})
return ghostPosts; } return ghostPosts; };
// Get Posts (Recent "setLimit?") // Get Posts (Recent "setLimit?")
export const getGhostRecentPosts = async (setLimit?:ArrayOrValue<LimitParam>) => { export const getGhostRecentPosts = async (setLimit?:ArrayOrValue<LimitParam>) => {
const ghostRecentPosts:PostsOrPages = await api.posts.browse({limit:setLimit?setLimit:"6",include,filter:'visibility:public'}); const ghostRecentPosts:PostsOrPages = await api.posts.browse({limit:setLimit?setLimit:"6",include,filter:'visibility:public'});
return ghostRecentPosts; } return ghostRecentPosts; };
// Get Posts (Featured "setLimit?") // Get Posts (Featured "setLimit?")
export const getGhostFeaturedPosts = async (setLimit?:ArrayOrValue<LimitParam>) => { export const getGhostFeaturedPosts = async (setLimit?:ArrayOrValue<LimitParam>) => {
const ghostFeaturedPosts:PostsOrPages = await api.posts.browse({limit:setLimit?setLimit:"1",include,filter:'featured:true'}); const ghostFeaturedPosts:PostsOrPages = await api.posts.browse({limit:setLimit?setLimit:"1",include,filter:'featured:true'});
return ghostFeaturedPosts; } return ghostFeaturedPosts; };
// Get Post (By Slug) // Get Post (By Slug)
export const getGhostPostbySlug = async (slug:Nullable<string>) => { export const getGhostPostbySlug = async (slug:Nullable<string>) => {
const ghostPostbySlug:PostOrPage = await api.posts.read({slug},{include}); const ghostPostbySlug:PostOrPage = await api.posts.read({slug},{include});
return ghostPostbySlug; } return ghostPostbySlug; };
// Get Post (By Tag) // Get Post (By Tag)
export const getGhostPostsbyTag = async (slug:Nullable<string>) => { export const getGhostPostsbyTag = async (slug:Nullable<string>) => {
const ghostPostsbyTag:PostsOrPages = await api.posts.browse({filter:`tag:${slug}`,include}); const ghostPostsbyTag:PostsOrPages = await api.posts.browse({filter:`tag:${slug}`,include});
return ghostPostsbyTag; } return ghostPostsbyTag; };
// Get Tags (General "ALL") // Get Tags (General "ALL")
export const getGhostTags = async () => { export const getGhostTags = async () => {
const ghostTags:Tags = await api.tags.browse({include:`count.posts`}); const ghostTags:Tags = await api.tags.browse({include:`count.posts`});
return ghostTags; } return ghostTags; };
// Get Tag (By Slug) // Get Tag (By Slug)
export const getGhostTagbySlug = async (slug:Nullable<string>) => { export const getGhostTagbySlug = async (slug:Nullable<string>) => {
const ghostTagbySlug:Tag = await api.tags.read({slug},{include:`count.posts`}); const ghostTagbySlug:Tag = await api.tags.read({slug},{include:`count.posts`});
return ghostTagbySlug; } return ghostTagbySlug; };
// Get Authors (General "ALL") // Get Authors (General "ALL")
export const getGhostAuthors = async () => { export const getGhostAuthors = async () => {
const ghostAuthors:Authors = await api.authors.browse({include:`count.posts`}); const ghostAuthors:Authors = await api.authors.browse({include:`count.posts`});
return ghostAuthors; } return ghostAuthors; };
// Get Pages (ALL) // Get Pages (ALL)
export const getGhostPages = async () => { export const getGhostPages = async () => {
const ghostPages:PostsOrPages = await api.pages.browse(); const ghostPages:PostsOrPages = await api.pages.browse();
return ghostPages; } return ghostPages; };
// Get Page (by Slug) // Get Page (by Slug)
export const getGhostPage = async (slug:Nullable<string>) => { export const getGhostPage = async (slug:Nullable<string>) => {
const ghostPage:PostOrPage = await api.pages.read({slug}); const ghostPage:PostOrPage = await api.pages.read({slug});
return ghostPage; } return ghostPage; };
// Get Settings // Get Settings
export const getGhostSettings = async () => { export const getGhostSettings = async () => {
const ghostSettings:Settings = await api.settings.browse(); const ghostSettings:Settings = await api.settings.browse();
return ghostSettings; } return ghostSettings; };

View File

@ -1,5 +1,22 @@
// FUNCTION EXPORTS // FUNCTION EXPORTS
export { getGhostPosts, getGhostRecentPosts, getGhostFeaturedPosts, getGhostPostbySlug, getGhostPostsbyTag, getGhostTags, getGhostTagbySlug, getGhostAuthors, getGhostPages, getGhostPage, getGhostSettings } from './functions'; export {
getGhostPosts, getGhostRecentPosts, getGhostFeaturedPosts,
getGhostPostbySlug, getGhostPostsbyTag, getGhostTags,
getGhostTagbySlug, getGhostAuthors, getGhostPages,
getGhostPage, getGhostSettings
} from './functions';
// TYPE EXPORTS // TYPE EXPORTS
export type { PostOrPage, ArrayOrValue, Author, Authors, BrowseFunction, CodeInjection, Excerpt, Facebook, FieldParam, FilterParam, FormatParam, GhostAPI, GhostContentAPIOptions, GhostData, GhostError, Identification, IncludeParam, LimitParam, Metadata, Nullable, OrderParam, PageParam, Pagination, Params, PostsOrPages, ReadFunction, Settings, SettingsResponse, SocialMedia, Tag, TagVisibility, Tags, Twitter } from './ghosttypes'; export type {
PostOrPage, ArrayOrValue, Author,
Authors, BrowseFunction, CodeInjection,
Excerpt, Facebook, FieldParam,
FilterParam, FormatParam, GhostAPI,
GhostContentAPIOptions, GhostData, GhostError,
Identification, IncludeParam, LimitParam,
Metadata, Nullable, OrderParam,
PageParam, Pagination, Params,
PostsOrPages, ReadFunction, Settings,
SettingsResponse, SocialMedia, Tag,
TagVisibility, Tags, Twitter
} from './tryghost-content-api.d';

View File

@ -1,10 +0,0 @@
import GhostContentAPI from './tryghost-content-api';
// CALL GHOST VARS AND CREATE CLIENT
const key = import.meta.env.CONTENT_API_KEY;
const url = import.meta.env.CONTENT_API_URL;
const version = "v5.0"
const api = new GhostContentAPI({ key, url, version })
export default api;

1
src/env.d.ts vendored
View File

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

View File

@ -0,0 +1,2 @@
export { default as ghostSitemap } from "./sitemap"
export { default as ghostRobots } from "./robots-txt"

View File

@ -0,0 +1,13 @@
import robotsTxt, { type RobotsTxtOptions } from "astro-robots-txt";
export function getRobotsTxtConfig(): RobotsTxtOptions {
const robotsConfig: RobotsTxtOptions = {};
return robotsConfig;
}
/**
* A wrapped version of the `astro-robots-txt` integration for GhostCMS.
*/
export default function ghostRobots() {
return robotsTxt(getRobotsTxtConfig());
}

View File

@ -0,0 +1,13 @@
import sitemap, { type SitemapOptions } from '@astrojs/sitemap';
export function getSitemapConfig(): SitemapOptions {
const sitemapConfig: SitemapOptions = {};
return sitemapConfig;
}
/**
* A wrapped version of the `@astrojs/sitemap` integration for GhostCMS.
*/
export default function ghostSitemap() {
return sitemap(getSitemapConfig());
}

23
src/routes/rss.xml.js Normal file
View File

@ -0,0 +1,23 @@
import rss from "@astrojs/rss";
import { getGhostPosts, getGhostSettings } from '../api';
import invariant from "tiny-invariant";
export async function GET(context) {
const posts = await getGhostPosts();
const settings = await getGhostSettings();
invariant(settings, "Settings not found");
const title = settings.title;
const description = settings.description;
return rss({
title: title,
description: description,
site: context.site,
//stylesheet: '/rss/styles.xsl',
items: posts.map((post) => ({
title: post.title,
pubDate: post.published_at,
description: post.excerpt,
link: `/blog/${post.slug}/`,
})),
});
}

View File

@ -1,7 +1,8 @@
import { z } from 'astro/zod'; import { z } from 'astro/zod';
export const UserConfigSchema = z.object({ export const UserConfigSchema = z.object({
theme: z.string().default('@matthiesenxyz/astro-ghostcms') theme: z.string().default('@matthiesenxyz/astro-ghostcms'),
rssStyle: z.string().optional()
}); });
export type UserConfig = z.infer<typeof UserConfigSchema> export type UserConfig = z.infer<typeof UserConfigSchema>