First group of progress changes... and it works

This commit is contained in:
Adam Matthiesen 2024-03-03 05:17:54 -08:00
parent 8e276d533d
commit 8db7190be8
106 changed files with 944 additions and 1891 deletions

View File

@ -6,7 +6,7 @@
"node": ">=18.19.0"
},
"scripts": {
"dev": "pnpm --filter playground dev",
"astro:dev": "pnpm --filter astro-playground dev",
"starlight:dev": "pnpm --filter starlight-playground dev",
"lint": "biome check .",
"lint:fix": "biome check --apply .",

View File

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

View File

@ -113,4 +113,3 @@ 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

@ -1,359 +0,0 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import type { AstroIntegration } from "astro";
import type { SafeParseError, SafeParseSuccess } from "astro/zod";
import fse from "fs-extra";
import { loadEnv } from "vite";
import ghostRobots from "./src/integrations/robots-txt";
import ghostSitemap from "./src/integrations/sitemap";
import { UserConfigSchema } from "./src/schemas";
import { addVirtualImport } from "./src/utils/add-virtual-import";
import latestVersion from "./src/utils/latestVersion.js";
import { fromZodError } from "./src/utils/zod-validation/fromZodError.js";
import type { UserConfig } from "./types";
/** INTERNAL CONSTANTS */
const IC = {
/** INTERNAL PACKAGE NAME */
PKG: "@matthiesenxyz/astro-ghostcms",
/** INTERNAL PACKAGE NAME (THEME) */
DT: "@matthiesenxyz/astro-ghostcms-theme-default",
/** INTERNAL STRING */
CHECK_ENV: "Checking for Environment Variables...",
/** SET ENV GRABBER MODE */
MODE: "all",
/** SET ENV GRABBER PREFIXES */
PREFIXES: "CONTENT_API",
/** INTERNAL STRING */
KEY_MISSING: "CONTENT_API_KEY Missing from .env/environment variables",
/** INTERNAL STRING */
URL_MISSING:
"CONTENT_API_URL Missing from .env/environment variables or ghostURL under the integration settings in `astro.config.mjs`",
/** INTERNAL STRING */
IT: "Injecting Theme: ",
/** INTERNAL STRING */
IDR: "Injecting Default Routes...",
/** INTERNAL STRING */
ITR: "Injecting Default Theme Routes...",
/** INTERNAL STRING */
IRD: "Route Injection Disabled - Skipping...",
/** INTERNAL STRING */
IIR: "Injecting Integration Route: ",
/** INTERNAL STRING */
II: "Injecting Integration: ",
/** INTERNAL STRING */
AIbU: "Already Imported by User: ",
/** INTERNAL STRING */
CF: "Checking for ",
/** INTERNAL STRING */
CONFSETUPDONE: "Step Complete",
/** INTERNAL STRING */
F0FR: "Inject `/404` Route",
/** INTERNAL STRING */
RSS: "Injecting `/rss.xml` Route and `@astrojs/rss` Integration",
/** INTERNAL STRING */
NOURL:
"No Ghost URL defined in User Config: Falling back to environment variables.",
/** INTERNAL STRING */
id404: "404 Injection Disabled",
/** INTERNAL STRING */
idRSS: "RSS Injection Disabled",
/** INTERNAL STRING */
satori_e: "Injecting Satori-OpenGraph Generator",
/** INTERNAL STRING */
satori_d: "Satori Injection disabled",
};
/** CONTENT API ENVIRONMENT VARIABLES */
const ENV = loadEnv(IC.MODE, process.cwd(), IC.PREFIXES);
/** Astro-GhostCMS Integration
* @ For more information and to see the docs check
* @see https://astro-ghostcms.xyz
*/
export default function GhostCMS(options: UserConfig): AstroIntegration {
return {
name: "astro-ghostcms",
hooks: {
"astro:config:setup": async ({
injectRoute,
config,
updateConfig,
logger,
}) => {
// DEFINE LOGGERS
const logConfigCheck = logger.fork("astro-ghostcms/config:check");
const logConfigSetup = logger.fork("astro-ghostcms/config:setup");
// CHECK USER CONFIG AND MAKE AVAILBLE TO INTEGRATIONS
logConfigCheck.info("Checking Config...");
const GhostUserConfig = UserConfigSchema.safeParse(
options || {},
) as SafeParseSuccess<UserConfig>;
if (!GhostUserConfig.success) {
const validationError = fromZodError(
(GhostUserConfig as unknown as SafeParseError<UserConfig>).error,
);
logConfigCheck.error(`Config Error - ${validationError}`);
throw Error("");
}
const GhostConfig = GhostUserConfig.data;
const GCD = {
theme: GhostConfig.theme,
dRI: GhostConfig.disableRouteInjection,
dCO: GhostConfig.disableConsoleOutput,
SM: GhostConfig.sitemap,
RTXT: GhostConfig.robotstxt,
gSite: GhostConfig.ghostURL,
dRSS: GhostConfig.disableRSS,
d404: GhostConfig.disable404,
dOG: GhostConfig.disableSatoriOG,
};
// Check For ENV Variables
if (!GCD.dCO) {
logConfigCheck.info(IC.CHECK_ENV);
}
if (ENV.CONTENT_API_KEY === undefined) {
logConfigCheck.error(IC.KEY_MISSING);
throw IC.KEY_MISSING;
}
if (GCD.gSite === undefined) {
logConfigCheck.warn(IC.NOURL);
if (ENV.CONTENT_API_URL === undefined) {
logConfigCheck.error(IC.URL_MISSING);
throw IC.URL_MISSING;
}
}
if (!GCD.dRI) {
// THEME SELECTOR
if (GCD.theme === IC.DT) {
if (!GCD.dCO) {
logConfigCheck.info(IC.IT + IC.DT);
}
} else {
if (!GCD.dCO) {
logConfigCheck.info(IC.IT + GCD.theme);
}
}
// INJECT ROUTES
//// DEFAULT PROGRAM ROUTES
if (!GCD.dCO) {
logConfigSetup.info(IC.IDR);
}
if (!GCD.d404) {
if (!GCD.dCO) {
logConfigSetup.info(IC.F0FR);
}
injectRoute({
pattern: "/404",
entrypoint: `${IC.PKG}/404.astro`,
});
} else {
if (!GCD.dCO) {
logConfigSetup.info(IC.id404);
}
}
if (!GCD.dRSS) {
if (!GCD.dCO) {
logConfigSetup.info(IC.RSS);
}
injectRoute({
pattern: "/rss-style.xsl",
entrypoint: `${IC.PKG}/rss-style.xsl.ts`,
});
injectRoute({
pattern: "/rss.xml",
entrypoint: `${IC.PKG}/rss.xml.ts`,
});
} else {
if (!GCD.dCO) {
logConfigSetup.info(IC.idRSS);
}
}
if (!GCD.dOG) {
if (!GCD.dCO) {
logConfigSetup.info(IC.satori_e);
}
injectRoute({
pattern: "/open-graph/[slug].png",
entrypoint: `${IC.PKG}/open-graph/[slug].png.ts`,
});
injectRoute({
pattern: "/open-graph/index.png",
entrypoint: `${IC.PKG}/open-graph/index.png.ts`,
});
injectRoute({
pattern: "/open-graph/authors.png",
entrypoint: `${IC.PKG}/open-graph/authors.png.ts`,
});
injectRoute({
pattern: "/open-graph/author/[slug].png",
entrypoint: `${IC.PKG}/open-graph/author/[slug].png.ts`,
});
injectRoute({
pattern: "/open-graph/tags.png",
entrypoint: `${IC.PKG}/open-graph/tags.png.ts`,
});
injectRoute({
pattern: "/open-graph/tag/[slug].png",
entrypoint: `${IC.PKG}/open-graph/tag/[slug].png.ts`,
});
} else {
if (!GCD.dCO) {
logConfigSetup.info(IC.satori_d);
}
}
// THEME ROUTES
if (!GCD.dCO) {
logConfigSetup.info(IC.ITR);
}
injectRoute({
pattern: "/",
entrypoint: `${GCD.theme}/index.astro`,
});
injectRoute({
pattern: "/[slug]",
entrypoint: `${GCD.theme}/[slug].astro`,
});
injectRoute({
pattern: "/tags",
entrypoint: `${GCD.theme}/tags.astro`,
});
injectRoute({
pattern: "/authors",
entrypoint: `${GCD.theme}/authors.astro`,
});
injectRoute({
pattern: "/tag/[slug]",
entrypoint: `${GCD.theme}/tag/[slug].astro`,
});
injectRoute({
pattern: "/author/[slug]",
entrypoint: `${GCD.theme}/author/[slug].astro`,
});
injectRoute({
pattern: "/archives/[...page]",
entrypoint: `${GCD.theme}/archives/[...page].astro`,
});
} else {
if (!GCD.dCO) {
logConfigSetup.info(IC.IRD);
}
}
// IMPORT INTEGRATIONS & INTEGRATION ROUTES
const integrations = [...config.integrations];
// IMPORT INTEGRATION: @ASTROJS/SITEMAP
if (!GCD.dCO) {
logConfigSetup.info(`${IC.CF}@astrojs/sitemap`);
}
if (!integrations.find(({ name }) => name === "@astrojs/sitemap")) {
if (!GCD.dCO) {
logConfigSetup.info(`${IC.II}@astrojs/sitemap`);
}
integrations.push(ghostSitemap(GCD.SM));
} else {
if (!GCD.dCO) {
logConfigSetup.info(`${IC.AIbU}@astrojs/sitemap`);
}
}
// IMPORT INTEGRATION: ASTRO-ROBOTS-TXT
if (!GCD.dCO) {
logConfigSetup.info(`${IC.CF}astro-robots-txt`);
}
if (!integrations.find(({ name }) => name === "astro-robots-txt")) {
if (!GCD.dCO) {
logConfigSetup.info(`${IC.II}astro-robots-txt`);
}
integrations.push(ghostRobots(GCD.RTXT));
} else {
if (!GCD.dCO) {
logConfigSetup.info(`${IC.AIbU}astro-robots-txt`);
}
}
// FINAL STEP TO KEEP INTEGRATION LIVE
try {
updateConfig({
// UPDATE ASTRO CONFIG WITH INTEGRATED INTEGRATIONS
integrations: [ghostSitemap(GCD.SM), ghostRobots(GCD.RTXT)],
vite: {
optimizeDeps: { exclude: ["@resvg/resvg-js"] },
},
});
} catch (e) {
logConfigSetup.error(e as string);
throw e;
}
addVirtualImport({
name: "virtual:@matthiesenxyz/astro-ghostcms/config",
content: `export default ${JSON.stringify(GhostUserConfig.data)}`,
updateConfig,
});
},
"astro:config:done": async ({ logger }) => {
// Config Done
const logConfigDone = logger.fork("astro-ghostcms/config:done");
const pJSON = await fse.readJson(
path.resolve(fileURLToPath(import.meta.url), "..", "package.json"),
);
const pkgVer = pJSON.version;
logConfigDone.info(`Config Done. Current Version: v${pkgVer}`);
},
"astro:server:setup": async ({ logger }) => {
// Dev Server Start
const logServerSetup = logger.fork("astro-ghostcms/server:setup");
const logCurrentVersion = logger.fork("astro-ghostcms/current-version");
const logNpmVersion = logger.fork("astro-ghostcms/npm-pub-version");
const logCheck = logger.fork("astro-ghostcms/check");
const pJSON = await fse.readJson(
path.resolve(fileURLToPath(import.meta.url), "..", "package.json"),
);
const pkgVer = pJSON.version;
const npmVER = await latestVersion(IC.PKG);
if (pkgVer !== npmVER) {
logCurrentVersion.warn(`Current Installed Version is v${pkgVer}`);
logNpmVersion.warn(`Latest Published Version is v${npmVER}`);
logCheck.warn("Please consider updating.");
}
logServerSetup.info(
"Setting up Astro-GhostCMS server for Development!",
);
},
"astro:server:start": async ({ logger }) => {
// Server Start
const logServerStart = logger.fork("astro-ghostcms/server:start");
logServerStart.info("Astro-GhostCMS Integration Ready!");
},
"astro:build:done": async ({ logger }) => {
// Build Done
const logBuildDone = logger.fork("astro-ghostcms/build:done");
const logCurrentVersion = logger.fork("astro-ghostcms/current-version");
const logNpmVersion = logger.fork("astro-ghostcms/npm-pub-version");
const logCheck = logger.fork("astro-ghostcms/check");
const pJSON = await fse.readJson(
path.resolve(fileURLToPath(import.meta.url), "..", "package.json"),
);
const pkgVer = pJSON.version;
const npmVER = await latestVersion(IC.PKG);
if (pkgVer !== npmVER) {
logCurrentVersion.warn(`Current Installed Version is v${pkgVer}`);
logNpmVersion.warn(`Latest Published Version is v${npmVER}`);
logCheck.warn("Please consider updating.");
}
logBuildDone.info(
`Build Complete, Integration Now ready for Production. Astro-GhostCMS v${pkgVer}`,
);
},
},
};
}

View File

@ -1,111 +1,73 @@
{
"name": "@matthiesenxyz/astro-ghostcms",
"description": "Astro GhostCMS integration to allow easier importing of GhostCMS Content",
"version": "3.2.9",
"homepage": "https://astro-ghostcms.xyz/",
"type": "module",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"sideEffects": false,
"author": {
"email": "adam@matthiesen.xyz",
"name": "Adam Matthiesen - MatthiesenXYZ",
"url": "https://matthiesen.xyz"
},
"keywords": [
"astro-component",
"astro-integration",
"withastro",
"astro",
"blog",
"content",
"integration",
"ghost",
"ghostcms",
"ghostcms-theme",
"ghost-theme",
"astro-theme"
],
"repository": {
"type": "git",
"url": "git+https://github.com/MatthiesenXYZ/astro-ghostcms.git"
},
"bugs": {
"url": "https://github.com/MatthiesenXYZ/astro-ghostcms/issues",
"email": "issues@astro-ghostcms.xyz"
},
"main": "index.ts",
"types": "types.d.ts",
"files": [
"src",
".env.demo",
"index.ts",
"tsconfig.json",
"types.d.ts"
],
"exports": {
".": "./index.ts",
"./api": {
"types": "./src/api/index.ts",
"default": "./src/api/index.ts"
"name": "@matthiesenxyz/astro-ghostcms",
"description": "Astro GhostCMS integration to allow easier importing of GhostCMS Content",
"version": "3.3.0-dev.1",
"homepage": "https://astro-ghostcms.xyz/",
"type": "module",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"./api-core": "./src/api/content-api/index.ts",
"./config": {
"types": "./src/integrations/virtual.d.ts",
"default": "./src/integrations/virtual-config.ts"
"sideEffects": false,
"author": {
"email": "adam@matthiesen.xyz",
"name": "Adam Matthiesen - MatthiesenXYZ",
"url": "https://matthiesen.xyz"
},
"./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",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test:ci": "vitest run --coverage.enabled --coverage.reporter='text-summary'"
},
"devDependencies": {
"@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.0.5",
"@resvg/resvg-js": "^2.6.0",
"@ts-ghost/core-api": "^5.1.2",
"astro": "^4.4.0",
"astro-robots-txt": "^1.0.0",
"fs-extra": "^11.1.0",
"package-json": "9.0.0",
"satori": "^0.10.11",
"satori-html": "^0.3.2",
"vite": "^5.1.3",
"vite-tsconfig-paths": "^4.2.2",
"zod": "^3.22.4"
"keywords": [
"astro-component",
"astro-integration",
"withastro",
"astro",
"blog",
"content",
"integration",
"ghost",
"ghostcms",
"ghostcms-theme",
"ghost-theme",
"astro-theme"
],
"repository": {
"type": "git",
"url": "git+https://github.com/MatthiesenXYZ/astro-ghostcms.git"
},
"bugs": {
"url": "https://github.com/MatthiesenXYZ/astro-ghostcms/issues",
"email": "issues@astro-ghostcms.xyz"
},
"files": [
"src",
"index.ts"
],
"exports": {
".": "./src/index.ts",
"./api": "./src/api/index.ts",
"./404.astro": "./src/routes/404.astro",
"./rss-routes/*": "./src/integrations/rssfeed/routes/*",
"./satoriOG": "./src/integrations/satoriog/satori.ts",
"./open-graph/*": "./src/integrations/satoriog/routes/*"
},
"scripts": {
},
"peerDependencies": {
"astro": "^4.4.1"
},
"devDependencies": {
},
"dependencies": {
"@astrojs/rss": "^4.0.5",
"@astrojs/sitemap": "^3.0.5",
"@matthiesenxyz/astro-ghostcms-theme-default": "^0.1.13",
"@resvg/resvg-js": "^2.6.0",
"@ts-ghost/core-api": "^6.0.0",
"@ts-ghost/content-api": "^4.0.12",
"astro-integration-kit": "^0.5.0",
"astro-robots-txt": "^1.0.0",
"picocolors": "1.0.0",
"satori": "^0.10.13",
"satori-html": "0.3.2",
"vite": "^5.1.4"
}
}
}

View File

@ -1,84 +0,0 @@
import { assert, beforeEach, describe, expect, test } from "vitest";
import TS_API from "./content-api";
describe("content-api", () => {
let api: TS_API;
beforeEach(() => {
api = new TS_API("https://ghost.org", "59d4bf56c73c04a18c867dc3ba", "v5.0");
});
test("content-api", () => {
expect(api).toBeDefined();
});
test("content-api shouldn't instantiate with an incorrect url", () => {
assert.throws(() => {
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 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 TS_API(
"https://ghost.org",
"1efedd9db174adee2d23d982:4b74dca0219bad629852191af326a45037346c2231240e0f7aec1f9371cc14e8",
// @ts-expect-error
"v4.0",
);
api.settings;
});
});
test("content-api.posts", () => {
expect(api.posts).toBeDefined();
expect(api.posts.browse).toBeDefined();
expect(api.posts.read).toBeDefined();
});
test("content-api.pages", () => {
expect(api.pages).toBeDefined();
expect(api.pages.browse).toBeDefined();
expect(api.pages.read).toBeDefined();
});
test("content-api.tags", () => {
expect(api.tags).toBeDefined();
expect(api.tags.browse).toBeDefined();
expect(api.tags.read).toBeDefined();
});
test("content-api.tiers", () => {
expect(api.tiers).toBeDefined();
expect(api.tiers.browse).toBeDefined();
expect(api.tiers.read).toBeDefined();
});
test("content-api.authors", () => {
expect(api.authors).toBeDefined();
expect(api.authors.browse).toBeDefined();
expect(api.authors.read).toBeDefined();
// @ts-expect-error
expect(api.authors.add).toBeUndefined();
// @ts-expect-error
expect(api.authors.edit).toBeUndefined();
expect(api.authors).toBeDefined();
});
test("content-api.settings", () => {
expect(api.settings).toBeDefined();
expect(api.settings.fetch).toBeDefined();
// @ts-expect-error
expect(api.settings.read).toBeUndefined();
// @ts-expect-error
expect(api.settings.browse).toBeUndefined();
});
});

View File

@ -1,116 +0,0 @@
import {
APIComposer,
BasicFetcher,
HTTPClient,
contentAPICredentialsSchema,
slugOrIdSchema,
} from "@ts-ghost/core-api";
import {
authorsIncludeSchema,
authorsSchema,
pagesIncludeSchema,
pagesSchema,
postsIncludeSchema,
postsSchema,
settingsSchema,
tagsIncludeSchema,
tagsSchema,
tiersIncludeSchema,
tiersSchema,
} from "./schemas";
export type { ContentAPICredentials, APIVersions } from "@ts-ghost/core-api";
export enum BrowseEndpointType {
authors = "authors",
tiers = "tiers",
posts = "posts",
pages = "pages",
tags = "tags",
settings = "settings",
}
export default class TS_API<Version extends `v5.${string}` = any> {
private httpClient: HTTPClient;
constructor(
protected readonly url: string,
protected readonly key: string,
protected readonly version: Version,
) {
const apiCredentials = contentAPICredentialsSchema.parse({
key,
version,
url,
});
this.httpClient = new HTTPClient({
...apiCredentials,
endpoint: "content",
});
}
get authors() {
return new APIComposer(
"authors",
{
schema: authorsSchema,
identitySchema: slugOrIdSchema,
include: authorsIncludeSchema,
},
this.httpClient,
).access(["read", "browse"]);
}
get tiers() {
return new APIComposer(
"tiers",
{
schema: tiersSchema,
identitySchema: slugOrIdSchema,
include: tiersIncludeSchema,
},
this.httpClient,
).access(["browse", "read"]);
}
get posts() {
return new APIComposer(
"posts",
{
schema: postsSchema,
identitySchema: slugOrIdSchema,
include: postsIncludeSchema,
},
this.httpClient,
).access(["browse", "read"]);
}
get pages() {
return new APIComposer(
"pages",
{
schema: pagesSchema,
identitySchema: slugOrIdSchema,
include: pagesIncludeSchema,
},
this.httpClient,
).access(["browse", "read"]);
}
get tags() {
return new APIComposer(
"tags",
{
schema: tagsSchema,
identitySchema: slugOrIdSchema,
include: tagsIncludeSchema,
},
this.httpClient,
).access(["browse", "read"]);
}
get settings() {
return new BasicFetcher(
"settings",
{ output: settingsSchema },
this.httpClient,
);
}
}

View File

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

View File

@ -1,164 +0,0 @@
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import createFetchMock from "vitest-fetch-mock";
import TS_API from "../../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 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 });
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() '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([]);
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: TS_API;
beforeEach(() => {
api = new TS_API(
"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"]);
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

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

View File

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

View File

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

View File

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

View File

@ -1,69 +0,0 @@
import { describe, expect, test } from "vitest";
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 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 });
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 };
// biome-ignore lint/style/useConst: <explanation>
let 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 };
// 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
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

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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
import { TS_API } from "./content-api";
import type { Page, Post } from "./content-api/schemas";
import { TSGhostContentAPI } from "@ts-ghost/content-api";
import type { Page, Post } from "../schemas/api";
// LOAD ENVIRONMENT VARIABLES
import { loadEnv } from "vite";
import config from "../integrations/virtual-config";
import config from "virtual:@matthiesenxyz/astro-ghostcms/config";
const CONF_URL = config.ghostURL;
@ -17,7 +17,7 @@ const { CONTENT_API_KEY, CONTENT_API_URL } = loadEnv(
const ghostApiKey = CONTENT_API_KEY;
const ghostUrl = CONF_URL ? CONF_URL : CONTENT_API_URL;
const version = "v5.0";
const api = new TS_API(ghostUrl, ghostApiKey, version);
const api = new TSGhostContentAPI(ghostUrl, ghostApiKey, version);
export const getAllAuthors = async () => {
const results = await api.authors

View File

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

View File

@ -1,32 +0,0 @@
import { describe, expect, it } from "vitest";
// Modified version of invariant script to allow tests
const isProduction = false;
const prefix: string = "Invariant failed";
function invariant(condition: any, message?: string | (() => string)) {
if (condition) {
return;
}
if (isProduction) {
throw new Error(prefix);
}
const provided: string | undefined =
typeof message === "function" ? message() : message;
const value: string = provided ? `${prefix}: ${provided}` : prefix;
return value;
}
// TEST SECTION
const testTrue = true;
const testFalse = false;
describe("test invariant", () => {
it("Test `true` value", () => {
invariant(testTrue, "This should not error");
expect(null);
});
it("Test `false` value", () => {
invariant(testFalse, "This should Error");
expect(String("Invariant failed"));
});
});

View File

@ -20,7 +20,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
const tinyinvariant = "merged";
const isProduction: boolean = process.env.NODE_ENV === "production";
const prefix: string = "Invariant failed";

View File

@ -0,0 +1,170 @@
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 External Integrations
import sitemap from "@astrojs/sitemap";
import robotsTxt from "astro-robots-txt";
// Import Internal Integrations
import ghostOGImages from "./integrations/satoriog"
import ghostRSS from "./integrations/rssfeed"
import ghostThemeProvider from "./integrations/themeprovider"
// 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,
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...')
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`)
}
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')))
// 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'))
}
}
// 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...'))
}
}
// 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 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": ({ logger }) => {
const GhostLogger = logger.fork(`${c.bold(c.blue('👻 Astro-GhostCMS'))}${c.gray("/")}${c.bold(c.green('DEV'))}`);
GhostLogger.info(c.bold(c.magenta('Running Astro-GhostCMS in Deveopment mode')))
},
"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

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

View File

@ -1,28 +0,0 @@
import robotsTxt, { type RobotsTxtOptions } from "astro-robots-txt";
import type { UserConfig } from "../schemas";
export function getRobotsTxtConfig(
opts: UserConfig["robotstxt"],
): RobotsTxtOptions {
const robotsConfig: RobotsTxtOptions = {};
if (opts?.host) {
robotsConfig.host = opts.host;
}
if (opts?.policy) {
robotsConfig.policy = opts.policy;
}
if (opts?.sitemap) {
robotsConfig.sitemap = opts.sitemap;
}
if (opts?.sitemapBaseFileName) {
robotsConfig.sitemapBaseFileName = opts.sitemapBaseFileName;
}
return robotsConfig;
}
/**
* A wrapped version of the `astro-robots-txt` integration for GhostCMS.
*/
export default function ghostRobots(opts: UserConfig["robotstxt"]) {
return robotsTxt(getRobotsTxtConfig(opts));
}

View File

@ -0,0 +1,48 @@
import { createResolver, defineIntegration } from "astro-integration-kit";
import { corePlugins } from "astro-integration-kit/plugins";
import c from "picocolors";
import { z } from "astro/zod";
export default defineIntegration({
name: "@matthiesenxyz/astro-ghostcms-rss",
optionsSchema: z.object({
verbose: z.boolean().optional().default(false),
}),
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...')))
const rssRoute = "@matthiesenxyz/astro-ghostcms/rss-routes"
injectRoute({
pattern: "/rss-style.xsl",
entrypoint: `${rssRoute}/rss-style.xsl.ts`,
});
injectRoute({
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')}`);
if (options.verbose) {
RSSLogger.info(c.bold(c.green('RSS Feed Setup Complete')))
}
}
}
}
})

