Add new Starlight-GhostCMS plugin #66
|
@ -0,0 +1,17 @@
|
|||
declare module 'astro:content' {
|
||||
export interface AstroCollectionEntry<TData> {
|
||||
body: string
|
||||
collection: string
|
||||
data: TData
|
||||
id: string
|
||||
render: () => Promise<{
|
||||
Content: import('astro').MarkdownInstance<object>['Content']
|
||||
}>
|
||||
slug: string
|
||||
}
|
||||
|
||||
export function getCollection<TData>(
|
||||
collection: string,
|
||||
filter?: (entry: AstroCollectionEntry<TData>) => boolean,
|
||||
): Promise<AstroCollectionEntry<TData>[]>
|
||||
}
|
|
@ -17,6 +17,7 @@ export default function starlightBlogPlugin(userConfig?: StarlightGhostConfig):
|
|||
...starlightConfig.components,
|
||||
...overrideStarlightComponent(starlightConfig.components, logger, 'MarkdownContent'),
|
||||
...overrideStarlightComponent(starlightConfig.components, logger, 'Sidebar'),
|
||||
...overrideStarlightComponent(starlightConfig.components, logger, "SiteTitle"),
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -32,22 +32,29 @@
|
|||
"email": "issues@astro-ghostcms.xyz"
|
||||
},
|
||||
"main": "index.ts",
|
||||
"types": "types.d.ts",
|
||||
"types": "index.ts",
|
||||
"files": [
|
||||
"src",
|
||||
".env.demo",
|
||||
"index.ts",
|
||||
"tsconfig.json",
|
||||
"types.d.ts"
|
||||
],
|
||||
"exports": {
|
||||
".": "./index.ts"
|
||||
".": "./index.ts",
|
||||
"./overrides/MarkdownContent.astro": "./src/overrides/MarkdownContent.astro",
|
||||
"./overrides/Sidebar.astro": "./src/overrides/SideBar.astro",
|
||||
"./overrides/SiteTitle.astro": "./src/overrides/SiteTitle.astro",
|
||||
"./routes/index.astro": "./src/routes/index.astro",
|
||||
"./routes/[slug].astro": "./src/routes/[slug].astro",
|
||||
"./schema": "./src/schemas/config.ts",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/starlight": "0.19.0",
|
||||
"@ts-ghost/core-api": "5.1.2",
|
||||
"vite": "^5.1.2",
|
||||
"astro": "4.3.7"
|
||||
},
|
||||
"peerdependencies": {
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
---
|
||||
import { getBlogEntryMetadata, type StarlightBlogEntry } from '../utils/content'
|
||||
|
||||
import type { Post } from '../schemas/posts'
|
||||
import Author from './Author.astro'
|
||||
|
||||
interface Props {
|
||||
entry: StarlightBlogEntry
|
||||
entry: Post
|
||||
}
|
||||
|
||||
const { entry } = Astro.props
|
||||
const { authors, date } = getBlogEntryMetadata(entry)
|
||||
const { authors, published_at, created_at } = entry
|
||||
|
||||
const hasAuthors = authors.length > 0
|
||||
const hasAuthors = authors !== undefined
|
||||
---
|
||||
|
||||
<div class="metadata not-content">
|
||||
<time datetime={entry.data.date.toISOString()}>
|
||||
{date}
|
||||
<time datetime={published_at?published_at:created_at.toString()}>
|
||||
{published_at}
|
||||
</time>
|
||||
{
|
||||
hasAuthors ? (
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
---
|
||||
import type { StarlightBlogEntry } from '../utils/content'
|
||||
|
||||
import Preview from './Preview.astro'
|
||||
import type { Post } from '../schemas/posts'
|
||||
|
||||
interface Props {
|
||||
entries: StarlightBlogEntry[]
|
||||
entries: Post[]
|
||||
}
|
||||
|
||||
const { entries } = Astro.props
|
||||
|
|
|
@ -1,26 +1,25 @@
|
|||
---
|
||||
import { getBlogEntryExcerpt, type StarlightBlogEntry } from '../utils/content'
|
||||
|
||||
import type { Post } from '../schemas/posts'
|
||||
import Metadata from './Metadata.astro'
|
||||
|
||||
interface Props {
|
||||
entry: StarlightBlogEntry
|
||||
entry: Post
|
||||
}
|
||||
|
||||
const { entry } = Astro.props
|
||||
|
||||
const Excerpt = await getBlogEntryExcerpt(entry)
|
||||
const Excerpt = entry.excerpt
|
||||
---
|
||||
|
||||
<article class="preview">
|
||||
<header>
|
||||
<h2>
|
||||
<a href={`/${entry.slug}`}>{entry.data.title}</a>
|
||||
<a href={`/${entry.slug}`}>{entry.title}</a>
|
||||
</h2>
|
||||
<Metadata entry={entry} />
|
||||
</header>
|
||||
<div class="sl-markdown-content">
|
||||
{typeof Excerpt === 'string' ? Excerpt : <Excerpt />}
|
||||
{typeof Excerpt === 'string' ? Excerpt : entry.excerpt}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
import type { Props } from "@astrojs/starlight/props";
|
||||
import AstrolightSiteTitle from "@astrojs/starlight/components/SiteTitle.astro";
|
||||
//import config from 'virtual:starlight-ghost-config'
|
||||
---
|
||||
|
||||
<AstrolightSiteTitle {...Astro.props} />
|
||||
<div>
|
||||
<a href="/blog">Blog</a>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
border-inline-start: 1px solid var(--sl-color-gray-5);
|
||||
display: none;
|
||||
padding-inline-start: 1rem;
|
||||
}
|
||||
|
||||
@media (min-width: 50rem) {
|
||||
div {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--sl-color-text-accent);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
import type { InferGetStaticPropsType } from 'astro'
|
||||
import config from 'virtual:starlight-ghost-config'
|
||||
|
||||
import Page from '../components/Page.astro'
|
||||
import Posts from '../components/Posts.astro'
|
||||
import PrevNextLinks from '../components/PrevNextLinks.astro'
|
||||
import { getPageProps } from '../utils/page'
|
||||
|
||||
export const prerender = true
|
||||
|
||||
export function getStaticPaths() {
|
||||
}
|
||||
|
||||
type Props = InferGetStaticPropsType<typeof getStaticPaths>
|
||||
|
||||
const { entries, nextLink, prevLink } = Astro.props
|
||||
|
||||
const pageProps = getPageProps(config.title)
|
||||
---
|
||||
|
||||
<Page {...pageProps}>
|
||||
<Posts {entries} />
|
||||
<footer class="not-content">
|
||||
<PrevNextLinks next={nextLink} prev={prevLink} />
|
||||
</footer>
|
||||
</Page>
|
||||
|
||||
<style>
|
||||
:global(.content-panel:first-of-type) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:global(.content-panel:nth-of-type(2)) {
|
||||
border-top: none;
|
||||
}
|
||||
</style>
|
|
@ -1,9 +1,16 @@
|
|||
/** @ts-expect-error */
|
||||
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.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import {
|
||||
ghostCodeInjectionSchema,
|
||||
ghostIdentitySchema,
|
||||
ghostMetadataSchema,
|
||||
ghostSocialMediaSchema,
|
||||
ghostVisibilitySchema,
|
||||
} from "@ts-ghost/core-api";
|
||||
import { z } from "astro/zod";
|
||||
|
||||
import { authorsSchema } from "./authors";
|
||||
import { tagsSchema } from "./tags";
|
||||
|
||||
const postsAuthorSchema = authorsSchema.extend({
|
||||
url: z.string().nullish(),
|
||||
});
|
||||
export const postsSchema = z.object({
|
||||
...ghostIdentitySchema.shape,
|
||||
...ghostMetadataSchema.shape,
|
||||
title: z.string(),
|
||||
html: z.string().catch(""),
|
||||
plaintext: z.string().nullish(),
|
||||
comment_id: z.string().nullable(),
|
||||
feature_image: z.string().nullable(),
|
||||
feature_image_alt: z.string().nullable(),
|
||||
feature_image_caption: z.string().nullable(),
|
||||
featured: z.boolean(),
|
||||
custom_excerpt: z.string().nullable(),
|
||||
...ghostCodeInjectionSchema.shape,
|
||||
...ghostSocialMediaSchema.shape,
|
||||
visibility: ghostVisibilitySchema,
|
||||
custom_template: z.string().nullable(),
|
||||
canonical_url: z.string().nullable(),
|
||||
authors: z.array(postsAuthorSchema).optional(),
|
||||
tags: z.array(tagsSchema).optional(),
|
||||
primary_author: postsAuthorSchema.nullish(),
|
||||
primary_tag: tagsSchema.nullish(),
|
||||
url: z.string(),
|
||||
excerpt: z.string().catch(""),
|
||||
reading_time: z.number().optional().default(0),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string().nullish(),
|
||||
published_at: z.string().nullish(),
|
||||
access: z.boolean(),
|
||||
comments: z.boolean(),
|
||||
email_subject: z.string().nullish(),
|
||||
});
|
||||
|
||||
export type Post = z.infer<typeof postsSchema>;
|
||||
|
||||
export const postsIncludeSchema = z.object({
|
||||
authors: z.literal(true).optional(),
|
||||
tags: z.literal(true).optional(),
|
||||
});
|
||||
export type PostsIncludeSchema = z.infer<typeof postsIncludeSchema>;
|
|
@ -0,0 +1,34 @@
|
|||
import {
|
||||
ghostCodeInjectionSchema,
|
||||
ghostIdentitySchema,
|
||||
ghostMetadataSchema,
|
||||
ghostSocialMediaSchema,
|
||||
ghostVisibilitySchema,
|
||||
} from "@ts-ghost/core-api";
|
||||
import { z } from "astro/zod";
|
||||
|
||||
export const tagsSchema = z.object({
|
||||
...ghostIdentitySchema.shape,
|
||||
...ghostMetadataSchema.shape,
|
||||
...ghostCodeInjectionSchema.shape,
|
||||
...ghostSocialMediaSchema.shape,
|
||||
name: z.string(),
|
||||
description: z.string().nullable(),
|
||||
feature_image: z.string().nullable(),
|
||||
visibility: ghostVisibilitySchema,
|
||||
canonical_url: z.string().nullable(),
|
||||
accent_color: z.string().nullable(),
|
||||
url: z.string(),
|
||||
count: z
|
||||
.object({
|
||||
posts: z.number(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
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>;
|
|
@ -0,0 +1,124 @@
|
|||
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 { invariant } from "./invariant";
|
||||
|
||||
const { CONTENT_API_KEY, CONTENT_API_URL } = loadEnv(
|
||||
"all",
|
||||
process.cwd(),
|
||||
"CONTENT_",
|
||||
);
|
||||
|
||||
invariant(CONTENT_API_KEY)
|
||||
invariant(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);
|
||||
|
||||
export const getAllAuthors = async () => {
|
||||
const results = await api.authors
|
||||
.browse()
|
||||
.include({ "count.posts": true })
|
||||
.fetch();
|
||||
if (!results.success) {
|
||||
throw new Error(results.errors.map((e) => e.message).join(", "));
|
||||
}
|
||||
return {
|
||||
authors: results.data,
|
||||
meta: results.meta,
|
||||
};
|
||||
};
|
||||
|
||||
export const getPosts = async () => {
|
||||
const results = await api.posts
|
||||
.browse()
|
||||
.include({
|
||||
authors: true,
|
||||
tags: true,
|
||||
})
|
||||
.fetch();
|
||||
if (!results.success) {
|
||||
throw new Error(results.errors.map((e) => e.message).join(", "));
|
||||
}
|
||||
return {
|
||||
posts: results.data,
|
||||
meta: results.meta,
|
||||
};
|
||||
};
|
||||
|
||||
export const getAllPosts = async () => {
|
||||
const posts: Post[] = [];
|
||||
let cursor = await api.posts
|
||||
.browse()
|
||||
.include({
|
||||
authors: true,
|
||||
tags: true,
|
||||
})
|
||||
.paginate();
|
||||
if (cursor.current.success) posts.push(...cursor.current.data);
|
||||
while (cursor.next) {
|
||||
cursor = await cursor.next.paginate();
|
||||
if (cursor.current.success) posts.push(...cursor.current.data);
|
||||
}
|
||||
return posts;
|
||||
};
|
||||
|
||||
export const getAllPages = async () => {
|
||||
const pages: Page[] = [];
|
||||
let cursor = await api.pages
|
||||
.browse()
|
||||
.include({
|
||||
authors: true,
|
||||
tags: true,
|
||||
})
|
||||
.paginate();
|
||||
if (cursor.current.success) pages.push(...cursor.current.data);
|
||||
while (cursor.next) {
|
||||
cursor = await cursor.next.paginate();
|
||||
if (cursor.current.success) pages.push(...cursor.current.data);
|
||||
}
|
||||
return pages;
|
||||
};
|
||||
|
||||
export const getSettings = async () => {
|
||||
const res = await api.settings.fetch();
|
||||
if (res.success) {
|
||||
return res.data;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const getAllTags = async () => {
|
||||
const results = await api.tags
|
||||
.browse()
|
||||
.include({ "count.posts": true })
|
||||
.fetch();
|
||||
if (!results.success) {
|
||||
throw new Error(results.errors.map((e) => e.message).join(", "));
|
||||
}
|
||||
return {
|
||||
tags: results.data,
|
||||
meta: results.meta,
|
||||
};
|
||||
};
|
||||
|
||||
export const getFeaturedPosts = async () => {
|
||||
const results = await api.posts
|
||||
.browse({ filter: "featured:true" })
|
||||
.include({
|
||||
authors: true,
|
||||
tags: true,
|
||||
})
|
||||
.fetch();
|
||||
if (!results.success) {
|
||||
throw new Error(results.errors.map((e) => e.message).join(", "));
|
||||
}
|
||||
return {
|
||||
posts: results.data,
|
||||
meta: results.meta,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,84 @@
|
|||
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();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,116 @@
|
|||
import {
|
||||
APIComposer,
|
||||
BasicFetcher,
|
||||
HTTPClient,
|
||||
contentAPICredentialsSchema,
|
||||
slugOrIdSchema,
|
||||
} from "@ts-ghost/core-api";
|
||||
|
||||
import {
|
||||
authorsIncludeSchema,
|
||||
authorsSchema,
|
||||
pagesIncludeSchema,
|
||||
pagesSchema,
|
||||
postsIncludeSchema,
|
||||
postsSchema,
|
||||
settingsSchema,
|
||||
tagsIncludeSchema,
|
||||
tagsSchema,
|
||||
tiersIncludeSchema,
|
||||
tiersSchema,
|
||||
} from "./schemas";
|
||||
|
||||
export type { ContentAPICredentials, APIVersions } from "@ts-ghost/core-api";
|
||||
|
||||
export enum BrowseEndpointType {
|
||||
authors = "authors",
|
||||
tiers = "tiers",
|
||||
posts = "posts",
|
||||
pages = "pages",
|
||||
tags = "tags",
|
||||
settings = "settings",
|
||||
}
|
||||
|
||||
export default class TS_API<Version extends `v5.${string}` = any> {
|
||||
private httpClient: HTTPClient;
|
||||
|
||||
constructor(
|
||||
protected readonly url: string,
|
||||
protected readonly key: string,
|
||||
protected readonly version: Version,
|
||||
) {
|
||||
const apiCredentials = contentAPICredentialsSchema.parse({
|
||||
key,
|
||||
version,
|
||||
url,
|
||||
});
|
||||
this.httpClient = new HTTPClient({
|
||||
...apiCredentials,
|
||||
endpoint: "content",
|
||||
});
|
||||
}
|
||||
|
||||
get authors() {
|
||||
return new APIComposer(
|
||||
"authors",
|
||||
{
|
||||
schema: authorsSchema,
|
||||
identitySchema: slugOrIdSchema,
|
||||
include: authorsIncludeSchema,
|
||||
},
|
||||
this.httpClient,
|
||||
).access(["read", "browse"]);
|
||||
}
|
||||
get tiers() {
|
||||
return new APIComposer(
|
||||
"tiers",
|
||||
{
|
||||
schema: tiersSchema,
|
||||
identitySchema: slugOrIdSchema,
|
||||
include: tiersIncludeSchema,
|
||||
},
|
||||
this.httpClient,
|
||||
).access(["browse", "read"]);
|
||||
}
|
||||
get posts() {
|
||||
return new APIComposer(
|
||||
"posts",
|
||||
{
|
||||
schema: postsSchema,
|
||||
identitySchema: slugOrIdSchema,
|
||||
include: postsIncludeSchema,
|
||||
},
|
||||
this.httpClient,
|
||||
).access(["browse", "read"]);
|
||||
}
|
||||
get pages() {
|
||||
return new APIComposer(
|
||||
"pages",
|
||||
{
|
||||
schema: pagesSchema,
|
||||
identitySchema: slugOrIdSchema,
|
||||
include: pagesIncludeSchema,
|
||||
},
|
||||
this.httpClient,
|
||||
).access(["browse", "read"]);
|
||||
}
|
||||
get tags() {
|
||||
return new APIComposer(
|
||||
"tags",
|
||||
{
|
||||
schema: tagsSchema,
|
||||
identitySchema: slugOrIdSchema,
|
||||
include: tagsIncludeSchema,
|
||||
},
|
||||
this.httpClient,
|
||||
).access(["browse", "read"]);
|
||||
}
|
||||
|
||||
get settings() {
|
||||
return new BasicFetcher(
|
||||
"settings",
|
||||
{ output: settingsSchema },
|
||||
this.httpClient,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
export { default as TS_API } from "./content-api";
|
||||
export * from "./schemas";
|
||||
|
||||
export type {
|
||||
InferFetcherDataShape,
|
||||
InferResponseDataShape,
|
||||
BrowseParams,
|
||||
} from "@ts-ghost/core-api";
|
|
@ -0,0 +1,164 @@
|
|||
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");
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
import {
|
||||
ghostIdentitySchema,
|
||||
ghostMetaSchema,
|
||||
ghostMetadataSchema,
|
||||
} from "@ts-ghost/core-api";
|
||||
import { z } from "zod";
|
||||
|
||||
export const authorsSchema = z.object({
|
||||
...ghostIdentitySchema.shape,
|
||||
...ghostMetadataSchema.shape,
|
||||
name: z.string(),
|
||||
profile_image: z.string().nullable(),
|
||||
cover_image: z.string().nullable(),
|
||||
bio: z.string().nullable(),
|
||||
website: z.string().nullable(),
|
||||
location: z.string().nullable(),
|
||||
facebook: z.string().nullable(),
|
||||
twitter: z.string().nullable(),
|
||||
count: z
|
||||
.object({
|
||||
posts: z.number(),
|
||||
})
|
||||
.optional(),
|
||||
url: z.string(),
|
||||
});
|
||||
|
||||
export type Author = z.infer<typeof authorsSchema>;
|
||||
|
||||
export const ghostFetchAuthorsSchema = z.object({
|
||||
meta: ghostMetaSchema,
|
||||
authors: z.array(authorsSchema),
|
||||
});
|
||||
|
||||
export const authorsIncludeSchema = z.object({
|
||||
"count.posts": z.literal(true).optional(),
|
||||
});
|
||||
export type AuthorsIncludeSchema = z.infer<typeof authorsIncludeSchema>;
|
|
@ -0,0 +1 @@
|
|||
export * from "./authors";
|
|
@ -0,0 +1 @@
|
|||
export * from "./socials";
|
|
@ -0,0 +1,32 @@
|
|||
// Transformed to TypeScript, original Code by Ghost Foundation, License:
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2013-2022 Ghost Foundation
|
||||
|
||||
// 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.
|
||||
|
||||
export const twitter = (username: string) => {
|
||||
// Creates the canonical twitter URL without the '@'
|
||||
return `https://twitter.com/${username.replace(/^@/, "")}`;
|
||||
};
|
||||
|
||||
export const facebook = (username: string) => {
|
||||
// Handles a starting slash, this shouldn't happen, but just in case
|
||||
return `https://www.facebook.com/${username.replace(/^\//, "")}`;
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
export * from "./authors";
|
||||
export * from "./helpers";
|
||||
export * from "./pages";
|
||||
export * from "./posts";
|
||||
export * from "./settings";
|
||||
export * from "./tags";
|
||||
export * from "./tiers";
|
|
@ -0,0 +1 @@
|
|||
export * from "./pages";
|
|
@ -0,0 +1,55 @@
|
|||
import {
|
||||
ghostCodeInjectionSchema,
|
||||
ghostIdentitySchema,
|
||||
ghostMetadataSchema,
|
||||
ghostSocialMediaSchema,
|
||||
ghostVisibilitySchema,
|
||||
} from "@ts-ghost/core-api";
|
||||
import { z } from "zod";
|
||||
|
||||
import { authorsSchema } from "../authors";
|
||||
import { tagsSchema } from "../tags";
|
||||
|
||||
const postsAuthorSchema = authorsSchema.extend({
|
||||
url: z.string().nullish(),
|
||||
});
|
||||
|
||||
export const pagesSchema = z.object({
|
||||
...ghostIdentitySchema.shape,
|
||||
...ghostMetadataSchema.shape,
|
||||
title: z.string(),
|
||||
html: z.string().catch(""),
|
||||
plaintext: z.string().nullish(),
|
||||
comment_id: z.string().nullable(),
|
||||
feature_image: z.string().nullable(),
|
||||
feature_image_alt: z.string().nullable(),
|
||||
feature_image_caption: z.string().nullable(),
|
||||
featured: z.boolean(),
|
||||
custom_excerpt: z.string().nullable(),
|
||||
...ghostCodeInjectionSchema.shape,
|
||||
...ghostSocialMediaSchema.shape,
|
||||
visibility: ghostVisibilitySchema,
|
||||
custom_template: z.string().nullable(),
|
||||
canonical_url: z.string().nullable(),
|
||||
authors: z.array(postsAuthorSchema).optional(),
|
||||
tags: z.array(tagsSchema).optional(),
|
||||
primary_author: postsAuthorSchema.nullish(),
|
||||
primary_tag: tagsSchema.nullish(),
|
||||
url: z.string(),
|
||||
excerpt: z.string().catch(""),
|
||||
reading_time: z.number().optional().default(0),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string(),
|
||||
published_at: z.string(),
|
||||
access: z.boolean(),
|
||||
comments: z.boolean(),
|
||||
email_subject: z.string().nullish(),
|
||||
});
|
||||
|
||||
export type Page = z.infer<typeof pagesSchema>;
|
||||
|
||||
export const pagesIncludeSchema = z.object({
|
||||
authors: z.literal(true).optional(),
|
||||
tags: z.literal(true).optional(),
|
||||
});
|
||||
export type PagesIncludeSchema = z.infer<typeof pagesIncludeSchema>;
|
|
@ -0,0 +1 @@
|
|||
export * from "./posts";
|
|
@ -0,0 +1,69 @@
|
|||
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();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
import {
|
||||
ghostCodeInjectionSchema,
|
||||
ghostIdentitySchema,
|
||||
ghostMetadataSchema,
|
||||
ghostSocialMediaSchema,
|
||||
ghostVisibilitySchema,
|
||||
} from "@ts-ghost/core-api";
|
||||
import { z } from "zod";
|
||||
|
||||
import { authorsSchema } from "../authors";
|
||||
import { tagsSchema } from "../tags";
|
||||
|
||||
const postsAuthorSchema = authorsSchema.extend({
|
||||
url: z.string().nullish(),
|
||||
});
|
||||
export const postsSchema = z.object({
|
||||
...ghostIdentitySchema.shape,
|
||||
...ghostMetadataSchema.shape,
|
||||
title: z.string(),
|
||||
html: z.string().catch(""),
|
||||
plaintext: z.string().nullish(),
|
||||
comment_id: z.string().nullable(),
|
||||
feature_image: z.string().nullable(),
|
||||
feature_image_alt: z.string().nullable(),
|
||||
feature_image_caption: z.string().nullable(),
|
||||
featured: z.boolean(),
|
||||
custom_excerpt: z.string().nullable(),
|
||||
...ghostCodeInjectionSchema.shape,
|
||||
...ghostSocialMediaSchema.shape,
|
||||
visibility: ghostVisibilitySchema,
|
||||
custom_template: z.string().nullable(),
|
||||
canonical_url: z.string().nullable(),
|
||||
authors: z.array(postsAuthorSchema).optional(),
|
||||
tags: z.array(tagsSchema).optional(),
|
||||
primary_author: postsAuthorSchema.nullish(),
|
||||
primary_tag: tagsSchema.nullish(),
|
||||
url: z.string(),
|
||||
excerpt: z.string().catch(""),
|
||||
reading_time: z.number().optional().default(0),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string().nullish(),
|
||||
published_at: z.string().nullish(),
|
||||
access: z.boolean(),
|
||||
comments: z.boolean(),
|
||||
email_subject: z.string().nullish(),
|
||||
});
|
||||
|
||||
export type Post = z.infer<typeof postsSchema>;
|
||||
|
||||
export const postsIncludeSchema = z.object({
|
||||
authors: z.literal(true).optional(),
|
||||
tags: z.literal(true).optional(),
|
||||
});
|
||||
export type PostsIncludeSchema = z.infer<typeof postsIncludeSchema>;
|
|
@ -0,0 +1 @@
|
|||
export * from "./settings";
|
|
@ -0,0 +1,40 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export const settingsSchema = z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
logo: z.string().nullable(),
|
||||
icon: z.string().nullable(),
|
||||
accent_color: z.string().nullable(),
|
||||
cover_image: z.string().nullable(),
|
||||
facebook: z.string().nullable(),
|
||||
twitter: z.string().nullable(),
|
||||
lang: z.string(),
|
||||
timezone: z.string(),
|
||||
codeinjection_head: z.string().nullable(),
|
||||
codeinjection_foot: z.string().nullable(),
|
||||
navigation: z.array(
|
||||
z.object({
|
||||
label: z.string(),
|
||||
url: z.string(),
|
||||
}),
|
||||
),
|
||||
secondary_navigation: z.array(
|
||||
z.object({
|
||||
label: z.string(),
|
||||
url: z.string(),
|
||||
}),
|
||||
),
|
||||
meta_title: z.string().nullable(),
|
||||
meta_description: z.string().nullable(),
|
||||
og_image: z.string().nullable(),
|
||||
og_title: z.string().nullable(),
|
||||
og_description: z.string().nullable(),
|
||||
twitter_image: z.string().nullable(),
|
||||
twitter_title: z.string().nullable(),
|
||||
twitter_description: z.string().nullable(),
|
||||
members_support_address: z.string(),
|
||||
url: z.string(),
|
||||
});
|
||||
|
||||
export type Settings = z.infer<typeof settingsSchema>;
|
|
@ -0,0 +1 @@
|
|||
export * from "./tags";
|
|
@ -0,0 +1,34 @@
|
|||
import {
|
||||
ghostCodeInjectionSchema,
|
||||
ghostIdentitySchema,
|
||||
ghostMetadataSchema,
|
||||
ghostSocialMediaSchema,
|
||||
ghostVisibilitySchema,
|
||||
} from "@ts-ghost/core-api";
|
||||
import { z } from "zod";
|
||||
|
||||
export const tagsSchema = z.object({
|
||||
...ghostIdentitySchema.shape,
|
||||
...ghostMetadataSchema.shape,
|
||||
...ghostCodeInjectionSchema.shape,
|
||||
...ghostSocialMediaSchema.shape,
|
||||
name: z.string(),
|
||||
description: z.string().nullable(),
|
||||
feature_image: z.string().nullable(),
|
||||
visibility: ghostVisibilitySchema,
|
||||
canonical_url: z.string().nullable(),
|
||||
accent_color: z.string().nullable(),
|
||||
url: z.string(),
|
||||
count: z
|
||||
.object({
|
||||
posts: z.number(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
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>;
|
|
@ -0,0 +1 @@
|
|||
export * from "./tiers";
|
|
@ -0,0 +1,40 @@
|
|||
import { ghostIdentitySchema, ghostVisibilitySchema } from "@ts-ghost/core-api";
|
||||
import { z } from "zod";
|
||||
|
||||
export const tiersSchema = z.object({
|
||||
...ghostIdentitySchema.shape,
|
||||
name: z.string(),
|
||||
description: z.string().nullable(),
|
||||
active: z.boolean(),
|
||||
type: z.union([z.literal("free"), z.literal("paid")]),
|
||||
welcome_page_url: z.string().nullable(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string().nullable(),
|
||||
stripe_prices: z
|
||||
.array(z.number())
|
||||
.optional()
|
||||
.transform((v) => (v?.length ? v : [])),
|
||||
monthly_price: z
|
||||
.number()
|
||||
.nullable()
|
||||
.optional()
|
||||
.transform((v) => (v ? v : null)),
|
||||
yearly_price: z
|
||||
.number()
|
||||
.nullable()
|
||||
.optional()
|
||||
.transform((v) => (v ? v : null)),
|
||||
benefits: z.array(z.string()),
|
||||
visibility: ghostVisibilitySchema,
|
||||
currency: z.string().nullish(),
|
||||
trial_days: z.number().default(0),
|
||||
});
|
||||
|
||||
export type Tier = z.infer<typeof tiersSchema>;
|
||||
|
||||
export const tiersIncludeSchema = z.object({
|
||||
monthly_price: z.literal(true).optional(),
|
||||
yearly_price: z.literal(true).optional(),
|
||||
benefits: z.literal(true).optional(),
|
||||
});
|
||||
export type TiersIncludeSchema = z.infer<typeof tiersIncludeSchema>;
|
|
@ -0,0 +1,3 @@
|
|||
export * from "./api-functions";
|
||||
export * from "./content-api/schemas";
|
||||
export * from "./invariant";
|
|
@ -0,0 +1,32 @@
|
|||
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"));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
/** MIT License
|
||||
|
||||
Copyright (c) 2019 Alexander Reardon
|
||||
|
||||
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.
|
||||
*/
|
||||
const tinyinvariant = "merged";
|
||||
const isProduction: boolean = process.env.NODE_ENV === "production";
|
||||
const prefix: string = "Invariant failed";
|
||||
|
||||
/** Throw an error if the condition is false
|
||||
* @example
|
||||
* import { invariant } from '@matthiesenxyz/astro-ghostcms/api';
|
||||
* invariant(var, "var is false but its not supposed to be!")
|
||||
*/
|
||||
export function invariant(
|
||||
condition: any,
|
||||
message?: string | (() => string),
|
||||
): asserts condition {
|
||||
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;
|
||||
throw new Error(value);
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"allowUnreachableCode": false,
|
||||
"allowUnusedLabels": false,
|
||||
"esModuleInterop": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"incremental": true,
|
||||
"jsx": "react-jsx",
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"verbatimModuleSyntax": true
|
||||
}
|
||||
}
|
634
pnpm-lock.yaml
634
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue