massive lint

This commit is contained in:
Adam Matthiesen 2024-03-03 08:49:15 -08:00
parent 3192714355
commit 5f7ac8e4a6
54 changed files with 1005 additions and 797 deletions

View File

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

View File

@ -5,7 +5,11 @@ import { TSGhostContentAPI } from "@ts-ghost/content-api";
describe("content-api", () => {
let api: TSGhostContentAPI;
beforeEach(() => {
api = new TSGhostContentAPI("https://ghost.org", "59d4bf56c73c04a18c867dc3ba", "v5.0");
api = new TSGhostContentAPI(
"https://ghost.org",
"59d4bf56c73c04a18c867dc3ba",
"v5.0",
);
});
test("content-api", () => {
@ -14,7 +18,11 @@ 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 TSGhostContentAPI(
"ghost.org",
"59d4bf56c73c04a18c867dc3ba",
"v5.0",
);
api.settings;
});
});

View File

@ -4,10 +4,7 @@ import type { Page, Post } from "../schemas/api";
// LOAD ENVIRONMENT VARIABLES
import { loadEnv } from "vite";
const {
CONTENT_API_KEY,
CONTENT_API_URL
} = loadEnv(
const { CONTENT_API_KEY, CONTENT_API_URL } = loadEnv(
"all",
process.cwd(),
"CONTENT_",
@ -125,4 +122,4 @@ export const getFeaturedPosts = async () => {
posts: results.data,
meta: results.meta,
};
};
};

View File

@ -1,2 +1,2 @@
export * from "./ghostAPI";
export * from "./invariant";
export * from "./invariant";

View File

@ -44,4 +44,4 @@ export function invariant(
const value: string = provided ? `${prefix}: ${provided}` : prefix;
throw new Error(value);
}
}

View File