View File

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

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

@ -0,0 +1,73 @@
import { createResolver, defineIntegration } from "astro-integration-kit";
import { corePlugins } from "astro-integration-kit/plugins";
import c from "picocolors";
import { z } from "astro/zod";
export default defineIntegration({
name: "@matthiesenxyz/astro-ghostcms-satoriog",
optionsSchema: z.object({
verbose: z.boolean().optional().default(false),
}),
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...')))
const pkgname = "@matthiesenxyz/astro-ghostcms/open-graph"
injectRoute({
pattern: "/open-graph/[slug].png",
entrypoint: `${pkgname}/[slug].png.ts`,
});
injectRoute({
pattern: "/open-graph/index.png",
entrypoint: `${pkgname}/index.png.ts`,
});
injectRoute({
pattern: "/open-graph/authors.png",
entrypoint: `${pkgname}/authors.png.ts`,
});
injectRoute({
pattern: "/open-graph/author/[slug].png",
entrypoint: `${pkgname}/author/[slug].png.ts`,
});
injectRoute({
pattern: "/open-graph/tags.png",
entrypoint: `${pkgname}/tags.png.ts`,
});
injectRoute({
pattern: "/open-graph/tag/[slug].png",
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')}`);
if (options.verbose) {
SatoriLogger.info(c.bold(c.green('OG Image Integration Setup Complete')))
}
}
}
}
})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,24 +1,5 @@
import type { Resvg } from "@resvg/resvg-js";
import type satori from "satori";
export type { UserConfig, GhostUserConfig } from "./src/schemas";
export type {
Author,
AuthorsIncludeSchema,
Page,
PagesIncludeSchema,
Post,
PostsIncludeSchema,
Settings,
Tag,
TagsIncludeSchema,
Tier,
TiersIncludeSchema,
} from "./src/api/index.ts";
export type { ContentAPICredentials, APIVersions } from "@ts-ghost/core-api";
type SatoriParameters = Parameters<typeof satori>;
type SatoriOptions = SatoriParameters[1];
type ResvgOptions = NonNullable<ConstructorParameters<typeof Resvg>[1]>;

