bsmnt 0.2.10 → 0.3.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/configs/skills.d.ts +27 -0
- package/dist/configs/skills.d.ts.map +1 -0
- package/dist/configs/skills.js +18 -0
- package/dist/configs/skills.js.map +1 -0
- package/dist/configs/skills.json +26 -0
- package/dist/helpers/add/hooks-config.d.ts.map +1 -1
- package/dist/helpers/add/hooks-config.js +0 -6
- package/dist/helpers/add/hooks-config.js.map +1 -1
- package/dist/helpers/create/copy-template.d.ts +1 -1
- package/dist/helpers/create/copy-template.d.ts.map +1 -1
- package/dist/helpers/create/index.d.ts.map +1 -1
- package/dist/helpers/create/index.js +2 -1
- package/dist/helpers/create/index.js.map +1 -1
- package/dist/helpers/create/setup-agent.d.ts.map +1 -1
- package/dist/helpers/create/setup-agent.js +15 -5
- package/dist/helpers/create/setup-agent.js.map +1 -1
- package/dist/helpers/integrate/merge-config.d.ts.map +1 -1
- package/dist/helpers/integrate/merge-config.js +1 -2
- package/dist/helpers/integrate/merge-config.js.map +1 -1
- package/dist/helpers/integrate/sanity/config.d.ts.map +1 -1
- package/dist/helpers/integrate/sanity/config.js +5 -10
- package/dist/helpers/integrate/sanity/config.js.map +1 -1
- package/dist/helpers/integrate/sanity/mergers/layout-merger.d.ts.map +1 -1
- package/dist/helpers/integrate/sanity/mergers/layout-merger.js +13 -12
- package/dist/helpers/integrate/sanity/mergers/layout-merger.js.map +1 -1
- package/dist/helpers/skills/index.d.ts +10 -0
- package/dist/helpers/skills/index.d.ts.map +1 -0
- package/dist/helpers/skills/index.js +136 -0
- package/dist/helpers/skills/index.js.map +1 -0
- package/dist/index.js +102 -35
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/helpers/integrate/sanity/files/app/api/blog/[slug]/route.ts +2 -1
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/confirm-publish-action.ts +31 -0
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/sanity.config.ts +17 -0
- package/src/helpers/integrate/sanity/files/lib/utils/json-ld.tsx +249 -0
- package/src/template-hooks/config.js +0 -6
- package/src/templates/next-default/app/layout.tsx +18 -0
- package/src/templates/next-default/lib/hooks/use-device-detection.ts +1 -1
- package/src/templates/next-default/lib/hooks/use-media-breakpoint.ts +1 -1
- package/src/templates/next-default/lib/hooks/use-media.ts +29 -0
- package/src/templates/next-default/lib/utils/json-ld.tsx +199 -0
- package/src/templates/next-default/package.json +1 -1
- package/src/templates/next-default/tsconfig.json +1 -0
- package/src/templates/next-experiments/app/layout.tsx +18 -0
- package/src/templates/next-experiments/lib/hooks/use-device-detection.ts +1 -1
- package/src/templates/next-experiments/lib/hooks/use-media-breakpoint.ts +1 -1
- package/src/templates/next-experiments/lib/hooks/use-media.ts +29 -0
- package/src/templates/next-experiments/lib/utils/json-ld.tsx +199 -0
- package/src/templates/next-experiments/package.json +1 -1
- package/src/templates/next-experiments/tsconfig.json +1 -0
- package/src/templates/next-pagebuilder/.env.example +11 -0
- package/src/templates/next-pagebuilder/README.md +23 -0
- package/src/templates/next-pagebuilder/_gitignore +67 -0
- package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +68 -0
- package/src/templates/next-pagebuilder/app/(content)/layout.tsx +13 -0
- package/src/templates/next-pagebuilder/app/api/[[...slug]]/route.ts +100 -0
- package/src/templates/next-pagebuilder/app/api/draft-mode/disable/route.ts +7 -0
- package/src/templates/next-pagebuilder/app/api/draft-mode/enable/route.ts +20 -0
- package/src/templates/next-pagebuilder/app/api/revalidate/route.ts +121 -0
- package/src/templates/next-pagebuilder/app/favicon.ico +0 -0
- package/src/templates/next-pagebuilder/app/layout.tsx +80 -0
- package/src/templates/next-pagebuilder/app/robots.ts +15 -0
- package/src/templates/next-pagebuilder/app/sitemap.md/route.ts +124 -0
- package/src/templates/next-pagebuilder/app/sitemap.xml/route.ts +80 -0
- package/src/templates/next-pagebuilder/app/studio/[[...tool]]/page.tsx +8 -0
- package/src/templates/next-pagebuilder/biome.json +239 -0
- package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +95 -0
- package/src/templates/next-pagebuilder/components/layout/header/components/cta-button.tsx +28 -0
- package/src/templates/next-pagebuilder/components/layout/header/components/mega-menu-panel.tsx +90 -0
- package/src/templates/next-pagebuilder/components/layout/header/components/nav-item-renderer.tsx +98 -0
- package/src/templates/next-pagebuilder/components/layout/header/components/nav-leaf-item.tsx +33 -0
- package/src/templates/next-pagebuilder/components/layout/header/components/types.ts +7 -0
- package/src/templates/next-pagebuilder/components/layout/header/header-client.tsx +110 -0
- package/src/templates/next-pagebuilder/components/layout/header/index.tsx +8 -0
- package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +45 -0
- package/src/templates/next-pagebuilder/components/layout/wrapper/index.tsx +30 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/article-content/index.tsx +83 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/article-content/related-post-item.tsx +27 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/description.tsx +17 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/hero.tsx +17 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/content-card.tsx +66 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/content-grid.tsx +42 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/index.tsx +28 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/types.ts +16 -0
- package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +36 -0
- package/src/templates/next-pagebuilder/components/page-builder/types.ts +23 -0
- package/src/templates/next-pagebuilder/components/page-document/index.tsx +91 -0
- package/src/templates/next-pagebuilder/components/sanity/draft-mode-toggle.tsx +27 -0
- package/src/templates/next-pagebuilder/components/sanity/rich-text.tsx +87 -0
- package/src/templates/next-pagebuilder/components/sanity/visual-editing.tsx +27 -0
- package/src/templates/next-pagebuilder/components/ui/image/index.tsx +216 -0
- package/src/templates/next-pagebuilder/components/ui/link/index.tsx +152 -0
- package/src/templates/next-pagebuilder/components/ui/sanity-image/index.tsx +41 -0
- package/src/templates/next-pagebuilder/lib/integrations/check-integration.ts +5 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/client.ts +27 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/components/page-builder-input.tsx +36 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/components/page-category-input.tsx +50 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/components/rich-text.tsx +84 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/confirm-publish-action.ts +40 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/env.ts +34 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/fetchers/layout.ts +35 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/icons.ts +58 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/live/index.tsx +61 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/page-builder-config.ts +132 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/page-category.ts +28 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/queries.ts +281 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.cli.ts +29 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.config.ts +211 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/index.ts +4 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/content/author.ts +70 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/index.ts +96 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/company-data.ts +62 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/navbar.ts +74 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/link.ts +125 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/metadata.ts +68 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page-builder.ts +39 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page.ts +232 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/singletons.ts +44 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/structure.ts +453 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/image.ts +8 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/link.ts +137 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/page-builder-markdown.ts +81 -0
- package/src/templates/next-pagebuilder/lib/scripts/sanity-typegen.ts +45 -0
- package/src/templates/next-pagebuilder/lib/styles/cn.ts +5 -0
- package/src/templates/next-pagebuilder/lib/styles/global.css +70 -0
- package/src/templates/next-pagebuilder/lib/utils/base-url.ts +17 -0
- package/src/templates/next-pagebuilder/lib/utils/format-date.ts +8 -0
- package/src/templates/next-pagebuilder/lib/utils/json-ld.tsx +213 -0
- package/src/templates/next-pagebuilder/lib/utils/metadata.ts +167 -0
- package/src/templates/next-pagebuilder/lib/utils/sitemap.ts +37 -0
- package/src/templates/next-pagebuilder/lib/utils/slug-tag.ts +6 -0
- package/src/templates/next-pagebuilder/next.config.ts +134 -0
- package/src/templates/next-pagebuilder/package.json +71 -0
- package/src/templates/next-pagebuilder/postcss.config.mjs +39 -0
- package/src/templates/next-pagebuilder/proxy.ts +81 -0
- package/src/templates/next-pagebuilder/svg.d.ts +5 -0
- package/src/templates/next-pagebuilder/tsconfig.json +38 -0
- package/src/templates/next-webgl/app/layout.tsx +18 -0
- package/src/templates/next-webgl/lib/hooks/use-device-detection.ts +1 -1
- package/src/templates/next-webgl/lib/hooks/use-media-breakpoint.ts +1 -1
- package/src/templates/next-webgl/lib/hooks/use-media.ts +29 -0
- package/src/templates/next-webgl/lib/utils/json-ld.tsx +199 -0
- package/src/templates/next-webgl/package.json +1 -1
- package/src/templates/next-webgl/tsconfig.json +1 -0
- package/plugins/no-anchor-element.grit +0 -11
- package/plugins/no-relative-parent-imports.grit +0 -6
- package/plugins/no-unnecessary-forwardref.grit +0 -5
- package/src/helpers/integrate/sanity/files/lib/scripts/copy-sanity-mcp.ts +0 -23
- package/src/helpers/integrate/sanity/files/lib/scripts/generate-page.ts +0 -297
- package/src/template-hooks/use-media.ts +0 -33
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { notFound } from "next/navigation"
|
|
2
|
+
import { client } from "@/lib/integrations/sanity/client"
|
|
3
|
+
import { sanityFetch } from "@/lib/integrations/sanity/live"
|
|
4
|
+
import {
|
|
5
|
+
ALL_PAGE_SLUGS_QUERY,
|
|
6
|
+
PAGE_QUERY,
|
|
7
|
+
} from "@/lib/integrations/sanity/queries"
|
|
8
|
+
import type {
|
|
9
|
+
ALL_PAGE_SLUGS_QUERY_RESULT,
|
|
10
|
+
PAGE_QUERY_RESULT,
|
|
11
|
+
} from "@/lib/integrations/sanity/sanity.types"
|
|
12
|
+
import { generateSanityMetadata } from "@/lib/utils/metadata"
|
|
13
|
+
import { getSlugTag } from "@/lib/utils/slug-tag"
|
|
14
|
+
import { PageDocument } from "@/components/page-document"
|
|
15
|
+
|
|
16
|
+
const getPageDocument = async (slug: string | null, stega = true) =>
|
|
17
|
+
sanityFetch({
|
|
18
|
+
query: PAGE_QUERY,
|
|
19
|
+
params: { slug },
|
|
20
|
+
tags: [getSlugTag(slug)],
|
|
21
|
+
stega,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const toPath = (segments?: string[]) =>
|
|
25
|
+
segments?.filter(Boolean).join("/") || null
|
|
26
|
+
|
|
27
|
+
const getResolvedPage = async (
|
|
28
|
+
path: string | null,
|
|
29
|
+
stega = true
|
|
30
|
+
): Promise<NonNullable<PAGE_QUERY_RESULT> | null> => {
|
|
31
|
+
const { data: page } = await getPageDocument(path, stega)
|
|
32
|
+
return (page as NonNullable<PAGE_QUERY_RESULT> | null) ?? null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const generateStaticParams = async () => {
|
|
36
|
+
if (!client) return []
|
|
37
|
+
|
|
38
|
+
const slugs =
|
|
39
|
+
await client.fetch<ALL_PAGE_SLUGS_QUERY_RESULT>(ALL_PAGE_SLUGS_QUERY)
|
|
40
|
+
|
|
41
|
+
return (slugs ?? []).flatMap((page) => {
|
|
42
|
+
if (!page.slug) return []
|
|
43
|
+
return { slug: page.slug.split("/") }
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type Props = { params: Promise<{ slug?: string[] }> }
|
|
48
|
+
|
|
49
|
+
export const generateMetadata = async ({ params }: Props) => {
|
|
50
|
+
const { slug } = await params
|
|
51
|
+
const path = toPath(slug)
|
|
52
|
+
const page = await getResolvedPage(path, false)
|
|
53
|
+
|
|
54
|
+
return page
|
|
55
|
+
? generateSanityMetadata({ document: page, url: path ? `/${path}` : "/" })
|
|
56
|
+
: {}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default async function CatchAllPage({ params }: Props) {
|
|
60
|
+
const { slug } = await params
|
|
61
|
+
|
|
62
|
+
const path = toPath(slug)
|
|
63
|
+
const page = await getResolvedPage(path)
|
|
64
|
+
|
|
65
|
+
if (!page) return notFound()
|
|
66
|
+
|
|
67
|
+
return <PageDocument page={page} path={path} />
|
|
68
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { JsonLd } from "@/components/layout/json-ld"
|
|
2
|
+
import { Wrapper } from "@/components/layout/wrapper"
|
|
3
|
+
import { SanityVisualEditing } from "@/components/sanity/visual-editing"
|
|
4
|
+
|
|
5
|
+
const Layout = async ({ children }: { children: React.ReactNode }) => (
|
|
6
|
+
<>
|
|
7
|
+
<JsonLd />
|
|
8
|
+
<Wrapper>{children}</Wrapper>
|
|
9
|
+
<SanityVisualEditing />
|
|
10
|
+
</>
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
export default Layout
|
|
@@ -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,121 @@
|
|
|
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 { pageBuilderReferenceMembers as pbrm } from "@/lib/integrations/sanity/page-builder-config"
|
|
7
|
+
import {
|
|
8
|
+
P_REF_PB_COMP_QUERY,
|
|
9
|
+
P_REV_SLUGS_QUERY,
|
|
10
|
+
} from "@/lib/integrations/sanity/queries"
|
|
11
|
+
import type { P_REF_PB_COMP_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types"
|
|
12
|
+
import { getPagePath } from "@/lib/utils/sitemap"
|
|
13
|
+
|
|
14
|
+
const ERRORS = {
|
|
15
|
+
BAD_REQ: ["Bad Request", { status: 400 }],
|
|
16
|
+
INVALID_SIG: ["Invalid signature", { status: 401 }],
|
|
17
|
+
} as const
|
|
18
|
+
|
|
19
|
+
type WebhookSlug = {
|
|
20
|
+
current?: string | null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type WebhookDocument = {
|
|
24
|
+
_id: string
|
|
25
|
+
_type: string
|
|
26
|
+
resolvedSlug?: string | null
|
|
27
|
+
slug?: WebhookSlug | null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type MutationOperation = "create" | "update" | "delete"
|
|
31
|
+
|
|
32
|
+
type RevalidateWebhookBody = WebhookDocument & {
|
|
33
|
+
operation?: MutationOperation
|
|
34
|
+
before?: Partial<WebhookDocument> | null
|
|
35
|
+
after?: Partial<WebhookDocument> | null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const pageBuilderComponent = new Set<string>(pbrm.map((m) => m.documentType))
|
|
39
|
+
|
|
40
|
+
const normalizeRevalidationSlug = (slug: string | null | undefined) => {
|
|
41
|
+
if (slug === undefined) return null
|
|
42
|
+
if (slug === null) return "/"
|
|
43
|
+
return `/${slug}`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const getCanonicalPageSlug = async (documentId: string) => {
|
|
47
|
+
if (!client) return null
|
|
48
|
+
|
|
49
|
+
const pageSlugs = await client.fetch<Array<{ slug: string | null }>>(
|
|
50
|
+
P_REV_SLUGS_QUERY,
|
|
51
|
+
{ documentIds: [documentId] }
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return normalizeRevalidationSlug(
|
|
55
|
+
pageSlugs.find((page) => page.slug !== undefined)?.slug
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const getPagesThatReferenceComp = async (documentId: string) => {
|
|
60
|
+
if (!client) return []
|
|
61
|
+
|
|
62
|
+
const pages = await client.fetch<P_REF_PB_COMP_QUERY_RESULT>(
|
|
63
|
+
P_REF_PB_COMP_QUERY,
|
|
64
|
+
{
|
|
65
|
+
documentId,
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return (pages ?? []).map((page) => normalizeRevalidationSlug(page.slug))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const getAffectedPageSlugsForWebhook = async (body: RevalidateWebhookBody) => {
|
|
73
|
+
const pages: Array<string | null> = []
|
|
74
|
+
|
|
75
|
+
if (body.operation === "delete" && body?.before?.slug?.current) {
|
|
76
|
+
revalidateTag(body.before.slug.current, "max")
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// If the document is a page, get its slug
|
|
80
|
+
if (body._type === "page") {
|
|
81
|
+
const data = await getCanonicalPageSlug(body._id)
|
|
82
|
+
if (data !== null) pages.push(data)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// If the document is a page builder component, get the page slugs that reference it
|
|
86
|
+
if (pageBuilderComponent.has(body._type)) {
|
|
87
|
+
const data = await getPagesThatReferenceComp(body._id)
|
|
88
|
+
pages.push(...data)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return pages.filter((page): page is string => page !== null)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function POST(request: NextRequest) {
|
|
95
|
+
try {
|
|
96
|
+
const { body, isValidSignature } = await parseBody<RevalidateWebhookBody>(
|
|
97
|
+
request,
|
|
98
|
+
process.env.SANITY_REVALIDATE_SECRET
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if (!isValidSignature) return new Response(...ERRORS.INVALID_SIG)
|
|
102
|
+
if (!(body?._type && body._id)) return new Response(...ERRORS.BAD_REQ)
|
|
103
|
+
if (body._id.startsWith("drafts."))
|
|
104
|
+
return NextResponse.json({ status: 200, now: Date.now(), skipped: true })
|
|
105
|
+
|
|
106
|
+
const affectedPageSlugs = await getAffectedPageSlugsForWebhook(body)
|
|
107
|
+
|
|
108
|
+
for (const slug of affectedPageSlugs) {
|
|
109
|
+
revalidatePath(getPagePath(slug))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (SANITY_LAYOUT_TAGS.includes(body?._type)) {
|
|
113
|
+
revalidateTag(body._type, "max")
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return NextResponse.json({ status: 200, now: Date.now() })
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error("Revalidation error:", error)
|
|
119
|
+
return new Response("Internal Server Error", { status: 500 })
|
|
120
|
+
}
|
|
121
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import "@/lib/styles/global.css"
|
|
2
|
+
|
|
3
|
+
import type { Metadata } from "next"
|
|
4
|
+
import { Geist } from "next/font/google"
|
|
5
|
+
import type { PropsWithChildren } from "react"
|
|
6
|
+
import { cn } from "@/lib/styles/cn"
|
|
7
|
+
import { getBaseUrl } from "@/lib/utils/base-url"
|
|
8
|
+
import AppData from "@/package.json"
|
|
9
|
+
|
|
10
|
+
const APP_NAME = AppData.name
|
|
11
|
+
const APP_DEFAULT_TITLE = "My Site"
|
|
12
|
+
const APP_TITLE_TEMPLATE = "%s"
|
|
13
|
+
const APP_DESCRIPTION = AppData.description
|
|
14
|
+
const APP_BASE_URL = getBaseUrl()
|
|
15
|
+
|
|
16
|
+
const geist = Geist({
|
|
17
|
+
subsets: ["latin"],
|
|
18
|
+
variable: "--font-geist-variable",
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export const metadata: Metadata = {
|
|
22
|
+
alternates: {
|
|
23
|
+
canonical: "/",
|
|
24
|
+
languages: {
|
|
25
|
+
"en-US": "/en-US",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
appleWebApp: {
|
|
29
|
+
capable: true,
|
|
30
|
+
statusBarStyle: "default",
|
|
31
|
+
title: APP_DEFAULT_TITLE,
|
|
32
|
+
},
|
|
33
|
+
applicationName: APP_NAME,
|
|
34
|
+
authors: [{ name: "basement.studio", url: "https://basement.studio" }],
|
|
35
|
+
description: APP_DESCRIPTION,
|
|
36
|
+
formatDetection: { telephone: false },
|
|
37
|
+
metadataBase: new URL(APP_BASE_URL),
|
|
38
|
+
openGraph: {
|
|
39
|
+
description: APP_DESCRIPTION,
|
|
40
|
+
images: [
|
|
41
|
+
{
|
|
42
|
+
alt: APP_DEFAULT_TITLE,
|
|
43
|
+
height: 630,
|
|
44
|
+
url: "/opengraph-image.jpg",
|
|
45
|
+
width: 1200,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
locale: "en_US",
|
|
49
|
+
siteName: APP_NAME,
|
|
50
|
+
title: {
|
|
51
|
+
default: APP_DEFAULT_TITLE,
|
|
52
|
+
template: APP_TITLE_TEMPLATE,
|
|
53
|
+
},
|
|
54
|
+
type: "website",
|
|
55
|
+
url: APP_BASE_URL,
|
|
56
|
+
},
|
|
57
|
+
other: {
|
|
58
|
+
"fb:app_id": process.env.NEXT_PUBLIC_FACEBOOK_APP_ID || "",
|
|
59
|
+
},
|
|
60
|
+
title: {
|
|
61
|
+
default: APP_DEFAULT_TITLE,
|
|
62
|
+
template: APP_TITLE_TEMPLATE,
|
|
63
|
+
},
|
|
64
|
+
twitter: {
|
|
65
|
+
card: "summary_large_image",
|
|
66
|
+
description: APP_DESCRIPTION,
|
|
67
|
+
title: {
|
|
68
|
+
default: APP_DEFAULT_TITLE,
|
|
69
|
+
template: APP_TITLE_TEMPLATE,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const Layout = ({ children }: PropsWithChildren) => (
|
|
75
|
+
<html lang="en" dir="ltr" className={cn(geist.variable)}>
|
|
76
|
+
<body className="text-black">{children}</body>
|
|
77
|
+
</html>
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
export default Layout
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MetadataRoute } from "next";
|
|
2
|
+
|
|
3
|
+
const APP_BASE_URL =
|
|
4
|
+
process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
|
|
5
|
+
|
|
6
|
+
export default function robots(): MetadataRoute.Robots {
|
|
7
|
+
return {
|
|
8
|
+
rules: {
|
|
9
|
+
userAgent: "*",
|
|
10
|
+
allow: "/",
|
|
11
|
+
disallow: [],
|
|
12
|
+
},
|
|
13
|
+
sitemap: `${APP_BASE_URL}/sitemap.xml`,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { NextResponse } from "next/server"
|
|
2
|
+
import { isSanityConfigured } from "@/lib/integrations/check-integration"
|
|
3
|
+
import type { PAGE_SITEMAP_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types"
|
|
4
|
+
import {
|
|
5
|
+
getMarkdownPath,
|
|
6
|
+
getSitemap,
|
|
7
|
+
getSitemapBaseUrl,
|
|
8
|
+
} from "@/lib/utils/sitemap"
|
|
9
|
+
|
|
10
|
+
export const dynamic = "force-dynamic"
|
|
11
|
+
|
|
12
|
+
type PageDoc = PAGE_SITEMAP_QUERY_RESULT[number]
|
|
13
|
+
|
|
14
|
+
const ROOT_GROUP = "__root__"
|
|
15
|
+
|
|
16
|
+
const getFolderGroup = (slug: string | null) => {
|
|
17
|
+
if (!slug) return ROOT_GROUP
|
|
18
|
+
|
|
19
|
+
const segments = slug.split("/").filter(Boolean)
|
|
20
|
+
|
|
21
|
+
if (segments.length <= 1) return ROOT_GROUP
|
|
22
|
+
|
|
23
|
+
return segments.slice(0, -1).join("/")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const getGroupHeading = (group: string) => {
|
|
27
|
+
if (group === ROOT_GROUP) return "### Root Pages"
|
|
28
|
+
|
|
29
|
+
return `### /${group}`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const sortPages = (left: PageDoc, right: PageDoc) => {
|
|
33
|
+
const leftLabel = left.title || left.slug || "Homepage"
|
|
34
|
+
const rightLabel = right.title || right.slug || "Homepage"
|
|
35
|
+
|
|
36
|
+
return leftLabel.localeCompare(rightLabel)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const GET = async () => {
|
|
40
|
+
const baseUrl = getSitemapBaseUrl()
|
|
41
|
+
|
|
42
|
+
if (!isSanityConfigured()) {
|
|
43
|
+
return new NextResponse(
|
|
44
|
+
[
|
|
45
|
+
"# Sitemap",
|
|
46
|
+
"",
|
|
47
|
+
"## Pages",
|
|
48
|
+
"",
|
|
49
|
+
`- [Homepage](${baseUrl}/index.md)`,
|
|
50
|
+
"",
|
|
51
|
+
"CMS not configured. No additional content available.",
|
|
52
|
+
].join("\n"),
|
|
53
|
+
{
|
|
54
|
+
headers: { "Content-Type": "text/markdown; charset=utf-8" },
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const allPages = await getSitemap()
|
|
61
|
+
|
|
62
|
+
if (!allPages) {
|
|
63
|
+
return new NextResponse("# Sitemap\n\nUnable to connect to CMS.", {
|
|
64
|
+
status: 503,
|
|
65
|
+
headers: { "Content-Type": "text/markdown; charset=utf-8" },
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const pages = (allPages ?? []).filter((page) => !page.noIndex)
|
|
70
|
+
const pageGroups = new Map<string, PageDoc[]>()
|
|
71
|
+
|
|
72
|
+
for (const page of pages) {
|
|
73
|
+
const group = getFolderGroup(page.slug ?? null)
|
|
74
|
+
const groupedPages = pageGroups.get(group) ?? []
|
|
75
|
+
|
|
76
|
+
groupedPages.push(page)
|
|
77
|
+
pageGroups.set(group, groupedPages)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const sortedGroupNames = [...pageGroups.keys()].sort((left, right) => {
|
|
81
|
+
if (left === ROOT_GROUP) return -1
|
|
82
|
+
if (right === ROOT_GROUP) return 1
|
|
83
|
+
|
|
84
|
+
return left.localeCompare(right)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const parts = [
|
|
88
|
+
`# ${new URL(baseUrl).hostname} - Content Sitemap`,
|
|
89
|
+
"",
|
|
90
|
+
"> This sitemap lists all available markdown content for AI agents.",
|
|
91
|
+
"",
|
|
92
|
+
"## Pages",
|
|
93
|
+
"",
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
if (pages.length > 0) {
|
|
97
|
+
for (const groupName of sortedGroupNames) {
|
|
98
|
+
const groupedPages = (pageGroups.get(groupName) ?? []).sort(sortPages)
|
|
99
|
+
|
|
100
|
+
if (groupedPages.length === 0) continue
|
|
101
|
+
|
|
102
|
+
parts.push(getGroupHeading(groupName), "")
|
|
103
|
+
|
|
104
|
+
for (const page of groupedPages) {
|
|
105
|
+
parts.push(
|
|
106
|
+
`- [${page.title || page.slug || "Homepage"}](${getMarkdownPath(baseUrl, page.slug ?? null)})`
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
parts.push("")
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (pages.length === 0) parts.push("No content published yet.", "")
|
|
115
|
+
|
|
116
|
+
return new NextResponse(parts.join("\n"))
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error("Error generating sitemap.md:", error)
|
|
119
|
+
return new NextResponse("# Sitemap\n\nError fetching content.", {
|
|
120
|
+
status: 500,
|
|
121
|
+
headers: { "Content-Type": "text/markdown; charset=utf-8" },
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { NextResponse } from "next/server"
|
|
2
|
+
import { isSanityConfigured } from "@/lib/integrations/check-integration"
|
|
3
|
+
import type { PAGE_SITEMAP_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types"
|
|
4
|
+
import { getPageUrl, getSitemap, getSitemapBaseUrl } from "@/lib/utils/sitemap"
|
|
5
|
+
|
|
6
|
+
export const dynamic = "force-dynamic"
|
|
7
|
+
|
|
8
|
+
type PageDoc = PAGE_SITEMAP_QUERY_RESULT[number]
|
|
9
|
+
|
|
10
|
+
interface SitemapEntry {
|
|
11
|
+
url: string
|
|
12
|
+
lastModified?: string | null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const createPageEntry = (
|
|
16
|
+
baseUrl: string,
|
|
17
|
+
slug: string | null,
|
|
18
|
+
updatedAt?: string | null
|
|
19
|
+
): SitemapEntry => ({
|
|
20
|
+
url: getPageUrl(baseUrl, slug),
|
|
21
|
+
lastModified: updatedAt ?? null,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const escapeXml = (value: string) =>
|
|
25
|
+
value
|
|
26
|
+
.replaceAll("&", "&")
|
|
27
|
+
.replaceAll("<", "<")
|
|
28
|
+
.replaceAll(">", ">")
|
|
29
|
+
.replaceAll('"', """)
|
|
30
|
+
.replaceAll("'", "'")
|
|
31
|
+
|
|
32
|
+
const renderUrl = (entry: SitemapEntry) => {
|
|
33
|
+
const parts = [" <url>", ` <loc>${escapeXml(entry.url)}</loc>`]
|
|
34
|
+
|
|
35
|
+
if (entry.lastModified) {
|
|
36
|
+
parts.push(
|
|
37
|
+
` <lastmod>${new Date(entry.lastModified).toISOString()}</lastmod>`
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
parts.push(" </url>")
|
|
42
|
+
|
|
43
|
+
return parts.join("\n")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const renderSitemap = (entries: SitemapEntry[]) =>
|
|
47
|
+
[
|
|
48
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
49
|
+
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
|
50
|
+
...entries.map(renderUrl),
|
|
51
|
+
"</urlset>",
|
|
52
|
+
].join("\n")
|
|
53
|
+
|
|
54
|
+
const getSitemapResponse = (entries: SitemapEntry[]) =>
|
|
55
|
+
new NextResponse(renderSitemap(entries))
|
|
56
|
+
|
|
57
|
+
export const GET = async () => {
|
|
58
|
+
const baseUrl = getSitemapBaseUrl()
|
|
59
|
+
const homepageEntry = createPageEntry(baseUrl, null)
|
|
60
|
+
|
|
61
|
+
if (!isSanityConfigured()) return getSitemapResponse([homepageEntry])
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const allPages = await getSitemap()
|
|
65
|
+
|
|
66
|
+
if (!allPages) return getSitemapResponse([homepageEntry])
|
|
67
|
+
|
|
68
|
+
const pages = (allPages ?? []).filter((page) => !page.noIndex)
|
|
69
|
+
const sitemapEntries = pages.map((page: PageDoc) =>
|
|
70
|
+
createPageEntry(baseUrl, page.slug ?? null, page._updatedAt)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return getSitemapResponse(
|
|
74
|
+
sitemapEntries.length > 0 ? sitemapEntries : [homepageEntry]
|
|
75
|
+
)
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error("Error generating sitemap.xml:", error)
|
|
78
|
+
return getSitemapResponse([homepageEntry])
|
|
79
|
+
}
|
|
80
|
+
}
|