@ -1,22 +1,22 @@
import { createResolver, defineIntegration } from "astro-integration-kit";
import { corePlugins } from "astro-integration-kit/plugins";
import { GhostUserConfigSchema } from "./schemas/userconfig";
import { loadEnv } from "vite";
import { AstroError } from "astro/errors";
import c from "picocolors";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { createResolver, defineIntegration } from "astro-integration-kit";
import { corePlugins } from "astro-integration-kit/plugins";
import { AstroError } from "astro/errors";
import fse from "fs-extra";
import c from "picocolors";
import { loadEnv } from "vite";
import { GhostUserConfigSchema } from "./schemas/userconfig";
import latestVersion from "./utils/latestVersion";
// Import External Integrations
import sitemap from "@astrojs/sitemap";
import robotsTxt from "astro-robots-txt";
import ghostRSS from "./integrations/rssfeed";
// Import Internal Integrations
import ghostOGImages from "./integrations/satoriog"
import ghostRSS from "./integrations/rssfeed"
import ghostThemeProvider from "./integrations/themeprovider"
import ghostOGImages from "./integrations/satoriog";
import ghostThemeProvider from "./integrations/themeprovider";
// Load environment variables
const ENV = loadEnv("all", process.cwd(), "CONTENT_API");
@ -24,166 +24,283 @@ 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);
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,
addDts,
injectRoute,
logger
}) => {
const GhostLogger = logger.fork(c.bold(c.blue('👻 Astro-GhostCMS')));
const GhostENVLogger = logger.fork(`${c.bold(c.blue('👻 Astro-GhostCMS'))}${c.gray("/")}${c.blue('ENV Check')}`);
const GhostIntegrationLogger = logger.fork(`${c.bold(c.blue('👻 Astro-GhostCMS'))}${c.gray("/")}${c.blue('Integrations')}`);
const GhostRouteLogger = logger.fork(`${c.bold(c.blue('👻 Astro-GhostCMS'))}${c.gray("/")}${c.blue('Router')}`);
return {
"astro:config:setup": ({
watchIntegration,
hasIntegration,
addIntegration,
addVirtualImports,
addDts,
injectRoute,
logger,
}) => {
const GhostLogger = logger.fork(c.bold(c.blue("👻 Astro-GhostCMS")));
const GhostENVLogger = logger.fork(
`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.blue(
"ENV Check",
)}`,
);
const GhostIntegrationLogger = logger.fork(
`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.blue(
"Integrations",
)}`,
);
const GhostRouteLogger = logger.fork(
`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.blue(
"Router",
)}`,
);
watchIntegration(resolve())
GhostLogger.info('Initializing @matthiesenxyz/astro-ghostcms...')
watchIntegration(resolve());
GhostLogger.info("Initializing @matthiesenxyz/astro-ghostcms...");
const verbose = options.fullConsoleLogs;
const verbose = options.fullConsoleLogs;
// Check for GhostCMS environment variables
GhostENVLogger.info(c.bold(c.yellow('Checking for GhostCMS environment variables & user configuration')))
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 environment variables
GhostENVLogger.info(
c.bold(
c.yellow(
"Checking for GhostCMS environment variables & user configuration",
),
),
);
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`,
);
}
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')))
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')))
// Set up Astro-GhostCMS Integrations
GhostIntegrationLogger.info(
c.bold(c.magenta("Configuring Enabled Integrations")),
);
// Theme Provider
if ( !options.disableThemeProvider ) {
addIntegration(
ghostThemeProvider({
theme: options.ThemeProvider?.theme,
verbose
})
)
} else {
if (verbose) {
GhostIntegrationLogger.info(c.gray('Theme Provider is disabled'))
}
}
// Theme Provider
if (!options.disableThemeProvider) {
addIntegration(
ghostThemeProvider({
theme: options.ThemeProvider?.theme,
verbose,
}),
);
} else {
if (verbose) {
GhostIntegrationLogger.info(c.gray("Theme Provider is disabled"));
}
}
// Satori OG Images
if ( options.enableOGImages ) {
addIntegration(ghostOGImages({ verbose }))
} else {
if (verbose) {
GhostIntegrationLogger.info(c.gray('OG Image Provider is disabled'))
}
}
// Satori OG Images
if (options.enableOGImages) {
addIntegration(ghostOGImages({ verbose }));
} else {
if (verbose) {
GhostIntegrationLogger.info(
c.gray("OG Image Provider is disabled"),
);
}
}
// RSS Feed
if ( options.enableRSSFeed ) {
addIntegration(ghostRSS({ verbose }))
} else {
if (verbose) {
GhostIntegrationLogger.info(c.gray('RSS Feed is disabled'))
}
}
// RSS Feed
if (options.enableRSSFeed) {
addIntegration(ghostRSS({ verbose }));
} else {
if (verbose) {
GhostIntegrationLogger.info(c.gray("RSS Feed is disabled"));
}
}
// @ASTROJS/SITEMAP
if ( !hasIntegration("@astrojs/sitemap") ) {
if (verbose) {
GhostIntegrationLogger.info(c.bold(c.magenta(`Adding ${c.blue("@astrojs/sitemap")} integration`)))
}
addIntegration(sitemap())
} else {
if (verbose) {
GhostIntegrationLogger.info(c.gray('@astrojs/sitemap integration already exists, skipping...'))
}
}
// ASTRO-ROBOTS-TXT
if ( !hasIntegration("@astro-robots-txt") ) {
if (verbose) {
GhostIntegrationLogger.info(c.bold(c.magenta(`Adding ${c.blue("astro-robots-txt")} integration`)))
}
addIntegration(robotsTxt())
} else {
if (verbose) {
GhostIntegrationLogger.info(c.gray('astro-robots-txt integration already exists, skipping...'))
}
}
// @ASTROJS/SITEMAP
if (!hasIntegration("@astrojs/sitemap")) {
if (verbose) {
GhostIntegrationLogger.info(
c.bold(
c.magenta(`Adding ${c.blue("@astrojs/sitemap")} integration`),
),
);
}
addIntegration(sitemap());
} else {
if (verbose) {
GhostIntegrationLogger.info(
c.gray(
"@astrojs/sitemap integration already exists, skipping...",
),
);
}
}
// ASTRO-ROBOTS-TXT
if (!hasIntegration("@astro-robots-txt")) {
if (verbose) {
GhostIntegrationLogger.info(
c.bold(
c.magenta(`Adding ${c.blue("astro-robots-txt")} integration`),
),
);
}
addIntegration(robotsTxt());
} else {
if (verbose) {
GhostIntegrationLogger.info(
c.gray(
"astro-robots-txt integration already exists, skipping...",
),
);
}
}
// Set up default 404 page
if ( !options.disableDefault404 ) {
if (verbose) {
GhostRouteLogger.info(c.bold(c.cyan('Setting up default 404 page')))
}
injectRoute({
pattern: "/404",
entrypoint: `${name}/404.astro`,
prerender: true
})
} else {
if (verbose) {
GhostRouteLogger.info(c.gray('Default 404 page is disabled, Skipping...'))
}
}
// Set up default 404 page
if (!options.disableDefault404) {
if (verbose) {
GhostRouteLogger.info(
c.bold(c.cyan("Setting up default 404 page")),
);
}
injectRoute({
pattern: "/404",
entrypoint: `${name}/404.astro`,
prerender: true,
});
} else {
if (verbose) {
GhostRouteLogger.info(
c.gray("Default 404 page is disabled, Skipping..."),
);
}
}
// Add virtual imports for user configuration
addVirtualImports({
'virtual:@matthiesenxyz/astro-ghostcms/config': `export default ${JSON.stringify(options)}`,
})
// Add virtual imports for user configuration
addVirtualImports({
"virtual:@matthiesenxyz/astro-ghostcms/config": `export default ${JSON.stringify(
options,
)}`,
});
// Add types for user configuration
addDts({
name: "@matthiesenxyz/astro-ghostcms/config",
content: `declare module "virtual:@matthiesenxyz/astro-ghostcms/config" {
// Add types for user configuration
addDts({
name: "@matthiesenxyz/astro-ghostcms/config",
content: `declare module "virtual:@matthiesenxyz/astro-ghostcms/config" {
const Config: import("../schemas/userconfig").GhostUserConfig;
export default Config;
}`
})
}`,
});
},
"astro:config:done": ({ logger }) => {
const GhostLogger = logger.fork(
`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.green(
"CONFIG",
)}`,
);
GhostLogger.info(
c.bold(c.green("Integration Setup & Configuration Complete")),
);
},
"astro:server:start": async ({ logger }) => {
const GhostLogger = logger.fork(
`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.bold(
c.green("DEV"),
)}`,
);
const GhostUpdateLogger = logger.fork(
`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.bold(
c.green("VERSION CHECK"),
)}`,
);
},
"astro:config:done": ({ logger }) => {
const GhostLogger = logger.fork(`${c.bold(c.blue('👻 Astro-GhostCMS'))}${c.gray("/")}${c.green('CONFIG')}`);
GhostLogger.info(c.bold(c.green('Integration Setup & Configuration Complete')))
},
"astro:server:start": async ({ logger }) => {
const GhostLogger = logger.fork(`${c.bold(c.blue('👻 Astro-GhostCMS'))}${c.gray("/")}${c.bold(c.green('DEV'))}`);
const GhostUpdateLogger = logger.fork(`${c.bold(c.blue('👻 Astro-GhostCMS'))}${c.gray("/")}${c.bold(c.green('VERSION CHECK'))}`);
// Start the DEV server
GhostLogger.info(
c.bold(c.magenta("Running Astro-GhostCMS in Deveopment mode 🚀")),
);
// Start the DEV server
GhostLogger.info(c.bold(c.magenta('Running Astro-GhostCMS in Deveopment mode 🚀')))
// Check for updates
const currentNPMVersion = await latestVersion(
"@matthiesenxyz/astro-ghostcms",
);
const packageJson = await fse.readJson(
path.resolve(fileURLToPath(import.meta.url), "../../package.json"),
);
const localVersion = packageJson.version;
// Check for updates
const currentNPMVersion = await latestVersion("@matthiesenxyz/astro-ghostcms");
const packageJson = await fse.readJson(path.resolve(fileURLToPath(import.meta.url), "../../package.json"));
const localVersion = packageJson.version;
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 }) => {
const GhostLogger = logger.fork(`${c.bold(c.blue('👻 Astro-GhostCMS'))}${c.gray("/")}${c.bold(c.green('BUILD'))}`);
GhostLogger.info(c.bold(c.magenta('Running Astro-GhostCMS in Production mode 🚀')))
}
}
}
})
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 }) => {
const GhostLogger = logger.fork(
`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.bold(
c.green("BUILD"),
)}`,
);
GhostLogger.info(
c.bold(c.magenta("Running Astro-GhostCMS in Production mode 🚀")),
);
},
};
},
});

View File

@ -1,3 +1,3 @@
import astroghostcms from './astro-ghostcms.js';
import astroghostcms from "./astro-ghostcms.js";
export default astroghostcms;

View File

@ -1,29 +1,29 @@
import { createResolver, defineIntegration } from "astro-integration-kit";
import { corePlugins } from "astro-integration-kit/plugins";
import c from "picocolors";
import { z } from "astro/zod";
import c from "picocolors";
export default defineIntegration({
name: "@matthiesenxyz/astro-ghostcms-rss",
optionsSchema: z.object({
name: "@matthiesenxyz/astro-ghostcms-rss",
optionsSchema: z.object({
verbose: z.boolean().optional().default(false),
}),
plugins: [...corePlugins],
setup({ options }) {
const { resolve } = createResolver(import.meta.url);
}),
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')}`);
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...')))
RSSLogger.info(c.bold(c.magenta("RSS Feed Enabled. Setting up...")));
const rssRoute = "@matthiesenxyz/astro-ghostcms/rss-routes"
const rssRoute = "@matthiesenxyz/astro-ghostcms/rss-routes";
injectRoute({
pattern: "/rss-style.xsl",
@ -34,15 +34,18 @@ export default defineIntegration({
pattern: "/rss.xml",
entrypoint: `${rssRoute}/rss.xml.ts`,
});
},
"astro:config:done": ({ logger }) => {
const RSSLogger = logger.fork(
`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.blue(
"RSSGenerator",
)}`,
);
},
"astro:config:done": ({ logger }) => {
const RSSLogger = logger.fork(`${c.bold(c.blue('👻 Astro-GhostCMS'))}${c.gray("/")}${c.blue('RSSGenerator')}`);
if (options.verbose) {
RSSLogger.info(c.bold(c.green('RSS Feed Setup Complete')))
}
}
}
}
})
if (options.verbose) {
RSSLogger.info(c.bold(c.green("RSS Feed Setup Complete")));
}
},
};
},
});

View File

@ -78,4 +78,4 @@ export async function GET() {
},
},
);
}
}

View File

@ -24,4 +24,4 @@ export async function GET({ site }: APIContext) {
author: post.primary_author?.name,
})),
});
}
}

View File