View File

@ -1,38 +0,0 @@
import sitemap, { type SitemapOptions } from "@astrojs/sitemap";
import type { UserConfig } from "../schemas";
export function getSitemapConfig(opts: UserConfig["sitemap"]): SitemapOptions {
const sitemapConfig: SitemapOptions = {};
if (opts?.filter) {
sitemapConfig.filter = opts.filter;
}
if (opts?.changefreq) {
sitemapConfig.changefreq = opts.changefreq;
}
if (opts?.entryLimit) {
sitemapConfig.entryLimit = opts.entryLimit;
}
if (opts?.customPages) {
sitemapConfig.customPages = opts.customPages;
}
if (opts?.i18n) {
sitemapConfig.i18n = opts.i18n;
}
if (opts?.lastmod) {
sitemapConfig.lastmod = opts.lastmod;
}
if (opts?.priority) {
sitemapConfig.priority = opts.priority;
}
if (opts?.serialize) {
sitemapConfig.serialize = opts.serialize;
}
return sitemapConfig;
}
/**
* A wrapped version of the `@astrojs/sitemap` integration for GhostCMS.
*/
export default function ghostSitemap(opts: UserConfig["sitemap"]) {
return sitemap(getSitemapConfig(opts));
}

View File

@ -0,0 +1,81 @@
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().optional().default("@matthiesenxyz/astro-ghostcms-theme-default"),
verbose: z.boolean().optional().default(false),
}),
plugins: [...corePlugins],
setup({ options }) {
const { resolve } = createResolver(import.meta.url);
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')}`);
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'))
} else {
themeLogger.info(`${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')}`);
if ( options.verbose ) {
themeLogger.info(c.bold(c.green('Provider Setup Complete')))
}
}
}
}
})

