bsmnt 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application/add-integration/index.d.ts +24 -6
- package/dist/application/add-integration/index.d.ts.map +1 -1
- package/dist/application/add-integration/index.js +130 -82
- package/dist/application/add-integration/index.js.map +1 -1
- package/dist/application/create-project/map-selection-to-registry.js +1 -1
- package/dist/application/create-project/map-selection-to-registry.js.map +1 -1
- package/dist/application/shared/project-detection.d.ts +2 -2
- package/dist/application/shared/project-detection.d.ts.map +1 -1
- package/dist/application/shared/project-detection.js +2 -2
- package/dist/application/shared/project-detection.js.map +1 -1
- package/dist/core/create/internal-ids.d.ts +4 -1
- package/dist/core/create/internal-ids.d.ts.map +1 -1
- package/dist/core/create/internal-ids.js +6 -1
- package/dist/core/create/internal-ids.js.map +1 -1
- package/dist/domain/cms.d.ts +0 -7
- package/dist/domain/cms.d.ts.map +1 -1
- package/dist/domain/cms.js.map +1 -1
- package/dist/domain/forms.d.ts +17 -0
- package/dist/domain/forms.d.ts.map +1 -0
- package/dist/domain/forms.js +13 -0
- package/dist/domain/forms.js.map +1 -0
- package/dist/domain/integration.d.ts +30 -0
- package/dist/domain/integration.d.ts.map +1 -0
- package/dist/domain/integration.js +12 -0
- package/dist/domain/integration.js.map +1 -0
- package/dist/domain/skills.d.ts +32 -0
- package/dist/domain/skills.d.ts.map +1 -1
- package/dist/domain/skills.js +39 -0
- package/dist/domain/skills.js.map +1 -1
- package/dist/index.js +95 -37
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/agents/register-sanity-mcp.js +9 -6
- package/dist/infrastructure/agents/register-sanity-mcp.js.map +1 -1
- package/dist/infrastructure/create/apply-package-json.d.ts.map +1 -1
- package/dist/infrastructure/create/apply-package-json.js +19 -0
- package/dist/infrastructure/create/apply-package-json.js.map +1 -1
- package/dist/infrastructure/create/install-dependencies.d.ts.map +1 -1
- package/dist/infrastructure/create/install-dependencies.js +4 -0
- package/dist/infrastructure/create/install-dependencies.js.map +1 -1
- package/dist/infrastructure/create/merge-registry.d.ts.map +1 -1
- package/dist/infrastructure/create/merge-registry.js +8 -0
- package/dist/infrastructure/create/merge-registry.js.map +1 -1
- package/dist/infrastructure/create/setup-agent.d.ts.map +1 -1
- package/dist/infrastructure/create/setup-agent.js +29 -6
- package/dist/infrastructure/create/setup-agent.js.map +1 -1
- package/dist/infrastructure/create/setup-sanity.d.ts.map +1 -1
- package/dist/infrastructure/create/setup-sanity.js +3 -0
- package/dist/infrastructure/create/setup-sanity.js.map +1 -1
- package/dist/infrastructure/integrations/inject-integration.d.ts +3 -3
- package/dist/infrastructure/integrations/inject-integration.d.ts.map +1 -1
- package/dist/infrastructure/integrations/inject-integration.js +81 -37
- package/dist/infrastructure/integrations/inject-integration.js.map +1 -1
- package/dist/modules/features/cms/sanity/config.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/config.js +11 -5
- package/dist/modules/features/cms/sanity/config.js.map +1 -1
- package/dist/modules/features/cms/sanity/index.js +8 -2
- package/dist/modules/features/cms/sanity/index.js.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/ci-workflow.d.ts +13 -0
- package/dist/modules/features/cms/sanity/mergers/ci-workflow.d.ts.map +1 -0
- package/dist/modules/features/cms/sanity/mergers/ci-workflow.js +38 -0
- package/dist/modules/features/cms/sanity/mergers/ci-workflow.js.map +1 -0
- package/dist/modules/features/cms/sanity/mergers/knip.d.ts +12 -0
- package/dist/modules/features/cms/sanity/mergers/knip.d.ts.map +1 -0
- package/dist/modules/features/cms/sanity/mergers/knip.js +38 -0
- package/dist/modules/features/cms/sanity/mergers/knip.js.map +1 -0
- package/dist/modules/features/cms/sanity/mergers/layout.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/layout.js +7 -3
- package/dist/modules/features/cms/sanity/mergers/layout.js.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/sitemap.d.ts +3 -3
- package/dist/modules/features/cms/sanity/mergers/sitemap.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/sitemap.js +48 -62
- package/dist/modules/features/cms/sanity/mergers/sitemap.js.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder/config.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder/config.js +8 -5
- package/dist/modules/features/cms/sanity-pagebuilder/config.js.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder/index.js +2 -2
- package/dist/modules/features/cms/sanity-pagebuilder/index.js.map +1 -1
- package/dist/modules/features/forms/hubspot/config.d.ts +31 -0
- package/dist/modules/features/forms/hubspot/config.d.ts.map +1 -0
- package/dist/modules/features/forms/hubspot/config.js +25 -0
- package/dist/modules/features/forms/hubspot/config.js.map +1 -0
- package/dist/paths.d.ts +8 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +8 -0
- package/dist/paths.js.map +1 -1
- package/package.json +17 -9
- package/src/assets/check-node-version.mjs +22 -0
- package/src/modules/features/cms/sanity/files/app/blog/page.tsx +54 -0
- package/src/modules/features/cms/sanity/files/app/feed.xml/route.ts +36 -0
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/components/disable-draft-mode.tsx +3 -3
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/schemas/article.ts +7 -7
- package/src/modules/features/cms/sanity/files/lib/scripts/setup-ci-secrets.ts +117 -0
- package/src/modules/features/cms/sanity/files/lib/utils/rss.ts +96 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/.knip.json +8 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/app/api/[[...slug]]/route.ts +100 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/app/api/draft-mode/disable/route.ts +7 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/app/api/draft-mode/enable/route.ts +20 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/app/api/revalidate/route.ts +126 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/app/feed.xml/route.ts +40 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/app/sitemap.md/route.ts +124 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/app/studio/[[...tool]]/page.tsx +8 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/draft-mode-toggle.tsx +27 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +87 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/visual-editing.tsx +28 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/components/ui/sanity-image/index.tsx +41 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/check-integration.ts +5 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/client.ts +27 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-input.tsx +39 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-category-input.tsx +50 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-field.tsx +18 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-input.tsx +14 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/confirm-publish-action.ts +40 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/env.ts +34 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/layout.ts +48 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/redirects.ts +17 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +62 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/live/index.tsx +88 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +130 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-category.ts +28 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-tree-pane.tsx +420 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/pinned-pages.ts +54 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/presentation.ts +119 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +385 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/sanity.cli.ts +29 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/sanity.config.ts +113 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +4 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/content/author.ts +70 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +96 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/company-data.ts +62 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/navbar.ts +75 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/redirect.ts +120 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/link.ts +125 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/metadata.ts +68 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page-builder.ts +39 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +242 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/singletons.ts +39 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +330 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/image.ts +8 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/link.ts +140 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/page-builder-markdown.ts +81 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/scripts/sanity-typegen.ts +45 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/check-redirect.ts +30 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/json-ld.tsx +212 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/metadata.ts +167 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/rss.ts +96 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/sitemap.ts +40 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/proxy.ts +81 -0
- package/src/templates/next-default/.github/workflows/ci.yml +62 -0
- package/src/templates/next-default/README.md +22 -0
- package/src/templates/next-default/knip.json +14 -0
- package/src/templates/next-default/lefthook.yml +5 -0
- package/src/templates/next-default/package.json +8 -4
- package/src/templates/next-experiments/.github/workflows/ci.yml +62 -0
- package/src/templates/next-experiments/README.md +22 -0
- package/src/templates/next-experiments/knip.json +14 -0
- package/src/templates/next-experiments/lefthook.yml +30 -0
- package/src/templates/next-experiments/package.json +10 -2
- package/src/templates/next-pagebuilder/.github/workflows/ci.yml +70 -0
- package/src/templates/next-pagebuilder/README.md +34 -0
- package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +6 -1
- package/src/templates/next-pagebuilder/app/(content)/layout.tsx +3 -12
- package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +11 -10
- package/src/templates/next-pagebuilder/components/layout/header/index.tsx +5 -3
- package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +9 -10
- package/src/templates/next-pagebuilder/components/layout/wrapper/index.tsx +3 -11
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/index.tsx +1 -1
- package/src/templates/next-pagebuilder/knip.json +27 -0
- package/src/templates/next-pagebuilder/lefthook.yml +5 -0
- package/src/templates/next-pagebuilder/lib/scripts/setup-ci-secrets.ts +117 -0
- package/src/templates/next-pagebuilder/package.json +9 -3
- package/src/templates/next-webgl/.github/workflows/ci.yml +62 -0
- package/src/templates/next-webgl/README.md +22 -0
- package/src/templates/next-webgl/knip.json +13 -0
- package/src/templates/next-webgl/lefthook.yml +5 -0
- package/src/templates/next-webgl/package.json +8 -4
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { sanityFetch } from "@/lib/integrations/sanity/live";
|
|
3
|
+
import { ALL_ARTICLES_QUERY } from "@/lib/integrations/sanity/queries";
|
|
4
|
+
|
|
5
|
+
export const metadata = {
|
|
6
|
+
title: "Blog",
|
|
7
|
+
alternates: {
|
|
8
|
+
types: { "application/rss+xml": "/feed.xml" },
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default async function BlogIndexPage() {
|
|
13
|
+
const { data: articles } = await sanityFetch({ query: ALL_ARTICLES_QUERY });
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<main className="container mx-auto px-4 py-8 max-w-4xl">
|
|
17
|
+
<h1 className="text-4xl font-bold mb-8">Blog</h1>
|
|
18
|
+
|
|
19
|
+
{articles && articles.length > 0 ? (
|
|
20
|
+
<ul className="flex flex-col gap-8">
|
|
21
|
+
{articles.map((article) => {
|
|
22
|
+
const slug = article.slug?.current;
|
|
23
|
+
if (!slug) return null;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<li key={article._id}>
|
|
27
|
+
<article>
|
|
28
|
+
<h2 className="text-2xl font-semibold mb-2">
|
|
29
|
+
<Link href={`/blog/${slug}`} className="hover:underline">
|
|
30
|
+
{article.title}
|
|
31
|
+
</Link>
|
|
32
|
+
</h2>
|
|
33
|
+
{article.excerpt && (
|
|
34
|
+
<p className="text-gray-600 mb-2">{article.excerpt}</p>
|
|
35
|
+
)}
|
|
36
|
+
<div className="flex gap-4 text-sm text-gray-500">
|
|
37
|
+
{article.author && <span>By {article.author}</span>}
|
|
38
|
+
{article.publishedAt && (
|
|
39
|
+
<time dateTime={article.publishedAt}>
|
|
40
|
+
{new Date(article.publishedAt).toLocaleDateString()}
|
|
41
|
+
</time>
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
</article>
|
|
45
|
+
</li>
|
|
46
|
+
);
|
|
47
|
+
})}
|
|
48
|
+
</ul>
|
|
49
|
+
) : (
|
|
50
|
+
<p className="text-gray-600">No articles published yet.</p>
|
|
51
|
+
)}
|
|
52
|
+
</main>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { client } from "@/lib/integrations/sanity/client";
|
|
2
|
+
import { ALL_ARTICLES_QUERY } from "@/lib/integrations/sanity/queries";
|
|
3
|
+
import { generateRssFeed, rssErrorResponse } from "@/lib/utils/rss";
|
|
4
|
+
import { getBaseUrl } from "@/lib/utils/url";
|
|
5
|
+
|
|
6
|
+
export const dynamic = "force-dynamic";
|
|
7
|
+
|
|
8
|
+
export async function GET() {
|
|
9
|
+
const baseUrl = getBaseUrl();
|
|
10
|
+
if (!client) return rssErrorResponse();
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const articles = await client.fetch(ALL_ARTICLES_QUERY);
|
|
14
|
+
|
|
15
|
+
const items = (articles ?? [])
|
|
16
|
+
.filter((article) => article.slug?.current && !article.metadata?.noIndex)
|
|
17
|
+
.map((article) => ({
|
|
18
|
+
title: article.title ?? "Untitled",
|
|
19
|
+
link: `${baseUrl}/blog/${article.slug?.current}`,
|
|
20
|
+
description: article.excerpt ?? undefined,
|
|
21
|
+
pubDate: article.publishedAt ?? undefined,
|
|
22
|
+
authors: article.author ? [article.author] : undefined,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
return generateRssFeed({
|
|
26
|
+
title: `${new URL(baseUrl).hostname} Blog`,
|
|
27
|
+
link: `${baseUrl}/blog`,
|
|
28
|
+
description: "Latest articles",
|
|
29
|
+
feedUrl: `${baseUrl}/feed.xml`,
|
|
30
|
+
items,
|
|
31
|
+
});
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error("Error generating RSS feed:", error);
|
|
34
|
+
return rssErrorResponse();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import Link from "next/link";
|
|
4
4
|
import { usePathname } from "next/navigation";
|
|
5
|
-
import {
|
|
5
|
+
import { useIsPresentationTool } from "next-sanity/hooks";
|
|
6
6
|
|
|
7
7
|
export function DisableDraftMode() {
|
|
8
|
-
const
|
|
8
|
+
const isPresentationTool = useIsPresentationTool();
|
|
9
9
|
const pathname = usePathname();
|
|
10
10
|
|
|
11
11
|
// Only show the disable draft mode button when outside of Presentation Tool
|
|
12
|
-
if (
|
|
12
|
+
if (isPresentationTool) {
|
|
13
13
|
return null;
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import { defineField, defineType } from "sanity";
|
|
2
2
|
|
|
3
3
|
const articleFields = [
|
|
4
|
-
defineField({
|
|
5
|
-
name: "title",
|
|
6
|
-
title: "Title",
|
|
7
|
-
type: "string",
|
|
8
|
-
description: "The title of the article",
|
|
9
|
-
validation: (Rule) => Rule.required(),
|
|
10
|
-
}),
|
|
11
4
|
defineField({
|
|
12
5
|
name: "excerpt",
|
|
13
6
|
title: "Excerpt",
|
|
@@ -66,6 +59,13 @@ export const article = defineType({
|
|
|
66
59
|
title: "Article",
|
|
67
60
|
type: "document",
|
|
68
61
|
fields: [
|
|
62
|
+
defineField({
|
|
63
|
+
name: "title",
|
|
64
|
+
title: "Title",
|
|
65
|
+
type: "string",
|
|
66
|
+
description: "The title of the article",
|
|
67
|
+
validation: (Rule) => Rule.required(),
|
|
68
|
+
}),
|
|
69
69
|
defineField({
|
|
70
70
|
name: "slug",
|
|
71
71
|
title: "Slug",
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Push CI environment to GitHub Actions for this repository.
|
|
3
|
+
*
|
|
4
|
+
* Reads values from `.env.local` and sets them on the current repo via the
|
|
5
|
+
* GitHub CLI (`gh`), matching the `build` step in `.github/workflows/ci.yml`:
|
|
6
|
+
* the public Sanity project ID and dataset become repository *variables*, and
|
|
7
|
+
* the read token becomes a *secret*.
|
|
8
|
+
*
|
|
9
|
+
* Run once the repo exists on GitHub: `bun run ci:secrets`
|
|
10
|
+
* Requires the GitHub CLI, authenticated (`gh auth login`).
|
|
11
|
+
*/
|
|
12
|
+
import { execFileSync } from "node:child_process";
|
|
13
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
|
|
16
|
+
const ENV_FILE = ".env.local";
|
|
17
|
+
|
|
18
|
+
/** Public config — stored as GitHub Actions repository variables. */
|
|
19
|
+
const VARIABLES = [
|
|
20
|
+
"NEXT_PUBLIC_SANITY_PROJECT_ID",
|
|
21
|
+
"NEXT_PUBLIC_SANITY_DATASET",
|
|
22
|
+
] as const;
|
|
23
|
+
|
|
24
|
+
/** Sensitive config — stored as GitHub Actions secrets. */
|
|
25
|
+
const SECRETS = ["SANITY_API_READ_TOKEN"] as const;
|
|
26
|
+
|
|
27
|
+
function parseEnvFile(file: string): Record<string, string> {
|
|
28
|
+
const env: Record<string, string> = {};
|
|
29
|
+
if (!existsSync(file)) return env;
|
|
30
|
+
|
|
31
|
+
for (const rawLine of readFileSync(file, "utf8").split("\n")) {
|
|
32
|
+
const line = rawLine.trim();
|
|
33
|
+
if (!line || line.startsWith("#")) continue;
|
|
34
|
+
|
|
35
|
+
const eq = line.indexOf("=");
|
|
36
|
+
if (eq === -1) continue;
|
|
37
|
+
|
|
38
|
+
const key = line.slice(0, eq).trim();
|
|
39
|
+
let value = line.slice(eq + 1).trim();
|
|
40
|
+
if (
|
|
41
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
42
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
43
|
+
) {
|
|
44
|
+
value = value.slice(1, -1);
|
|
45
|
+
}
|
|
46
|
+
env[key] = value;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return env;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function ensureGitHubRepo(): void {
|
|
53
|
+
try {
|
|
54
|
+
execFileSync("gh", ["--version"], { stdio: "pipe" });
|
|
55
|
+
} catch {
|
|
56
|
+
console.error(
|
|
57
|
+
"✖ GitHub CLI (gh) not found. Install it: https://cli.github.com",
|
|
58
|
+
);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
execFileSync("gh", ["repo", "view", "--json", "nameWithOwner"], {
|
|
64
|
+
stdio: "pipe",
|
|
65
|
+
});
|
|
66
|
+
} catch {
|
|
67
|
+
console.error("✖ No GitHub repository detected for this directory.");
|
|
68
|
+
console.error(
|
|
69
|
+
" Create and push the repo first (e.g. `gh repo create`), then re-run.",
|
|
70
|
+
);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
ensureGitHubRepo();
|
|
76
|
+
|
|
77
|
+
const env = parseEnvFile(path.resolve(ENV_FILE));
|
|
78
|
+
let pushed = 0;
|
|
79
|
+
let skipped = 0;
|
|
80
|
+
|
|
81
|
+
for (const name of VARIABLES) {
|
|
82
|
+
const value = env[name] ?? process.env[name];
|
|
83
|
+
if (!value) {
|
|
84
|
+
console.info(`- ${name}: not found in ${ENV_FILE}, skipping`);
|
|
85
|
+
skipped++;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
// Public value — safe to pass on the command line.
|
|
89
|
+
execFileSync("gh", ["variable", "set", name, "--body", value], {
|
|
90
|
+
stdio: "pipe",
|
|
91
|
+
});
|
|
92
|
+
console.info(`✓ variable ${name}`);
|
|
93
|
+
pushed++;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (const name of SECRETS) {
|
|
97
|
+
const value = env[name] ?? process.env[name];
|
|
98
|
+
if (!value) {
|
|
99
|
+
console.info(`- ${name}: not found in ${ENV_FILE}, skipping`);
|
|
100
|
+
skipped++;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
// Sensitive — pipe via stdin so the token never appears in the process args.
|
|
104
|
+
execFileSync("gh", ["secret", "set", name], { input: value, stdio: "pipe" });
|
|
105
|
+
console.info(`✓ secret ${name}`);
|
|
106
|
+
pushed++;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (pushed === 0) {
|
|
110
|
+
console.info(
|
|
111
|
+
`\nNothing to sync. Add your Sanity values to ${ENV_FILE} (or run Sanity setup) first.`,
|
|
112
|
+
);
|
|
113
|
+
} else {
|
|
114
|
+
console.info(
|
|
115
|
+
`\nDone — ${String(pushed)} value(s) pushed to GitHub Actions${skipped ? `, ${String(skipped)} skipped` : ""}.`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export interface RssFeedItem {
|
|
2
|
+
title: string;
|
|
3
|
+
link: string;
|
|
4
|
+
description?: string | undefined;
|
|
5
|
+
pubDate?: string | undefined;
|
|
6
|
+
authors?: string[] | undefined;
|
|
7
|
+
category?: string | undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface RssFeedConfig {
|
|
11
|
+
title: string;
|
|
12
|
+
link: string;
|
|
13
|
+
description: string;
|
|
14
|
+
feedUrl: string;
|
|
15
|
+
language?: string;
|
|
16
|
+
items: RssFeedItem[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function escapeXml(str: string): string {
|
|
20
|
+
return str
|
|
21
|
+
.replace(/&/g, "&")
|
|
22
|
+
.replace(/</g, "<")
|
|
23
|
+
.replace(/>/g, ">")
|
|
24
|
+
.replace(/"/g, """)
|
|
25
|
+
.replace(/'/g, "'");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function generateRssFeed(config: RssFeedConfig): Response {
|
|
29
|
+
const {
|
|
30
|
+
title,
|
|
31
|
+
link,
|
|
32
|
+
description,
|
|
33
|
+
feedUrl,
|
|
34
|
+
language = "en-us",
|
|
35
|
+
items,
|
|
36
|
+
} = config;
|
|
37
|
+
|
|
38
|
+
const itemsXml = items
|
|
39
|
+
.map((item) => {
|
|
40
|
+
const lines = [
|
|
41
|
+
` <title>${escapeXml(item.title)}</title>`,
|
|
42
|
+
` <link>${escapeXml(item.link)}</link>`,
|
|
43
|
+
` <guid isPermaLink="true">${escapeXml(item.link)}</guid>`,
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
if (item.description) {
|
|
47
|
+
lines.push(
|
|
48
|
+
` <description>${escapeXml(item.description)}</description>`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const pubDate = item.pubDate ? new Date(item.pubDate) : null;
|
|
53
|
+
if (pubDate && !Number.isNaN(pubDate.getTime())) {
|
|
54
|
+
lines.push(` <pubDate>${pubDate.toUTCString()}</pubDate>`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const author of item.authors ?? []) {
|
|
58
|
+
lines.push(` <dc:creator>${escapeXml(author)}</dc:creator>`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (item.category) {
|
|
62
|
+
lines.push(` <category>${escapeXml(item.category)}</category>`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return ` <item>\n${lines.join("\n")}\n </item>`;
|
|
66
|
+
})
|
|
67
|
+
.join("\n");
|
|
68
|
+
|
|
69
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
70
|
+
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">
|
|
71
|
+
<channel>
|
|
72
|
+
<title>${escapeXml(title)}</title>
|
|
73
|
+
<link>${escapeXml(link)}</link>
|
|
74
|
+
<description>${escapeXml(description)}</description>
|
|
75
|
+
<language>${language}</language>
|
|
76
|
+
<atom:link href="${escapeXml(feedUrl)}" rel="self" type="application/rss+xml" />
|
|
77
|
+
${itemsXml}
|
|
78
|
+
</channel>
|
|
79
|
+
</rss>`;
|
|
80
|
+
|
|
81
|
+
return new Response(xml, {
|
|
82
|
+
headers: {
|
|
83
|
+
"Content-Type": "application/rss+xml",
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function rssErrorResponse(): Response {
|
|
89
|
+
return new Response(
|
|
90
|
+
'<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>Error</title></channel></rss>',
|
|
91
|
+
{
|
|
92
|
+
status: 500,
|
|
93
|
+
headers: { "Content-Type": "application/xml" },
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { isSanityConfigured } from "@/lib/integrations/check-integration";
|
|
3
|
+
import { sanityFetch } from "@/lib/integrations/sanity/live";
|
|
4
|
+
import { PAGE_QUERY } from "@/lib/integrations/sanity/queries";
|
|
5
|
+
import type { PAGE_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types";
|
|
6
|
+
import { renderPageBuilderMarkdown } from "@/lib/integrations/sanity/utils/page-builder-markdown";
|
|
7
|
+
import { getBaseUrl } from "@/lib/utils/base-url";
|
|
8
|
+
|
|
9
|
+
type PageResult = NonNullable<PAGE_QUERY_RESULT>;
|
|
10
|
+
|
|
11
|
+
const getSlugPath = (slugParts?: string[]) => {
|
|
12
|
+
if (!slugParts?.length) return null;
|
|
13
|
+
|
|
14
|
+
const lastSegment = slugParts.at(-1);
|
|
15
|
+
|
|
16
|
+
if (!lastSegment?.endsWith(".md")) return null;
|
|
17
|
+
|
|
18
|
+
const pathSegments = [...slugParts];
|
|
19
|
+
pathSegments[pathSegments.length - 1] = lastSegment.slice(0, -3);
|
|
20
|
+
|
|
21
|
+
const slug = pathSegments.filter(Boolean).join("/");
|
|
22
|
+
if (!slug) return null;
|
|
23
|
+
|
|
24
|
+
return slug === "index" ? null : slug;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export async function GET(
|
|
28
|
+
_request: Request,
|
|
29
|
+
props: { params: Promise<{ slug?: string[] }> },
|
|
30
|
+
) {
|
|
31
|
+
const { slug: rawSlugParts } = await props.params;
|
|
32
|
+
const slug = getSlugPath(rawSlugParts);
|
|
33
|
+
|
|
34
|
+
if (
|
|
35
|
+
rawSlugParts?.length &&
|
|
36
|
+
slug === null &&
|
|
37
|
+
rawSlugParts.join("/") !== "index.md"
|
|
38
|
+
) {
|
|
39
|
+
return new NextResponse(null, { status: 404 });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!rawSlugParts?.length) {
|
|
43
|
+
return new NextResponse(null, { status: 404 });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!isSanityConfigured()) {
|
|
47
|
+
return new NextResponse("# 404 Not Found\n\nCMS not configured.", {
|
|
48
|
+
status: 404,
|
|
49
|
+
headers: { "Content-Type": "text/markdown; charset=utf-8" },
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { data } = await sanityFetch({
|
|
54
|
+
query: PAGE_QUERY,
|
|
55
|
+
params: { slug },
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const page = data as PageResult | null;
|
|
59
|
+
|
|
60
|
+
const baseUrl = getBaseUrl();
|
|
61
|
+
|
|
62
|
+
if (!page) {
|
|
63
|
+
return new NextResponse("# 404 Not Found\n\nPage not found.", {
|
|
64
|
+
status: 404,
|
|
65
|
+
headers: { "Content-Type": "text/markdown; charset=utf-8" },
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const body = renderPageBuilderMarkdown(page.pageBuilder);
|
|
70
|
+
const canonicalPath = slug ? `/${slug}` : "/";
|
|
71
|
+
|
|
72
|
+
return new NextResponse(
|
|
73
|
+
[
|
|
74
|
+
`# ${page.title}`,
|
|
75
|
+
"",
|
|
76
|
+
page._updatedAt
|
|
77
|
+
? `**Updated:** ${new Date(page._updatedAt).toLocaleDateString()}`
|
|
78
|
+
: null,
|
|
79
|
+
"",
|
|
80
|
+
`Canonical URL: ${baseUrl}${canonicalPath}`,
|
|
81
|
+
"",
|
|
82
|
+
"---",
|
|
83
|
+
"",
|
|
84
|
+
body || "No page builder content yet.",
|
|
85
|
+
"",
|
|
86
|
+
"---",
|
|
87
|
+
"",
|
|
88
|
+
`[View all content](${baseUrl}/sitemap.md)`,
|
|
89
|
+
]
|
|
90
|
+
.filter((part): part is string => part !== null)
|
|
91
|
+
.join("\n"),
|
|
92
|
+
{
|
|
93
|
+
headers: {
|
|
94
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
95
|
+
Vary: "Accept",
|
|
96
|
+
"X-Content-Type-Options": "nosniff",
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { defineEnableDraftMode } from "next-sanity/draft-mode";
|
|
3
|
+
import { isSanityConfigured } from "@/lib/integrations/check-integration";
|
|
4
|
+
import { client } from "@/lib/integrations/sanity/client";
|
|
5
|
+
import { sanityToken } from "@/lib/integrations/sanity/env";
|
|
6
|
+
|
|
7
|
+
const draftModeHandler =
|
|
8
|
+
isSanityConfigured() && client
|
|
9
|
+
? defineEnableDraftMode({
|
|
10
|
+
client: client.withConfig({ token: sanityToken }),
|
|
11
|
+
})
|
|
12
|
+
: {
|
|
13
|
+
GET: () =>
|
|
14
|
+
NextResponse.json(
|
|
15
|
+
{ error: "Sanity is not configured" },
|
|
16
|
+
{ status: 503 },
|
|
17
|
+
),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const { GET } = draftModeHandler;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { revalidatePath, revalidateTag } from "next/cache";
|
|
2
|
+
import { type NextRequest, NextResponse } from "next/server";
|
|
3
|
+
import { parseBody } from "next-sanity/webhook";
|
|
4
|
+
import { client } from "@/lib/integrations/sanity/client";
|
|
5
|
+
import { SANITY_LAYOUT_TAGS } from "@/lib/integrations/sanity/fetchers/layout";
|
|
6
|
+
import { SANITY_REDIRECTS_TAG } from "@/lib/integrations/sanity/fetchers/redirects";
|
|
7
|
+
import { pageBuilderReferenceMembers as pbrm } from "@/lib/integrations/sanity/page-builder-config";
|
|
8
|
+
import {
|
|
9
|
+
P_REF_PB_COMP_QUERY,
|
|
10
|
+
P_REV_SLUGS_QUERY,
|
|
11
|
+
} from "@/lib/integrations/sanity/queries";
|
|
12
|
+
import type { P_REF_PB_COMP_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types";
|
|
13
|
+
import { getPagePath } from "@/lib/utils/sitemap";
|
|
14
|
+
|
|
15
|
+
const ERRORS = {
|
|
16
|
+
BAD_REQ: ["Bad Request", { status: 400 }],
|
|
17
|
+
INVALID_SIG: ["Invalid signature", { status: 401 }],
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
type WebhookSlug = {
|
|
21
|
+
current?: string | null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type WebhookDocument = {
|
|
25
|
+
_id: string;
|
|
26
|
+
_type: string;
|
|
27
|
+
resolvedSlug?: string | null;
|
|
28
|
+
slug?: WebhookSlug | null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type MutationOperation = "create" | "update" | "delete";
|
|
32
|
+
|
|
33
|
+
type RevalidateWebhookBody = WebhookDocument & {
|
|
34
|
+
operation?: MutationOperation;
|
|
35
|
+
before?: Partial<WebhookDocument> | null;
|
|
36
|
+
after?: Partial<WebhookDocument> | null;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const pageBuilderComponent = new Set<string>(pbrm.map((m) => m.documentType));
|
|
40
|
+
|
|
41
|
+
const normalizeRevalidationSlug = (slug: string | null | undefined) => {
|
|
42
|
+
if (slug === undefined) return null;
|
|
43
|
+
if (slug === null) return "/";
|
|
44
|
+
return `/${slug}`;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const getCanonicalPageSlug = async (documentId: string) => {
|
|
48
|
+
if (!client) return null;
|
|
49
|
+
|
|
50
|
+
const pageSlugs = await client.fetch<Array<{ slug: string | null }>>(
|
|
51
|
+
P_REV_SLUGS_QUERY,
|
|
52
|
+
{ documentIds: [documentId] },
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return normalizeRevalidationSlug(
|
|
56
|
+
pageSlugs.find((page) => page.slug !== undefined)?.slug,
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const getPagesThatReferenceComp = async (documentId: string) => {
|
|
61
|
+
if (!client) return [];
|
|
62
|
+
|
|
63
|
+
const pages = await client.fetch<P_REF_PB_COMP_QUERY_RESULT>(
|
|
64
|
+
P_REF_PB_COMP_QUERY,
|
|
65
|
+
{
|
|
66
|
+
documentId,
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return (pages ?? []).map((page) => normalizeRevalidationSlug(page.slug));
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const getAffectedPageSlugsForWebhook = async (body: RevalidateWebhookBody) => {
|
|
74
|
+
const pages: Array<string | null> = [];
|
|
75
|
+
|
|
76
|
+
if (body.operation === "delete" && body?.before?.slug?.current) {
|
|
77
|
+
revalidateTag(body.before.slug.current, "max");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// If the document is a page, get its slug
|
|
81
|
+
if (body._type === "page") {
|
|
82
|
+
const data = await getCanonicalPageSlug(body._id);
|
|
83
|
+
if (data !== null) pages.push(data);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// If the document is a page builder component, get the page slugs that reference it
|
|
87
|
+
if (pageBuilderComponent.has(body._type)) {
|
|
88
|
+
const data = await getPagesThatReferenceComp(body._id);
|
|
89
|
+
pages.push(...data);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return pages.filter((page): page is string => page !== null);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export async function POST(request: NextRequest) {
|
|
96
|
+
try {
|
|
97
|
+
const { body, isValidSignature } = await parseBody<RevalidateWebhookBody>(
|
|
98
|
+
request,
|
|
99
|
+
process.env.SANITY_REVALIDATE_SECRET,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (!isValidSignature) return new Response(...ERRORS.INVALID_SIG);
|
|
103
|
+
if (!(body?._type && body._id)) return new Response(...ERRORS.BAD_REQ);
|
|
104
|
+
if (body._id.startsWith("drafts."))
|
|
105
|
+
return NextResponse.json({ status: 200, now: Date.now(), skipped: true });
|
|
106
|
+
|
|
107
|
+
const affectedPageSlugs = await getAffectedPageSlugsForWebhook(body);
|
|
108
|
+
|
|
109
|
+
for (const slug of affectedPageSlugs) {
|
|
110
|
+
revalidatePath(getPagePath(slug));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (SANITY_LAYOUT_TAGS.includes(body?._type)) {
|
|
114
|
+
revalidateTag(body._type, "max");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (body._type === SANITY_REDIRECTS_TAG) {
|
|
118
|
+
revalidateTag(SANITY_REDIRECTS_TAG, "max");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return NextResponse.json({ status: 200, now: Date.now() });
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error("Revalidation error:", error);
|
|
124
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ALL_BLOG_ARTICLES_QUERY } from "@/lib/integrations/sanity/queries";
|
|
2
|
+
import { generateRssFeed, rssErrorResponse } from "@/lib/utils/rss";
|
|
3
|
+
import { getPageUrl, getSitemapBaseUrl } from "@/lib/utils/sitemap";
|
|
4
|
+
|
|
5
|
+
export const dynamic = "force-dynamic";
|
|
6
|
+
|
|
7
|
+
export async function GET() {
|
|
8
|
+
const baseUrl = getSitemapBaseUrl();
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const sanityModule = await import("@/lib/integrations/sanity/client");
|
|
12
|
+
const client = sanityModule?.client;
|
|
13
|
+
if (!client) return rssErrorResponse();
|
|
14
|
+
|
|
15
|
+
const articles = await client.fetch(ALL_BLOG_ARTICLES_QUERY);
|
|
16
|
+
|
|
17
|
+
const items = (articles ?? [])
|
|
18
|
+
.filter((article) => article.resolvedSlug && !article.noIndex)
|
|
19
|
+
.map((article) => ({
|
|
20
|
+
title: article.title ?? "Untitled",
|
|
21
|
+
link: getPageUrl(baseUrl, article.resolvedSlug),
|
|
22
|
+
description: article.metadata?.metaDescription ?? undefined,
|
|
23
|
+
pubDate: article.blogContent?.date ?? undefined,
|
|
24
|
+
authors: article.blogContent?.author?.name
|
|
25
|
+
? [article.blogContent.author.name]
|
|
26
|
+
: undefined,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
return generateRssFeed({
|
|
30
|
+
title: `${new URL(baseUrl).hostname} Blog`,
|
|
31
|
+
link: `${baseUrl}/blog`,
|
|
32
|
+
description: "Latest articles",
|
|
33
|
+
feedUrl: `${baseUrl}/feed.xml`,
|
|
34
|
+
items,
|
|
35
|
+
});
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error("Error generating RSS feed:", error);
|
|
38
|
+
return rssErrorResponse();
|
|
39
|
+
}
|
|
40
|
+
}
|