@ -1,31 +1,37 @@
import { createResolver, defineIntegration } from "astro-integration-kit";
import { corePlugins } from "astro-integration-kit/plugins";
import c from "picocolors";
import { z } from "astro/zod";
import c from "picocolors";
export default defineIntegration({
name: "@matthiesenxyz/astro-ghostcms-satoriog",
optionsSchema: z.object({
name: "@matthiesenxyz/astro-ghostcms-satoriog",
optionsSchema: z.object({
verbose: z.boolean().optional().default(false),
}),
plugins: [...corePlugins],
setup({ options }) {
const { resolve } = createResolver(import.meta.url);
}),
plugins: [...corePlugins],
setup({ options }) {
const { resolve } = createResolver(import.meta.url);
return {
"astro:config:setup": ({
watchIntegration,
updateConfig,
injectRoute,
logger
}) => {
watchIntegration(resolve())
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')}`);
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...')))
SatoriLogger.info(
c.bold(c.magenta("OG Image Integration Enabled. Setting up...")),
);
const pkgname = "@matthiesenxyz/astro-ghostcms/open-graph"
const pkgname = "@matthiesenxyz/astro-ghostcms/open-graph";
injectRoute({
pattern: "/open-graph/[slug].png",
@ -57,17 +63,23 @@ export default defineIntegration({
entrypoint: `${pkgname}/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')}`);
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",
)}`,
);
if (options.verbose) {
SatoriLogger.info(c.bold(c.green('OG Image Integration Setup Complete')))
SatoriLogger.info(
c.bold(c.green("OG Image Integration Setup Complete")),
);
}
}
}
}
})
},
};
},
});

View File

@ -5,12 +5,7 @@ import type {
InferGetStaticPropsType,
} from "astro";
import { html } from "satori-html";
import {
getAllPages,
getAllPosts,
getSettings,
invariant,
} from "../../../api";
import { getAllPages, getAllPosts, getSettings, invariant } from "../../../api";
import satoriOG from "../satori";
export const getStaticPaths: GetStaticPaths = async () => {
@ -67,4 +62,4 @@ export const GET: APIRoute = async ({ props, site }) => {
],
},
});
};
};

View File

@ -61,4 +61,4 @@ export const GET: APIRoute = async ({ props, site }) => {
],
},
});
};
};

View File

@ -5,12 +5,7 @@ import type {
InferGetStaticPropsType,
} from "astro";
import { html } from "satori-html";
import {
getAllPages,
getAllPosts,
getSettings,
invariant,
} from "../../../api";
import { getAllPages, getAllPosts, getSettings, invariant } from "../../../api";
import satoriOG from "../satori";
export const getStaticPaths: GetStaticPaths = async () => {
@ -65,4 +60,4 @@ export const GET: APIRoute = async ({ props, site }) => {
],
},
});
};
};

View File

@ -5,12 +5,7 @@ import type {
InferGetStaticPropsType,
} from "astro";
import { html } from "satori-html";
import {
getAllPages,
getAllPosts,
getSettings,
invariant,
} from "../../../api";
import { getAllPages, getAllPosts, getSettings, invariant } from "../../../api";
import satoriOG from "../satori";
export const getStaticPaths: GetStaticPaths = async () => {
@ -65,4 +60,4 @@ export const GET: APIRoute = async ({ props, site }) => {
],
},
});
};
};

View File

@ -68,4 +68,4 @@ export const GET: APIRoute = async ({ props, site }) => {
],
},
});
};
};

View File

@ -5,12 +5,7 @@ import type {
InferGetStaticPropsType,
} from "astro";
import { html } from "satori-html";
import {
getAllPages,
getAllPosts,
getSettings,
invariant,
} from "../../../api";
import { getAllPages, getAllPosts, getSettings, invariant } from "../../../api";
import satoriOG from "../satori";
export const getStaticPaths: GetStaticPaths = async () => {
@ -65,4 +60,4 @@ export const GET: APIRoute = async ({ props, site }) => {
],
},
});
};
};

View File

@ -52,4 +52,4 @@ export function getOgImagePath(inputFilename = "index"): string {
filename = "index";
}
return `./open-graph/${filename}.png`;
}
}

View File

@ -17,4 +17,4 @@ export type ToImageOptions = {
| ResvgOptions
| ((params: { width: number; height: number }) => ResvgOptions);
};
export type ToResponseOptions = ToImageOptions & { response?: ResponseInit };
export type ToResponseOptions = ToImageOptions & { response?: ResponseInit };

View File

@ -4,32 +4,43 @@ import { z } from "astro/zod";
import c from "picocolors";
export default defineIntegration({
name: "@matthiesenxyz/astro-ghostcms-themeprovider",
optionsSchema: z.object({
theme: z.string().optional().default("@matthiesenxyz/astro-ghostcms-theme-default"),
name: "@matthiesenxyz/astro-ghostcms-themeprovider",
optionsSchema: z.object({
theme: z
.string()
.optional()
.default("@matthiesenxyz/astro-ghostcms-theme-default"),
verbose: z.boolean().optional().default(false),
}),
plugins: [...corePlugins],
setup({ options }) {
const { resolve } = createResolver(import.meta.url);
}),
plugins: [...corePlugins],
setup({ options }) {
const { resolve } = createResolver(import.meta.url);
return {
"astro:config:setup": ({
watchIntegration,
injectRoute,
logger
}) => {
watchIntegration(resolve())
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 themeLogger = logger.fork(
`${c.bold(c.blue("👻 Astro-GhostCMS"))}${c.gray("/")}${c.blue(
"Theme Provider",
)}`,
);
themeLogger.info(c.bold(c.magenta('Theme Provider enabled. Setting up...')))
themeLogger.info(
c.bold(c.magenta("Theme Provider enabled. Setting up...")),
);
if ( options.verbose) {
if ( options.theme === "@matthiesenxyz/astro-ghostcms-theme-default" ) {
themeLogger.info(c.blue('No theme is set, injecting default theme'))
if (options.verbose) {
if (options.theme === "@matthiesenxyz/astro-ghostcms-theme-default") {
themeLogger.info(
c.blue("No theme is set, injecting default theme"),
);
} else {
themeLogger.info(`${c.bold(c.cyan("Injecting Theme:"))} ${c.bold(c.underline(c.magenta(options.theme)))}`)
themeLogger.info(
`${c.bold(c.cyan("Injecting Theme:"))} ${c.bold(
c.underline(c.magenta(options.theme)),
)}`,
);
}
}
@ -38,7 +49,7 @@ export default defineIntegration({
entrypoint: `${options.theme}/index.astro`,
});
injectRoute({
injectRoute({
pattern: "/[slug]",
entrypoint: `${options.theme}/[slug].astro`,
});
@ -66,16 +77,19 @@ export default defineIntegration({
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",
)}`,
);
},
"astro:config:done": ({ logger }) => {
const themeLogger = logger.fork(`${c.bold(c.blue('👻 Astro-GhostCMS'))}${c.gray("/")}${c.blue('Theme Provider')}`);
if ( options.verbose ) {
themeLogger.info(c.bold(c.green('Provider Setup Complete')))
if (options.verbose) {
themeLogger.info(c.bold(c.green("Provider Setup Complete")));
}
}
}
}
})
},
};
},
});

View File