View File

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

View File

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

View File

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

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

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

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

@ -5,10 +5,10 @@ import {
ghostSocialMediaSchema,
ghostVisibilitySchema,
} from "@ts-ghost/core-api";
import { z } from "zod";
import { z } from "astro/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

@ -5,10 +5,10 @@ import {
ghostSocialMediaSchema,
ghostVisibilitySchema,
} from "@ts-ghost/core-api";
import { z } from "zod";
import { z } from "astro/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

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,8 @@
import { z } from "astro/zod";
import { RobotsTxtSchema, SitemapSchema } from "./index";
import { RobotsTxtSchema } from "./robots.ts";
import { SitemapSchema } from "./sitemap.ts";
export const UserConfigSchema = z.object({
export const GhostUserConfigSchema = z.object({
/** OPTIONAL - Either set the URL in your .env or put it here
* @example
* // https://astro.build/config
@ -13,31 +14,24 @@ export const UserConfigSchema = z.object({
* ],
* }); */
ghostURL: z.string().url().optional(),
/** OPTIONAL - Allows the user to disable the `/rss.xml` injection */
disableRSS: z.boolean().default(false),
/** OPTIONAL - Allows the user to disable the `/open-graph/*` route injection
* @ This feature uses `satori` to generate OpenGraph Images
/** OPTIONAL - Disable the theme provider
* @default false
*/
disableSatoriOG: z.boolean().default(false),
/** OPTIONAL - Allows the user to disable the `/404` injection */
disable404: z.boolean().default(false),
/** OPTIONAL - Disable Route Injector
* This option allows the user to disable the route injection system and utilize just the integraions other functions. Such as API, sitemap and robotstxt integrations. */
disableRouteInjection: z.boolean().default(false),
/** OPTIONAL - (Default: true) Allows the user to disable "info" console output */
disableConsoleOutput: z.boolean().default(true),
/** OPTIONAL - Theme Selector
* This option allows the user to replace the included theme with an external npm module
* @example
* // https://astro.build/config
* export default defineConfig({
* integrations: [
* ghostcms({
* theme: "@matthiesenxyz/astro-ghostcms-theme-default"
* })
* ],
* }); */
theme: z.string().default("@matthiesenxyz/astro-ghostcms-theme-default"),
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(),
/** Allows the user to disable the provided 404 page */
disableDefault404: z.boolean().optional().default(false),
/** Allows the user to disable the provided RSS Feed */
enableRSSFeed: z.boolean().optional().default(true),
/** Allows the user to Enable or Disable the default Satori OG Image Generation
* @default true
*/
enableOGImages: z.boolean().optional().default(true),
/** OPTIONAL - astrojs/sitemap
* This option allows the user to configure the included integration
* Options shown are the availble options
@ -50,8 +44,11 @@ export const UserConfigSchema = z.object({
* REFERENCE https://www.npmjs.com/package/astro-robots-txt#configuration
*/
robotstxt: RobotsTxtSchema.optional(),
/** Allows the user to turn on/off Full Console Logs
* @default true
*/
fullConsoleLogs: z.boolean().optional().default(false),
});
/** USER CONFIGURATION SCHEMA */
export type UserConfig = z.infer<typeof UserConfigSchema>;
export type GhostUserConfig = z.infer<typeof UserConfigSchema>;
export type GhostUserConfig = z.infer<typeof GhostUserConfigSchema>;

View File

@ -1,82 +0,0 @@
/** MIT License
Copyright (c) 2024 Florian Lefebvre
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. */
import { type Mock, afterEach, describe, expect, test, vi } from "vitest";
import { addVirtualImport } from "./add-virtual-import.js";
import { addVitePlugin } from "./add-vite-plugin.js";
vi.mock("./add-vite-plugin");
const pluginNameStub = <T extends string>(name: T): `vite-plugin-${T}` =>
`vite-plugin-${name}`;
describe("add-virtual-import", () => {
const name = "test-module";
const content = "export default {}";
afterEach(() => {
vi.clearAllMocks();
});
test("It should call `addVitePlugin`", () => {
const updateConfig = vi.fn();
addVirtualImport({
name,
content,
updateConfig,
});
expect(addVitePlugin).toHaveBeenCalled();
});
test("`addVitePlugin` should get called with the correct plugin name", () => {
const updateConfig = vi.fn();
addVirtualImport({
name,
content,
updateConfig,
});
const expectedName = pluginNameStub(name);
const { plugin } = (addVitePlugin as Mock).mock.lastCall[0];
expect(plugin.name).toEqual(expectedName);
});
test("Virtual module should resolve correct name", () => {
const updateConfig = vi.fn();
addVirtualImport({
name,
content,
updateConfig,
});
const { plugin } = (addVitePlugin as Mock).mock.lastCall[0];
const resolvedVirtualModuleId = plugin.resolveId(name);
expect(resolvedVirtualModuleId).toEqual(`\0${name}`);
});
});

View File

@ -1,93 +0,0 @@
/** MIT License
Copyright (c) 2024 Florian Lefebvre
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. */
import type { HookParameters } from "astro";
import type { Plugin } from "vite";
import { addVitePlugin } from "./add-vite-plugin.js";
const resolveVirtualModuleId = <T extends string>(id: T): `\0${T}` => {
return `\0${id}`;
};
const createVirtualModule = (name: string, content: string): Plugin => {
const pluginName = `vite-plugin-${name}`;
return {
name: pluginName,
resolveId(id) {
if (id === name) {
return resolveVirtualModuleId(id);
}
},
load(id) {
if (id === resolveVirtualModuleId(name)) {
return content;
}
},
};
};
/**
* Creates a Vite virtual module and updates the Astro config.
* Virtual imports are useful for passing things like config options, or data computed within the integration.
*
* @param {object} params
* @param {string} params.name
* @param {string} params.content
* @param {import("astro").HookParameters<"astro:config:setup">["updateConfig"]} params.updateConfig
*
* @see https://astro-integration-kit.netlify.app/utilities/add-virtual-import/
*
* @example
* ```ts
* // my-integration/index.ts
* import { addVirtualImport } from "astro-integration-kit";
*
* addVirtualImport(
* name: 'virtual:my-integration/config',
* content: `export default ${ JSON.stringify({foo: "bar"}) }`,
* updateConfig
* );
* ```
*
* This is then readable anywhere else in your integration:
*
* ```ts
* // myIntegration/src/component/layout.astro
* import config from "virtual:my-integration/config";
*
* console.log(config.foo) // "bar"
* ```
*/
export const addVirtualImport = ({
name,
content,
updateConfig,
}: {
name: string;
content: string;
updateConfig: HookParameters<"astro:config:setup">["updateConfig"];
}) => {
addVitePlugin({
plugin: createVirtualModule(name, content),
updateConfig,
});
};

View File

@ -1,69 +0,0 @@
import type { Plugin } from "vite";
import { describe, expect, test, vi } from "vitest";
import { addVitePlugin } from "./add-vite-plugin.js";
describe("addVitePlugin", () => {
test("Should run", () => {
const updateConfig = vi.fn();
expect(() =>
addVitePlugin({
plugin: null,
updateConfig,
}),
).not.toThrow();
});
test("Should call updateConfig", () => {
const updateConfig = vi.fn();
addVitePlugin({
plugin: null,
updateConfig,
});
expect(updateConfig).toHaveBeenCalled();
});
test("Should add vite plugin", () => {
let plugin: Plugin;
const pluginName = "test-plugin";
const updateConfig = vi.fn((config) => {
plugin = config.vite.plugins[0];
});
const expectedPlugin = {
name: pluginName,
};
addVitePlugin({
plugin: expectedPlugin,
updateConfig,
});
// @ts-ignore
expect(plugin).toBeDefined();
});
test("Plugin name should match", () => {
let plugin: Plugin;
const pluginName = "test-plugin";
const updateConfig = vi.fn((config) => {
plugin = config.vite.plugins[0];
});
const expectedPlugin = {
name: pluginName,
};
addVitePlugin({
plugin: expectedPlugin,
updateConfig,
});
// @ts-ignore
expect(plugin.name).toBe(pluginName);
});
});

View File

@ -1,55 +0,0 @@
/** MIT License
Copyright (c) 2024 Florian Lefebvre
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. */
import type { HookParameters } from "astro";
import type { PluginOption } from "vite";
/**
* Adds a [vite plugin](https://vitejs.dev/guide/using-plugins) to the
* Astro config.
*
* @param {Params} params
* @param {import("vite").Plugin} params.plugin
* @param {import("astro").HookParameters<"astro:config:setup">["updateConfig"]} params.updateConfig
*
* @see https://astro-integration-kit.netlify.app/utilities/add-vite-plugin/
*
* @example
* ```ts
* addVitePlugin({
* plugin,
* updateConfig
* })
* ```
*/
export const addVitePlugin = ({
plugin,
updateConfig,
}: {
plugin: PluginOption;
updateConfig: HookParameters<"astro:config:setup">["updateConfig"];
}) => {
updateConfig({
vite: {
plugins: [plugin],
},
});
};

View File

@ -1,26 +0,0 @@
import packageJson from "package-json";
export type Options = {
/**
A semver range or [dist-tag](https://docs.npmjs.com/cli/dist-tag).
*/
readonly version?: string;
};
/**
Get the latest version of an npm package.
@example
```
console.log(await latestVersion('ava'));
//=> '0.18.0'
```
*/
export default async function latestVersion(
packageName: string,
options?: Options,
) {
const { version } = await packageJson(packageName.toLowerCase(), options);
return version;
}

View File

@ -1,22 +0,0 @@
import * as zod from "zod";
export class ValidationError extends Error {
name;
details;
constructor(message, options) {
super(message, options);
this.name = "ZodValidationError";
this.details = getIssuesFromErrorOptions(options);
}
toString() {
return this.message;
}
}
function getIssuesFromErrorOptions(options) {
if (options) {
const cause = options.cause;
if (cause instanceof zod.ZodError) {
return cause.issues;
}
}
return [];
}

View File

@ -1,5 +0,0 @@
export const ISSUE_SEPARATOR = "; ";
export const MAX_ISSUES_IN_MESSAGE = 99;
export const PREFIX = "Validation error";
export const PREFIX_SEPARATOR = ": ";
export const UNION_SEPARATOR = ", or ";

View File

@ -1,10 +0,0 @@
import { fromZodIssue } from "./fromZodIssue";
export const errorMap = (issue, ctx) => {
const error = fromZodIssue({
...issue,
message: issue.message ?? ctx.defaultError,
});
return {
message: error.message,
};
};

View File

@ -1,37 +0,0 @@
import { ValidationError } from "./ValidationError";
import {
ISSUE_SEPARATOR,
MAX_ISSUES_IN_MESSAGE,
PREFIX,
PREFIX_SEPARATOR,
UNION_SEPARATOR,
} from "./config";
import { getMessageFromZodIssue } from "./fromZodIssue";
import { prefixMessage } from "./prefixMessage";
export function fromZodError(zodError, options = {}) {
const {
maxIssuesInMessage = MAX_ISSUES_IN_MESSAGE,
issueSeparator = ISSUE_SEPARATOR,
unionSeparator = UNION_SEPARATOR,
prefixSeparator = PREFIX_SEPARATOR,
prefix = PREFIX,
includePath = true,
} = options;
const zodIssues = zodError.errors;
const reason =
zodIssues.length === 0
? zodError.message
: zodIssues
.slice(0, maxIssuesInMessage)
.map((issue) =>
getMessageFromZodIssue({
issue,
issueSeparator,
unionSeparator,
includePath,
}),
)
.join(issueSeparator);
const message = prefixMessage(reason, prefix, prefixSeparator);
return new ValidationError(message, { cause: zodError });
}

View File

@ -1,61 +0,0 @@
import * as zod from "zod";
import { ValidationError } from "./ValidationError";
import {
ISSUE_SEPARATOR,
PREFIX,
PREFIX_SEPARATOR,
UNION_SEPARATOR,
} from "./config";
import { prefixMessage } from "./prefixMessage";
import { isNonEmptyArray } from "./utils/NonEmptyArray";
import { joinPath } from "./utils/joinPath";
export function getMessageFromZodIssue(props) {
const { issue, issueSeparator, unionSeparator, includePath } = props;
if (issue.code === "invalid_union") {
return issue.unionErrors
.reduce((acc, zodError) => {
const newIssues = zodError.issues
.map((issue) =>
getMessageFromZodIssue({
issue,
issueSeparator,
unionSeparator,
includePath,
}),
)
.join(issueSeparator);
if (!acc.includes(newIssues)) {
acc.push(newIssues);
}
return acc;
}, [])
.join(unionSeparator);
}
if (includePath && isNonEmptyArray(issue.path)) {
if (issue.path.length === 1) {
const identifier = issue.path[0];
if (typeof identifier === "number") {
return `${issue.message} at index ${identifier}`;
}
}
return `${issue.message} at "${joinPath(issue.path)}"`;
}
return issue.message;
}
export function fromZodIssue(issue, options = {}) {
const {
issueSeparator = ISSUE_SEPARATOR,
unionSeparator = UNION_SEPARATOR,
prefixSeparator = PREFIX_SEPARATOR,
prefix = PREFIX,
includePath = true,
} = options;
const reason = getMessageFromZodIssue({
issue,
issueSeparator,
unionSeparator,
includePath,
});
const message = prefixMessage(reason, prefix, prefixSeparator);
return new ValidationError(message, { cause: new zod.ZodError([issue]) });
}

View File

@ -1,7 +0,0 @@
export { ValidationError } from "./ValidationError";
export { isValidationError } from "./isValidationError";
export { isValidationErrorLike } from "./isValidationErrorLike";
export { errorMap } from "./errorMap";
export { fromZodIssue } from "./fromZodIssue";
export { fromZodError } from "./fromZodError";
export { toValidationError } from "./toValidationError";

View File

@ -1,4 +0,0 @@
import { ValidationError } from "./ValidationError";
export function isValidationError(err) {
return err instanceof ValidationError;
}

View File

@ -1,3 +0,0 @@
export function isValidationErrorLike(err) {
return err instanceof Error && err.name === "ZodValidationError";
}

View File

@ -1,13 +0,0 @@
import { PREFIX } from "./config";
export function prefixMessage(message, prefix, prefixSeparator) {
if (prefix !== null) {
if (message.length > 0) {
return [prefix, message].join(prefixSeparator);
}
return prefix;
}
if (message.length > 0) {
return message;
}
return PREFIX;
}

View File

@ -1,14 +0,0 @@
import * as zod from "zod";
import { ValidationError } from "./ValidationError";
import { fromZodError } from "./fromZodError";
export const toValidationError =
(options = {}) =>
(err) => {
if (err instanceof zod.ZodError) {
return fromZodError(err, options);
}
if (err instanceof Error) {
return new ValidationError(err.message, { cause: err });
}
return new ValidationError("Unknown error");
};

View File

@ -1,3 +0,0 @@
export function isNonEmptyArray(value) {
return value.length !== 0;
}

View File

@ -1,22 +0,0 @@
const identifierRegex = /[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*/u;
export function joinPath(path) {
if (path.length === 1) {
return path[0].toString();
}
return path.reduce((acc, item) => {
if (typeof item === "number") {
return `${acc}[${item.toString()}]`;
}
if (item.includes('"')) {
return `${acc}["${escapeQuotes(item)}"]`;
}
if (!identifierRegex.test(item)) {
return `${acc}["${item}"]`;
}
const separator = acc.length === 0 ? "" : ".";
return acc + separator + item;
}, "");
}
function escapeQuotes(str) {
return str.replace(/"/g, '\\"');
}

View File

@ -1,15 +0,0 @@
/// <reference types="vitest" />
/// <reference types="vite/client" />
import tsconfigPaths from "vite-tsconfig-paths";
import { defineProject } from "vitest/config";
export default defineProject({
plugins: [tsconfigPaths()],
test: {
globals: true,
include: ["./**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
watchExclude: [".*\\/node_modules\\/.*", ".*\\/build\\/.*"],
exclude: ["node_modules", "dist", ".idea", ".git", ".cache"],
},
});

View File

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

View File

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

View File

@ -0,0 +1 @@
CONTENT_API_KEY=a33da3965a3a9fb2c6b3f63b48

View File

@ -10,12 +10,10 @@ export default defineConfig({
//tailwind(),
UnoCSS({ injectReset: true }),
ghostcms({
disable404: false,
disableRSS: false,
disableRouteInjection: false,
disableConsoleOutput: false,
theme: "@matthiesenxyz/astro-ghostcms-brutalbyelian",
ghostURL: "https://ghostdemo.matthiesen.xyz",
ThemeProvider: {
theme: '@matthiesenxyz/astro-ghostcms-brutalbyelian',
},
}),
],
});

View File

@ -1,5 +1,5 @@
{
"name": "playground",
"name": "astro-playground",
"type": "module",
"version": "0.0.2",
"private": true,
@ -12,10 +12,8 @@
},
"dependencies": {
"astro": "^4.4.0",
"@matthiesenxyz/astro-ghostcms-theme-default": "workspace:*",
"@matthiesenxyz/astro-ghostcms-catppuccin": "workspace:*",
"@matthiesenxyz/astro-ghostcms-brutalbyelian": "workspace:*",
"@matthiesenxyz/astro-ghostcms": "workspace:*",
"@matthiesenxyz/astro-ghostcms-brutalbyelian": "workspace:*",
"@astrojs/tailwind": "^5.1.0",
"@unocss/astro": "^0.58.5",
"tailwindcss": "^3.3.5"

View File

Before

Width:  |  Height:  |  Size: 749 B

After

Width:  |  Height:  |  Size: 749 B

View File

@ -0,0 +1,3 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />
/// <reference types="../.astro/@matthiesenxyz/astro-ghostcms/config.d.ts" />

View File

Before

Width:  |  Height:  |  Size: 696 B

After

Width:  |  Height:  |  Size: 696 B

View File

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

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