@ -1,146 +1,164 @@
import createFetchMock from "vitest-fetch-mock";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import createFetchMock from "vitest-fetch-mock";
import { TSGhostContentAPI } from "@ts-ghost/content-api";
const fetchMocker = createFetchMock(vi);
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");
test(".browse() params shouldnt accept invalid params", () => {
// @ts-expect-error - shouldnt accept invalid params
const browse = api.authors.browse({ pp: 2 });
expect(browse.getParams().browseParams).toStrictEqual({});
});
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");
test(".browse() params shouldnt accept invalid params", () => {
// @ts-expect-error - shouldnt accept invalid params
const browse = api.authors.browse({ pp: 2 });
expect(browse.getParams().browseParams).toStrictEqual({});
});
test(".browse() 'order' params should ony accept fields values", () => {
// @ts-expect-error - order should ony contain field
expect(() => api.authors.browse({ order: "foo ASC" })).toThrow();
// valid
expect(api.authors.browse({ order: "name ASC" }).getParams().browseParams).toStrictEqual({
order: "name ASC",
});
expect(api.authors.browse({ order: "name ASC,slug DESC" }).getParams().browseParams).toStrictEqual({
order: "name ASC,slug DESC",
});
expect(
api.authors.browse({ order: "name ASC,slug DESC,location ASC" }).getParams().browseParams
).toStrictEqual({
order: "name ASC,slug DESC,location ASC",
});
// @ts-expect-error - order should ony contain field (There is a typo in location)
expect(() => api.authors.browse({ order: "name ASC,slug DESC,locaton ASC" })).toThrow();
});
test(".browse() 'order' params should ony accept fields values", () => {
// @ts-expect-error - order should ony contain field
expect(() => api.authors.browse({ order: "foo ASC" })).toThrow();
// valid
expect(
api.authors.browse({ order: "name ASC" }).getParams().browseParams,
).toStrictEqual({
order: "name ASC",
});
expect(
api.authors.browse({ order: "name ASC,slug DESC" }).getParams()
.browseParams,
).toStrictEqual({
order: "name ASC,slug DESC",
});
expect(
api.authors
.browse({ order: "name ASC,slug DESC,location ASC" })
.getParams().browseParams,
).toStrictEqual({
order: "name ASC,slug DESC,location ASC",
});
// @ts-expect-error - order should ony contain field (There is a typo in location)
expect(() =>
api.authors.browse({ order: "name ASC,slug DESC,locaton ASC" }),
).toThrow();
});
test(".browse() 'filter' params should ony accept valid field", () => {
expect(() =>
api.authors.browse({
// @ts-expect-error - order should ony contain field
filter: "foo:bar",
})
).toThrow();
expect(
api.authors
.browse({
filter: "name:bar",
})
.getParams().browseParams
).toStrictEqual({
filter: "name:bar",
});
expect(
api.authors
.browse({
filter: "name:bar+slug:-test",
})
.getParams().browseParams
).toStrictEqual({
filter: "name:bar+slug:-test",
});
});
test(".browse() 'filter' params should ony accept valid field", () => {
expect(() =>
api.authors.browse({
// @ts-expect-error - order should ony contain field
filter: "foo:bar",
}),
).toThrow();
expect(
api.authors
.browse({
filter: "name:bar",
})
.getParams().browseParams,
).toStrictEqual({
filter: "name:bar",
});
expect(
api.authors
.browse({
filter: "name:bar+slug:-test",
})
.getParams().browseParams,
).toStrictEqual({
filter: "name:bar+slug:-test",
});
});
test(".browse 'fields' argument should ony accept valid fields", () => {
expect(
api.authors
.browse()
.fields({
// @ts-expect-error - order should ony contain field
foo: true,
})
.getOutputFields()
).toEqual([]);
test(".browse 'fields' argument should ony accept valid fields", () => {
expect(
api.authors
.browse()
.fields({
// @ts-expect-error - order should ony contain field
foo: true,
})
.getOutputFields(),
).toEqual([]);
expect(api.authors.browse().fields({ location: true }).getOutputFields()).toEqual(["location"]);
expect(api.authors.browse().fields({ name: true, website: true }).getOutputFields()).toEqual([
"name",
"website",
]);
});
expect(
api.authors.browse().fields({ location: true }).getOutputFields(),
).toEqual(["location"]);
expect(
api.authors
.browse()
.fields({ name: true, website: true })
.getOutputFields(),
).toEqual(["name", "website"]);
});
});
describe("authors resource mocked", () => {
let api: TSGhostContentAPI;
let api: TSGhostContentAPI;
beforeEach(() => {
api = new TSGhostContentAPI("https://my-ghost-blog.com", "59d4bf56c73c04a18c867dc3ba", "v5.0");
fetchMocker.enableMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
beforeEach(() => {
api = new TSGhostContentAPI(
"https://my-ghost-blog.com",
"59d4bf56c73c04a18c867dc3ba",
"v5.0",
);
fetchMocker.enableMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
test("aouthors should be fetched correctly", async () => {
const authors = api.authors;
expect(authors).not.toBeUndefined();
const browseQuery = authors
.browse({
page: 2,
})
.fields({
name: true,
id: true,
});
expect(browseQuery).not.toBeUndefined();
expect(browseQuery.getOutputFields()).toStrictEqual(["name", "id"]);
test("aouthors should be fetched correctly", async () => {
const authors = api.authors;
expect(authors).not.toBeUndefined();
const browseQuery = authors
.browse({
page: 2,
})
.fields({
name: true,
id: true,
});
expect(browseQuery).not.toBeUndefined();
expect(browseQuery.getOutputFields()).toStrictEqual(["name", "id"]);
fetchMocker.doMockOnce(
JSON.stringify({
authors: [
{
name: "foo",
id: "eaoizdjoa1321123",
},
],
meta: {
pagination: {
page: 1,
limit: 15,
pages: 1,
total: 1,
next: null,
prev: null,
},
},
})
);
const result = await browseQuery.fetch();
expect(fetchMocker).toHaveBeenCalledTimes(1);
expect(fetchMocker).toHaveBeenCalledWith(
"https://my-ghost-blog.com/ghost/api/content/authors/?page=2&fields=name%2Cid&key=59d4bf56c73c04a18c867dc3ba",
{
headers: {
"Content-Type": "application/json",
"Accept-Version": "v5.0",
},
}
);
expect(result).not.toBeUndefined();
if (result.success) {
expect(result.data.length).toBe(1);
expect(result.data[0].name).toBe("foo");
expect(result.data[0].id).toBe("eaoizdjoa1321123");
}
});
});
fetchMocker.doMockOnce(
JSON.stringify({
authors: [
{
name: "foo",
id: "eaoizdjoa1321123",
},
],
meta: {
pagination: {
page: 1,
limit: 15,
pages: 1,
total: 1,
next: null,
prev: null,
},
},
}),
);
const result = await browseQuery.fetch();
expect(fetchMocker).toHaveBeenCalledTimes(1);
expect(fetchMocker).toHaveBeenCalledWith(
"https://my-ghost-blog.com/ghost/api/content/authors/?page=2&fields=name%2Cid&key=59d4bf56c73c04a18c867dc3ba",
{
headers: {
"Content-Type": "application/json",
"Accept-Version": "v5.0",
},
},
);
expect(result).not.toBeUndefined();
if (result.success) {
expect(result.data.length).toBe(1);
expect(result.data[0].name).toBe("foo");
expect(result.data[0].id).toBe("eaoizdjoa1321123");
}
});
});

View File

@ -34,4 +34,4 @@ export const ghostFetchAuthorsSchema = z.object({
export const authorsIncludeSchema = z.object({
"count.posts": z.literal(true).optional(),
});
export type AuthorsIncludeSchema = z.infer<typeof authorsIncludeSchema>;
export type AuthorsIncludeSchema = z.infer<typeof authorsIncludeSchema>;

View File

@ -4,4 +4,4 @@ export * from "./posts";
export * from "./settings";
export * from "./socials";
export * from "./tags";
export * from "./tiers";
export * from "./tiers";

View File

@ -52,4 +52,4 @@ export const pagesIncludeSchema = z.object({
authors: z.literal(true).optional(),
tags: z.literal(true).optional(),
});
export type PagesIncludeSchema = z.infer<typeof pagesIncludeSchema>;
export type PagesIncludeSchema = z.infer<typeof pagesIncludeSchema>;

View File

@ -4,52 +4,64 @@ import { TSGhostContentAPI } from "@ts-ghost/content-api";
import type { Post } from "./index";
const url = process.env.VITE_GHOST_URL || "https://my-ghost-blog.com";
const key = process.env.VITE_GHOST_CONTENT_API_KEY || "59d4bf56c73c04a18c867dc3ba";
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");
test(".browse() params shouldnt accept invalid params", () => {
// @ts-expect-error - shouldnt accept invalid params
const browse = api.posts.browse({ pp: 2 });
expect(browse.getParams().browseParams).toStrictEqual({});
const api = new TSGhostContentAPI(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 });
expect(browse.getParams().browseParams).toStrictEqual({});
const outputFields = {
slug: true,
title: true,
// @ts-expect-error - shouldnt accept invalid params
foo: true,
} satisfies { [k in keyof Post]?: true | undefined };
const outputFields = {
slug: true,
title: true,
// @ts-expect-error - shouldnt accept invalid params
foo: true,
} satisfies { [k in keyof Post]?: true | undefined };
const test = api.posts
.browse()
// @ts-expect-error - shouldnt accept invalid params
.fields(outputFields);
expect(test.getOutputFields()).toEqual(["slug", "title"]);
const test = api.posts
.browse()
// @ts-expect-error - shouldnt accept invalid params
.fields(outputFields);
expect(test.getOutputFields()).toEqual(["slug", "title"]);
const fields = ["slug", "title", "foo"] as const;
const unknownOriginFields = fields.reduce((acc, k) => {
acc[k as keyof Post] = true;
return acc;
}, {} as { [k in keyof Post]?: true | undefined });
const result = api.posts.browse().fields(unknownOriginFields);
expect(result.getOutputFields()).toEqual(["slug", "title"]);
});
test(".browse() params, output fields declare const", () => {
const outputFields = {
slug: true,
title: true,
} satisfies { [k in keyof Post]?: true | undefined };
const fields = ["slug", "title", "foo"] as const;
const unknownOriginFields = fields.reduce(
(acc, k) => {
acc[k as keyof Post] = true;
return acc;
},
{} as { [k in keyof Post]?: true | undefined },
);
const result = api.posts.browse().fields(unknownOriginFields);
expect(result.getOutputFields()).toEqual(["slug", "title"]);
});
test(".browse() params, output fields declare const", () => {
const outputFields = {
slug: true,
title: true,
} satisfies { [k in keyof Post]?: true | undefined };
const test = api.posts.browse().fields(outputFields);
expect(test.getOutputFields()).toEqual(["slug", "title"]);
const test = api.posts.browse().fields(outputFields);
expect(test.getOutputFields()).toEqual(["slug", "title"]);
// @ts-expect-error - shouldnt accept invalid params
expect(() => api.posts.browse({ filter: "slugg:test" })).toThrow();
// @ts-expect-error - shouldnt accept invalid params
expect(() => api.posts.browse({ filter: "slug:test,foo:-[bar,baz]" })).toThrow();
expect(api.posts.browse({ filter: "slug:test,tags:-[bar,baz]" })).toBeDefined();
expect(api.posts.browse({ filter: "slug:test,tags:[bar,baz]" })).toBeDefined();
// @ts-expect-error - shouldnt accept invalid params
expect(() => api.posts.browse({ filter: "slug:test,food:-[bar,baz]" })).toThrow();
});
});
// @ts-expect-error - shouldnt accept invalid params
expect(() => api.posts.browse({ filter: "slugg:test" })).toThrow();
// @ts-expect-error - shouldnt accept invalid params
expect(() =>
api.posts.browse({ filter: "slug:test,foo:-[bar,baz]" }),
).toThrow();
expect(
api.posts.browse({ filter: "slug:test,tags:-[bar,baz]" }),
).toBeDefined();
expect(
api.posts.browse({ filter: "slug:test,tags:[bar,baz]" }),
).toBeDefined();
// @ts-expect-error - shouldnt accept invalid params
expect(() =>
api.posts.browse({ filter: "slug:test,food:-[bar,baz]" }),
).toThrow();
});
});

View File

@ -51,4 +51,4 @@ export const postsIncludeSchema = z.object({
authors: z.literal(true).optional(),
tags: z.literal(true).optional(),
});
export type PostsIncludeSchema = z.infer<typeof postsIncludeSchema>;
export type PostsIncludeSchema = z.infer<typeof postsIncludeSchema>;

View File

@ -3,34 +3,37 @@ 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";
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");
}
});
});
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

@ -37,4 +37,4 @@ export const settingsSchema = z.object({
url: z.string(),
});
export type Settings = z.infer<typeof settingsSchema>;
export type Settings = z.infer<typeof settingsSchema>;

View File

@ -29,4 +29,4 @@ export const twitter = (username: string) => {
export const facebook = (username: string) => {
// Handles a starting slash, this shouldn't happen, but just in case
return `https://www.facebook.com/${username.replace(/^\//, "")}`;
};
};

View File

@ -31,4 +31,4 @@ export type Tag = z.infer<typeof tagsSchema>;
export const tagsIncludeSchema = z.object({
"count.posts": z.literal(true).optional(),
});
export type TagsIncludeSchema = z.infer<typeof tagsIncludeSchema>;
export type TagsIncludeSchema = z.infer<typeof tagsIncludeSchema>;

View File

@ -37,4 +37,4 @@ export const tiersIncludeSchema = z.object({
yearly_price: z.literal(true).optional(),
benefits: z.literal(true).optional(),
});
export type TiersIncludeSchema = z.infer<typeof tiersIncludeSchema>;
export type TiersIncludeSchema = z.infer<typeof tiersIncludeSchema>;

View File

@ -1,54 +1,54 @@
import { expect, describe, it } from "vitest";
import { describe, expect, it } from "vitest";
import { GhostUserConfigSchema } from "./userconfig";
describe("GhostUserConfigSchema", () => {
it("should validate a valid user config", () => {
const validConfig = {
ghostURL: "https://ghostdemo.matthiesen.xyz",
disableThemeProvider: true,
ThemeProvider: {
theme: "@matthiesenxyz/astro-ghostcms-theme-default",
},
disableDefault404: false,
enableRSSFeed: true,
enableOGImages: true,
sitemap: {
// sitemap configuration
},
robotstxt: {
// robotstxt configuration
},
fullConsoleLogs: false,
};
it("should validate a valid user config", () => {
const validConfig = {
ghostURL: "https://ghostdemo.matthiesen.xyz",
disableThemeProvider: true,
ThemeProvider: {
theme: "@matthiesenxyz/astro-ghostcms-theme-default",
},
disableDefault404: false,
enableRSSFeed: true,
enableOGImages: true,
sitemap: {
// sitemap configuration
},
robotstxt: {
// robotstxt configuration
},
fullConsoleLogs: false,
};
const result = GhostUserConfigSchema.safeParse(validConfig);
const result = GhostUserConfigSchema.safeParse(validConfig);
expect(result.success).to.be.true;
expect(result.data).to.deep.equal(validConfig);
});
expect(result.success).to.be.true;
expect(result.data).to.deep.equal(validConfig);
});
it("should invalidate an invalid user config", () => {
const invalidConfig = {
ghostURL: "invalid-url",
disableThemeProvider: "true",
ThemeProvider: {
theme: 123,
},
disableDefault404: "false",
enableRSSFeed: "true",
enableOGImages: "true",
sitemap: {
// invalid sitemap configuration
},
robotstxt: {
// invalid robotstxt configuration
},
fullConsoleLogs: "false",
};
it("should invalidate an invalid user config", () => {
const invalidConfig = {
ghostURL: "invalid-url",
disableThemeProvider: "true",
ThemeProvider: {
theme: 123,
},
disableDefault404: "false",
enableRSSFeed: "true",
enableOGImages: "true",
sitemap: {
// invalid sitemap configuration
},
robotstxt: {
// invalid robotstxt configuration
},
fullConsoleLogs: "false",
};
const result = GhostUserConfigSchema.safeParse(invalidConfig);
const result = GhostUserConfigSchema.safeParse(invalidConfig);
expect(result.success).to.be.false;
expect(!result.success).to.exist;
});
});
expect(result.success).to.be.false;
expect(!result.success).to.exist;
});
});

View File

@ -18,12 +18,17 @@ export const GhostUserConfigSchema = z.object({
* @default false
*/
disableThemeProvider: z.boolean().optional().default(false),
ThemeProvider: z.object({
/** OPTIONAL - Set the theme you want to use
* @default "@matthiesenxyz/astro-ghostcms-theme-default"
*/
theme: z.string().optional().default("@matthiesenxyz/astro-ghostcms-theme-default"),
}).optional(),
ThemeProvider: z
.object({
/** OPTIONAL - Set the theme you want to use
* @default "@matthiesenxyz/astro-ghostcms-theme-default"
*/
theme: z
.string()
.optional()
.default("@matthiesenxyz/astro-ghostcms-theme-default"),
})
.optional(),
/** Allows the user to disable the provided 404 page */
disableDefault404: z.boolean().optional().default(false),
/** Allows the user to disable the provided RSS Feed */

View File

@ -23,4 +23,4 @@ export default async function latestVersion(
const { version } = await packageJson(packageName.toLowerCase(), options);
return version;
}
}

View File

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

View File

@ -1,6 +1,6 @@
/// <reference types="vitest" />
/// <reference types="vite/client" />
import { defineProject } from 'vitest/config'
import { defineProject } from "vitest/config";
export default defineProject({
test: {
@ -9,4 +9,4 @@ export default defineProject({
watchExclude: [".*\\/node_modules\\/.*", ".*\\/build\\/.*"],
exclude: ["node_modules", "dist", ".idea", ".git", ".cache"],
},
})
});

View File

@ -1,16 +1,16 @@
import { defineConfig } from "astro/config";
import ghostcms from "@matthiesenxyz/astro-ghostcms";
import UnoCSS from 'unocss/astro';
import { defineConfig } from "astro/config";
import UnoCSS from "unocss/astro";
// https://astro.build/config
export default defineConfig({
site: "https://example.xyz/",
trailingSlash: 'ignore',
trailingSlash: "ignore",
integrations: [
UnoCSS({ injectReset: true }),
ghostcms({
theme: "@matthiesenxyz/astro-ghostcms-brutalbyelian",
ghostURL: "https://ghostdemo.matthiesen.xyz",
})
}),
],
});

View File

@ -1,9 +1,9 @@
/// <reference types="astro/client" />
interface ImportMetaEnv {
readonly CONTENT_API_KEY: string
readonly CONTENT_API_URL: string
readonly CONTENT_API_KEY: string;
readonly CONTENT_API_URL: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
readonly env: ImportMetaEnv;
}

View File

@ -1,6 +1,6 @@
import brutalTheme from '@matthiesenxyz/astro-ghostcms-brutalbyelian';
import { defineConfig } from 'unocss';
import brutalTheme from "@matthiesenxyz/astro-ghostcms-brutalbyelian";
import { defineConfig } from "unocss";
export default defineConfig({
presets: [ brutalTheme() ],
presets: [brutalTheme()],
});

View File

@ -1,91 +1,126 @@
import type { StarlightPlugin, StarlightUserConfig } from '@astrojs/starlight/types'
import type { AstroIntegrationLogger } from 'astro'
import { type StarlightGhostConfig, validateConfig } from './src/schemas/config'
import { vitePluginStarlightGhostConfig } from './src/integrations/vite'
import { facebook, getSettings, invariant, twitter } from './src/utils/api'
import type {
StarlightPlugin,
StarlightUserConfig,
} from "@astrojs/starlight/types";
import type { AstroIntegrationLogger } from "astro";
import { vitePluginStarlightGhostConfig } from "./src/integrations/vite";
import {
type StarlightGhostConfig,
validateConfig,
} from "./src/schemas/config";
import { facebook, getSettings, invariant, twitter } from "./src/utils/api";
const settings = await getSettings()
const settings = await getSettings();
export type { StarlightGhostConfig }
export type { StarlightGhostConfig };
export default function starlightGhostCMS(userConfig?: StarlightGhostConfig): StarlightPlugin {
const config: StarlightGhostConfig = validateConfig(userConfig)
invariant(settings, "Settings not available... check your api key/url")
export default function starlightGhostCMS(
userConfig?: StarlightGhostConfig,
): StarlightPlugin {
const config: StarlightGhostConfig = validateConfig(userConfig);
invariant(settings, "Settings not available... check your api key/url");
return {
name: '@matthiesenxyz/starlight-ghostcms-plugin',
hooks: {
setup({ astroConfig, addIntegration, config: starlightConfig, logger, updateConfig: updateStarlightConfig }) {
updateStarlightConfig({
social: {
...starlightConfig.social,
rss: `${astroConfig.site}/rss.xml`,
twitter: twitter(settings.twitter?settings.twitter:""),
facebook: facebook(settings.facebook?settings.facebook:""),
},
components: {
...starlightConfig.components,
...overrideStarlightComponent(starlightConfig.components, logger, 'MarkdownContent'),
...overrideStarlightComponent(starlightConfig.components, logger, 'Sidebar'),
...overrideStarlightComponent(starlightConfig.components, logger, "SiteTitle"),
}
})
return {
name: "@matthiesenxyz/starlight-ghostcms-plugin",
hooks: {
setup({
astroConfig,
addIntegration,
config: starlightConfig,
logger,
updateConfig: updateStarlightConfig,
}) {
updateStarlightConfig({
social: {
...starlightConfig.social,
rss: `${astroConfig.site}/rss.xml`,
twitter: twitter(settings.twitter ? settings.twitter : ""),
facebook: facebook(settings.facebook ? settings.facebook : ""),
},
components: {
...starlightConfig.components,
...overrideStarlightComponent(
starlightConfig.components,
logger,
"MarkdownContent",
),
...overrideStarlightComponent(
starlightConfig.components,
logger,
"Sidebar",
),
...overrideStarlightComponent(
starlightConfig.components,
logger,
"SiteTitle",
),
},
});
addIntegration({
name: '@matthiesenxyz/starlight-ghostcms',
hooks: {
'astro:config:setup': ({ injectRoute, updateConfig }) => {
injectRoute({
pattern: '/blog',
entrypoint: '@matthiesenxyz/starlight-ghostcms/routes/index.astro',
prerender: true,
})
injectRoute({
pattern: '/blog/[slug]',
entrypoint: '@matthiesenxyz/starlight-ghostcms/routes/[slug].astro',
prerender: true,
})
injectRoute({
pattern: '/blog/about',
entrypoint: '@matthiesenxyz/starlight-ghostcms/routes/about.astro',
prerender: true,
})
injectRoute({
pattern: '/blog/authors',
entrypoint: '@matthiesenxyz/starlight-ghostcms/routes/authors.astro',
})
injectRoute({
pattern: '/rss.xml',
entrypoint: '@matthiesenxyz/starlight-ghostcms/routes/rss.xml.ts'
})
addIntegration({
name: "@matthiesenxyz/starlight-ghostcms",
hooks: {
"astro:config:setup": ({ injectRoute, updateConfig }) => {
injectRoute({
pattern: "/blog",
entrypoint:
"@matthiesenxyz/starlight-ghostcms/routes/index.astro",
prerender: true,
});
injectRoute({
pattern: "/blog/[slug]",
entrypoint:
"@matthiesenxyz/starlight-ghostcms/routes/[slug].astro",
prerender: true,
});
injectRoute({
pattern: "/blog/about",
entrypoint:
"@matthiesenxyz/starlight-ghostcms/routes/about.astro",
prerender: true,
});
injectRoute({
pattern: "/blog/authors",
entrypoint:
"@matthiesenxyz/starlight-ghostcms/routes/authors.astro",
});
injectRoute({
pattern: "/rss.xml",
entrypoint:
"@matthiesenxyz/starlight-ghostcms/routes/rss.xml.ts",
});
updateConfig({
vite: {
plugins: [vitePluginStarlightGhostConfig(config)],
},
})
}
}
})
}
},
}
updateConfig({
vite: {
plugins: [vitePluginStarlightGhostConfig(config)],
},
});
},
},
});
},
},
};
}
function overrideStarlightComponent(
components: StarlightUserConfig['components'],
logger: AstroIntegrationLogger,
component: keyof NonNullable<StarlightUserConfig['components']>,
) {
if (components?.[component]) {
logger.warn(`It looks like you already have a \`${component}\` component override in your Starlight configuration.`)
logger.warn(`To use \`starlight-ghostcms\`, remove the override for the \`${component}\` component.\n`)
logger.warn("This Warning can be ignored if you know what your doing ;)")
return {}
}
return {
[component]: `@matthiesenxyz/starlight-ghostcms/overrides/${component}.astro`,
}
}
components: StarlightUserConfig["components"],
logger: AstroIntegrationLogger,
component: keyof NonNullable<StarlightUserConfig["components"]>,
) {
if (components?.[component]) {
logger.warn(
`It looks like you already have a \`${component}\` component override in your Starlight configuration.`,
);
logger.warn(
`To use \`starlight-ghostcms\`, remove the override for the \`${component}\` component.\n`,
);
logger.warn("This Warning can be ignored if you know what your doing ;)");
return {};
}
return {
[component]: `@matthiesenxyz/starlight-ghostcms/overrides/${component}.astro`,
};
}

View File

@ -1,22 +1,24 @@
import type { ViteUserConfig } from 'astro'
import type { ViteUserConfig } from "astro";
import type { StarlightGhostConfig } from '../schemas/config.ts'
import type { StarlightGhostConfig } from "../schemas/config.ts";
// Expose the starlight-blog plugin configuration.
export function vitePluginStarlightGhostConfig(config: StarlightGhostConfig): VitePlugin {
const moduleId = 'virtual:starlight-ghost-config'
const resolvedModuleId = `\0${moduleId}`
const moduleContent = `export default ${JSON.stringify(config)}`
export function vitePluginStarlightGhostConfig(
config: StarlightGhostConfig,
): VitePlugin {
const moduleId = "virtual:starlight-ghost-config";
const resolvedModuleId = `\0${moduleId}`;
const moduleContent = `export default ${JSON.stringify(config)}`;
return {
name: 'vite-plugin-starlight-ghost-config',
load(id) {
return id === resolvedModuleId ? moduleContent : undefined
},
resolveId(id) {
return id === moduleId ? resolvedModuleId : undefined
},
}
return {
name: "vite-plugin-starlight-ghost-config",
load(id) {
return id === resolvedModuleId ? moduleContent : undefined;
},
resolveId(id) {
return id === moduleId ? resolvedModuleId : undefined;
},
};
}
type VitePlugin = NonNullable<ViteUserConfig['plugins']>[number]
type VitePlugin = NonNullable<ViteUserConfig["plugins"]>[number];

View File

@ -1,15 +1,19 @@
import { z } from 'astro/zod';
import type { AstroBuiltinAttributes } from 'astro';
import type { HTMLAttributes } from 'astro/types';
import type { AstroBuiltinAttributes } from "astro";
import type { HTMLAttributes } from "astro/types";
import { z } from "astro/zod";
const linkHTMLAttributesSchema = z.record(
z.union([z.string(), z.number(), z.boolean(), z.undefined()])
) as z.Schema<Omit<HTMLAttributes<'a'>, keyof AstroBuiltinAttributes | 'children'>>;
z.union([z.string(), z.number(), z.boolean(), z.undefined()]),
) as z.Schema<
Omit<HTMLAttributes<"a">, keyof AstroBuiltinAttributes | "children">
>;
export type LinkHTMLAttributes = z.infer<typeof linkHTMLAttributesSchema>;
const badgeSchema = () =>
z.object({
variant: z.enum(['note', 'danger', 'success', 'caution', 'tip', 'default']).default('default'),
variant: z
.enum(["note", "danger", "success", "caution", "tip", "default"])
.default("default"),
text: z.string(),
});
@ -17,8 +21,8 @@ export const BadgeConfigSchema = () =>
z
.union([z.string(), badgeSchema()])
.transform((badge) => {
if (typeof badge === 'string') {
return { variant: 'default' as const, text: badge };
if (typeof badge === "string") {
return { variant: "default" as const, text: badge };
}
return badge;
})
@ -27,7 +31,7 @@ export const BadgeConfigSchema = () =>
export type Badge = z.output<ReturnType<typeof badgeSchema>>;
export interface Link {
type: 'link';
type: "link";
label: string;
href: string;
isCurrent: boolean;
@ -36,11 +40,11 @@ export interface Link {
}
interface Group {
type: 'group';
type: "group";
label: string;
entries: (Link | Group)[];
collapsed: boolean;
badge: Badge | undefined;
}
export type SidebarEntry = Link | Group;
export type SidebarEntry = Link | Group;

View File

@ -5,17 +5,17 @@ import { getAllPosts, getSettings, invariant } from "../utils/api";
const posts = await getAllPosts();
const settings = await getSettings();
import config from 'virtual:starlight-ghost-config';
import config from "virtual:starlight-ghost-config";
export async function GET({ site }: APIContext) {
invariant(settings,"Settings is not defined")
invariant(settings, "Settings is not defined");
const title = config.title;
const description = config.rssDescription;
const ghostSite = settings.url
const ghostSite = settings.url;
return rss({
title: title,
description: description,
site: site?site:ghostSite,
site: site ? site : ghostSite,
items: posts.map((post) => ({
title: post.title,
pubDate: new Date(

View File

@ -1,50 +1,52 @@
import { AstroError } from 'astro/errors'
import { z } from 'astro/zod'
import { AstroError } from "astro/errors";
import { z } from "astro/zod";
const configSchema = z
.object({
/**
* The number of blog posts to display per page in the blog post list.
*/
postCount: z.number().min(1).default(5),
/**
* The number of recent blog posts to display in the sidebar.
*/
recentPostCount: z.number().min(1).default(10),
/**
* The title of the blog.
*/
title: z.string().default('Blog'),
/**
* The description of the blog on the RSS Feed.
*/
rssDescription: z.string().default('My Awesome Starlight-GhostCMS Blog'),
/**
* Turn on and off "Powered by Ghost"
*/
supportGhost: z.boolean().default(true),
})
.default({})
.object({
/**
* The number of blog posts to display per page in the blog post list.
*/
postCount: z.number().min(1).default(5),
/**
* The number of recent blog posts to display in the sidebar.
*/
recentPostCount: z.number().min(1).default(10),
/**
* The title of the blog.
*/
title: z.string().default("Blog"),
/**
* The description of the blog on the RSS Feed.
*/
rssDescription: z.string().default("My Awesome Starlight-GhostCMS Blog"),
/**
* Turn on and off "Powered by Ghost"
*/
supportGhost: z.boolean().default(true),
})
.default({});
export function validateConfig(userConfig: unknown): StarlightGhostConfig {
const config = configSchema.safeParse(userConfig)
const config = configSchema.safeParse(userConfig);
if (!config.success) {
const errors = config.error.flatten()
if (!config.success) {
const errors = config.error.flatten();
throw new AstroError(
`Invalid starlight-GhostCMS configuration:
throw new AstroError(
`Invalid starlight-GhostCMS configuration:
${errors.formErrors.map((formError) => ` - ${formError}`).join('\n')}
${errors.formErrors.map((formError) => ` - ${formError}`).join("\n")}
${Object.entries(errors.fieldErrors)
.map(([fieldName, fieldErrors]) => ` - ${fieldName}: ${fieldErrors.join(' - ')}`)
.join('\n')}
.map(
([fieldName, fieldErrors]) => ` - ${fieldName}: ${fieldErrors.join(" - ")}`,
)
.join("\n")}
`,
"See the error report above for more informations.\n\nIf you believe this is a bug, please file an issue at https://github.com/matthiesenxyz/astro-ghostcms/issues/new/choose",
)
}
"See the error report above for more informations.\n\nIf you believe this is a bug, please file an issue at https://github.com/matthiesenxyz/astro-ghostcms/issues/new/choose",
);
}
return config.data
return config.data;
}
export type StarlightGhostConfig = z.infer<typeof configSchema>
export type StarlightGhostConfig = z.infer<typeof configSchema>;

View File

@ -1,8 +1,8 @@
import { TS_API } from "./content-api";
import type { Page, Post } from "./content-api/schemas";
import type { ContentAPICredentials } from './content-api/content-api'
// LOAD ENVIRONMENT VARIABLES
import { loadEnv } from "vite";
import { TS_API } from "./content-api";
import type { ContentAPICredentials } from "./content-api/content-api";
import type { Page, Post } from "./content-api/schemas";
import { invariant } from "./invariant.js";
const { CONTENT_API_KEY, CONTENT_API_URL } = loadEnv(
@ -11,17 +11,11 @@ const { CONTENT_API_KEY, CONTENT_API_URL } = loadEnv(
"CONTENT_",
);
invariant(
CONTENT_API_KEY,
"CONTENT_API_KEY Missing from .env"
)
invariant(
CONTENT_API_URL,
"CONTENT_API_URL Missing from .env"
)
invariant(CONTENT_API_KEY, "CONTENT_API_KEY Missing from .env");
invariant(CONTENT_API_URL, "CONTENT_API_URL Missing from .env");
const key:ContentAPICredentials["key"] = CONTENT_API_KEY;
const url:ContentAPICredentials["url"] = CONTENT_API_URL;
const key: ContentAPICredentials["key"] = CONTENT_API_KEY;
const url: ContentAPICredentials["url"] = CONTENT_API_URL;
const version = "v5.0";
const api = new TS_API(url, key, version);
@ -73,20 +67,21 @@ export const getAllPosts = async () => {
return posts;
};
export const getSluggedPost = async (slug:string) => {
export const getSluggedPost = async (slug: string) => {
const results = await api.posts
.read({slug: slug})
.read({ slug: slug })
.include({
authors: true,
tags: true,
}).fetch()
if (!results.success) {
throw new Error(results.errors.map((e) => e.message).join(", "));
}
return {
post: results.data,
};
})
.fetch();
if (!results.success) {
throw new Error(results.errors.map((e) => e.message).join(", "));
}
return {
post: results.data,
};
};
export const getAllPages = async () => {
@ -106,20 +101,21 @@ export const getAllPages = async () => {
return pages;
};
export const getSluggedPage = async (slug:string) => {
export const getSluggedPage = async (slug: string) => {
const results = await api.pages
.read({slug: slug})
.read({ slug: slug })
.include({
authors: true,
tags: true,
}).fetch()
if (!results.success) {
throw new Error(results.errors.map((e) => e.message).join(", "));
}
return {
post: results.data,
};
})
.fetch();
if (!results.success) {
throw new Error(results.errors.map((e) => e.message).join(", "));
}
return {
post: results.data,
};
};
export const getSettings = async () => {

View File

@ -1,33 +1,33 @@
export function isAnyBlogPage(slug: string) {
return slug.match(/^blog(\/?$|\/.+\/?$)/) !== null
return slug.match(/^blog(\/?$|\/.+\/?$)/) !== null;
}
export function isBlogRoot(slug: string) {
return slug === 'blog'
return slug === "blog";
}
export function isAnyBlogPostPage(slug: string) {
return slug.match(/^blog\/(?!(\d+\/?|tags\/.+)$).+$/) !== null
return slug.match(/^blog\/(?!(\d+\/?|tags\/.+)$).+$/) !== null;
}
export function isBlogPostPage(slug: string, postSlug: string) {
return slug === postSlug
return slug === postSlug;
}
export function isBlogTagsPage(slug: string, tag: string) {
return slug === `blog/tags/${tag}`
return slug === `blog/tags/${tag}`;
}
export function getPageProps(title: string): StarlightPageProps {
return {
frontmatter: {
title,
},
}
return {
frontmatter: {
title,
},
};
}
interface StarlightPageProps {
frontmatter: {
title: string
}
frontmatter: {
title: string;
};
}

View File

@ -1,5 +1,5 @@
declare module 'virtual:starlight/user-config' {
const Config: import('@astrojs/starlight/types').StarlightConfig
declare module "virtual:starlight/user-config" {
const Config: import("@astrojs/starlight/types").StarlightConfig;
export default Config
export default Config;
}

View File

@ -1,5 +1,5 @@
declare module 'virtual:starlight-ghost-config' {
const StarlightGhostConfig: import('./src/schemas/config').StarlightGhostConfig
declare module "virtual:starlight-ghost-config" {
const StarlightGhostConfig: import("./src/schemas/config").StarlightGhostConfig;
export default StarlightGhostConfig
export default StarlightGhostConfig;
}

View File

@ -12,7 +12,7 @@ export default defineConfig({
ghostcms({
ghostURL: "https://ghostdemo.matthiesen.xyz",
ThemeProvider: {
theme: '@matthiesenxyz/astro-ghostcms-brutalbyelian',
theme: "@matthiesenxyz/astro-ghostcms-brutalbyelian",
},
}),
],

View File

@ -1,4 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

View File

@ -1,11 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

View File

@ -1,35 +1,35 @@
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
import starlightGhostCMS from '@matthiesenxyz/starlight-ghostcms';
import starlight from "@astrojs/starlight";
import starlightGhostCMS from "@matthiesenxyz/starlight-ghostcms";
import { defineConfig } from "astro/config";
// https://astro.build/config
export default defineConfig({
site: "http://localhost:4321",
integrations: [
starlight({
title: 'My Docs',
title: "My Docs",
plugins: [
starlightGhostCMS({
title: "Demo Blog",
rssDescription: "Starlight Playground"
})
rssDescription: "Starlight Playground",
}),
],
social: {
github: 'https://github.com/withastro/starlight',
github: "https://github.com/withastro/starlight",
},
sidebar: [
{
label: 'Guides',
label: "Guides",
items: [
// Each item here is one entry in the navigation menu.
{ label: 'Example Guide', link: '/guides/example/' },
{ label: "Example Guide", link: "/guides/example/" },
],
},
{
label: 'Reference',
autogenerate: { directory: 'reference' },
label: "Reference",
autogenerate: { directory: "reference" },
},
],
}),
],
});
});

View File

@ -1,6 +1,6 @@
import { defineCollection } from 'astro:content';
import { docsSchema } from '@astrojs/starlight/schema';
import { docsSchema } from "@astrojs/starlight/schema";
import { defineCollection } from "astro:content";
export const collections = {
docs: defineCollection({ schema: docsSchema() }),
};
};

View File

@ -1,2 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />
/// <reference types="astro/client" />