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.
Files changed (163) hide show
  1. package/dist/configs/skills.d.ts +27 -0
  2. package/dist/configs/skills.d.ts.map +1 -0
  3. package/dist/configs/skills.js +18 -0
  4. package/dist/configs/skills.js.map +1 -0
  5. package/dist/configs/skills.json +26 -0
  6. package/dist/helpers/add/hooks-config.d.ts.map +1 -1
  7. package/dist/helpers/add/hooks-config.js +0 -6
  8. package/dist/helpers/add/hooks-config.js.map +1 -1
  9. package/dist/helpers/create/copy-template.d.ts +1 -1
  10. package/dist/helpers/create/copy-template.d.ts.map +1 -1
  11. package/dist/helpers/create/index.d.ts.map +1 -1
  12. package/dist/helpers/create/index.js +2 -1
  13. package/dist/helpers/create/index.js.map +1 -1
  14. package/dist/helpers/create/setup-agent.d.ts.map +1 -1
  15. package/dist/helpers/create/setup-agent.js +15 -5
  16. package/dist/helpers/create/setup-agent.js.map +1 -1
  17. package/dist/helpers/integrate/merge-config.d.ts.map +1 -1
  18. package/dist/helpers/integrate/merge-config.js +1 -2
  19. package/dist/helpers/integrate/merge-config.js.map +1 -1
  20. package/dist/helpers/integrate/sanity/config.d.ts.map +1 -1
  21. package/dist/helpers/integrate/sanity/config.js +5 -10
  22. package/dist/helpers/integrate/sanity/config.js.map +1 -1
  23. package/dist/helpers/integrate/sanity/mergers/layout-merger.d.ts.map +1 -1
  24. package/dist/helpers/integrate/sanity/mergers/layout-merger.js +13 -12
  25. package/dist/helpers/integrate/sanity/mergers/layout-merger.js.map +1 -1
  26. package/dist/helpers/skills/index.d.ts +10 -0
  27. package/dist/helpers/skills/index.d.ts.map +1 -0
  28. package/dist/helpers/skills/index.js +136 -0
  29. package/dist/helpers/skills/index.js.map +1 -0
  30. package/dist/index.js +102 -35
  31. package/dist/index.js.map +1 -1
  32. package/package.json +3 -2
  33. package/src/helpers/integrate/sanity/files/app/api/blog/[slug]/route.ts +2 -1
  34. package/src/helpers/integrate/sanity/files/lib/integrations/sanity/confirm-publish-action.ts +31 -0
  35. package/src/helpers/integrate/sanity/files/lib/integrations/sanity/sanity.config.ts +17 -0
  36. package/src/helpers/integrate/sanity/files/lib/utils/json-ld.tsx +249 -0
  37. package/src/template-hooks/config.js +0 -6
  38. package/src/templates/next-default/app/layout.tsx +18 -0
  39. package/src/templates/next-default/lib/hooks/use-device-detection.ts +1 -1
  40. package/src/templates/next-default/lib/hooks/use-media-breakpoint.ts +1 -1
  41. package/src/templates/next-default/lib/hooks/use-media.ts +29 -0
  42. package/src/templates/next-default/lib/utils/json-ld.tsx +199 -0
  43. package/src/templates/next-default/package.json +1 -1
  44. package/src/templates/next-default/tsconfig.json +1 -0
  45. package/src/templates/next-experiments/app/layout.tsx +18 -0
  46. package/src/templates/next-experiments/lib/hooks/use-device-detection.ts +1 -1
  47. package/src/templates/next-experiments/lib/hooks/use-media-breakpoint.ts +1 -1
  48. package/src/templates/next-experiments/lib/hooks/use-media.ts +29 -0
  49. package/src/templates/next-experiments/lib/utils/json-ld.tsx +199 -0
  50. package/src/templates/next-experiments/package.json +1 -1
  51. package/src/templates/next-experiments/tsconfig.json +1 -0
  52. package/src/templates/next-pagebuilder/.env.example +11 -0
  53. package/src/templates/next-pagebuilder/README.md +23 -0
  54. package/src/templates/next-pagebuilder/_gitignore +67 -0
  55. package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +68 -0
  56. package/src/templates/next-pagebuilder/app/(content)/layout.tsx +13 -0
  57. package/src/templates/next-pagebuilder/app/api/[[...slug]]/route.ts +100 -0
  58. package/src/templates/next-pagebuilder/app/api/draft-mode/disable/route.ts +7 -0
  59. package/src/templates/next-pagebuilder/app/api/draft-mode/enable/route.ts +20 -0
  60. package/src/templates/next-pagebuilder/app/api/revalidate/route.ts +121 -0
  61. package/src/templates/next-pagebuilder/app/favicon.ico +0 -0
  62. package/src/templates/next-pagebuilder/app/layout.tsx +80 -0
  63. package/src/templates/next-pagebuilder/app/robots.ts +15 -0
  64. package/src/templates/next-pagebuilder/app/sitemap.md/route.ts +124 -0
  65. package/src/templates/next-pagebuilder/app/sitemap.xml/route.ts +80 -0
  66. package/src/templates/next-pagebuilder/app/studio/[[...tool]]/page.tsx +8 -0
  67. package/src/templates/next-pagebuilder/biome.json +239 -0
  68. package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +95 -0
  69. package/src/templates/next-pagebuilder/components/layout/header/components/cta-button.tsx +28 -0
  70. package/src/templates/next-pagebuilder/components/layout/header/components/mega-menu-panel.tsx +90 -0
  71. package/src/templates/next-pagebuilder/components/layout/header/components/nav-item-renderer.tsx +98 -0
  72. package/src/templates/next-pagebuilder/components/layout/header/components/nav-leaf-item.tsx +33 -0
  73. package/src/templates/next-pagebuilder/components/layout/header/components/types.ts +7 -0
  74. package/src/templates/next-pagebuilder/components/layout/header/header-client.tsx +110 -0
  75. package/src/templates/next-pagebuilder/components/layout/header/index.tsx +8 -0
  76. package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +45 -0
  77. package/src/templates/next-pagebuilder/components/layout/wrapper/index.tsx +30 -0
  78. package/src/templates/next-pagebuilder/components/page-builder/components/article-content/index.tsx +83 -0
  79. package/src/templates/next-pagebuilder/components/page-builder/components/article-content/related-post-item.tsx +27 -0
  80. package/src/templates/next-pagebuilder/components/page-builder/components/description.tsx +17 -0
  81. package/src/templates/next-pagebuilder/components/page-builder/components/hero.tsx +17 -0
  82. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/content-card.tsx +66 -0
  83. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/content-grid.tsx +42 -0
  84. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/index.tsx +28 -0
  85. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/types.ts +16 -0
  86. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +36 -0
  87. package/src/templates/next-pagebuilder/components/page-builder/types.ts +23 -0
  88. package/src/templates/next-pagebuilder/components/page-document/index.tsx +91 -0
  89. package/src/templates/next-pagebuilder/components/sanity/draft-mode-toggle.tsx +27 -0
  90. package/src/templates/next-pagebuilder/components/sanity/rich-text.tsx +87 -0
  91. package/src/templates/next-pagebuilder/components/sanity/visual-editing.tsx +27 -0
  92. package/src/templates/next-pagebuilder/components/ui/image/index.tsx +216 -0
  93. package/src/templates/next-pagebuilder/components/ui/link/index.tsx +152 -0
  94. package/src/templates/next-pagebuilder/components/ui/sanity-image/index.tsx +41 -0
  95. package/src/templates/next-pagebuilder/lib/integrations/check-integration.ts +5 -0
  96. package/src/templates/next-pagebuilder/lib/integrations/sanity/client.ts +27 -0
  97. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
  98. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/page-builder-input.tsx +36 -0
  99. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/page-category-input.tsx +50 -0
  100. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/rich-text.tsx +84 -0
  101. package/src/templates/next-pagebuilder/lib/integrations/sanity/confirm-publish-action.ts +40 -0
  102. package/src/templates/next-pagebuilder/lib/integrations/sanity/env.ts +34 -0
  103. package/src/templates/next-pagebuilder/lib/integrations/sanity/fetchers/layout.ts +35 -0
  104. package/src/templates/next-pagebuilder/lib/integrations/sanity/icons.ts +58 -0
  105. package/src/templates/next-pagebuilder/lib/integrations/sanity/live/index.tsx +61 -0
  106. package/src/templates/next-pagebuilder/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
  107. package/src/templates/next-pagebuilder/lib/integrations/sanity/page-builder-config.ts +132 -0
  108. package/src/templates/next-pagebuilder/lib/integrations/sanity/page-category.ts +28 -0
  109. package/src/templates/next-pagebuilder/lib/integrations/sanity/queries.ts +281 -0
  110. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.cli.ts +29 -0
  111. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.config.ts +211 -0
  112. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/index.ts +4 -0
  113. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
  114. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
  115. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
  116. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
  117. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/content/author.ts +70 -0
  118. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
  119. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/index.ts +96 -0
  120. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/company-data.ts +62 -0
  121. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
  122. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/navbar.ts +74 -0
  123. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/link.ts +125 -0
  124. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
  125. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/metadata.ts +68 -0
  126. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
  127. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page-builder.ts +39 -0
  128. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
  129. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page.ts +232 -0
  130. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
  131. package/src/templates/next-pagebuilder/lib/integrations/sanity/singletons.ts +44 -0
  132. package/src/templates/next-pagebuilder/lib/integrations/sanity/structure.ts +453 -0
  133. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/image.ts +8 -0
  134. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/link.ts +137 -0
  135. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/page-builder-markdown.ts +81 -0
  136. package/src/templates/next-pagebuilder/lib/scripts/sanity-typegen.ts +45 -0
  137. package/src/templates/next-pagebuilder/lib/styles/cn.ts +5 -0
  138. package/src/templates/next-pagebuilder/lib/styles/global.css +70 -0
  139. package/src/templates/next-pagebuilder/lib/utils/base-url.ts +17 -0
  140. package/src/templates/next-pagebuilder/lib/utils/format-date.ts +8 -0
  141. package/src/templates/next-pagebuilder/lib/utils/json-ld.tsx +213 -0
  142. package/src/templates/next-pagebuilder/lib/utils/metadata.ts +167 -0
  143. package/src/templates/next-pagebuilder/lib/utils/sitemap.ts +37 -0
  144. package/src/templates/next-pagebuilder/lib/utils/slug-tag.ts +6 -0
  145. package/src/templates/next-pagebuilder/next.config.ts +134 -0
  146. package/src/templates/next-pagebuilder/package.json +71 -0
  147. package/src/templates/next-pagebuilder/postcss.config.mjs +39 -0
  148. package/src/templates/next-pagebuilder/proxy.ts +81 -0
  149. package/src/templates/next-pagebuilder/svg.d.ts +5 -0
  150. package/src/templates/next-pagebuilder/tsconfig.json +38 -0
  151. package/src/templates/next-webgl/app/layout.tsx +18 -0
  152. package/src/templates/next-webgl/lib/hooks/use-device-detection.ts +1 -1
  153. package/src/templates/next-webgl/lib/hooks/use-media-breakpoint.ts +1 -1
  154. package/src/templates/next-webgl/lib/hooks/use-media.ts +29 -0
  155. package/src/templates/next-webgl/lib/utils/json-ld.tsx +199 -0
  156. package/src/templates/next-webgl/package.json +1 -1
  157. package/src/templates/next-webgl/tsconfig.json +1 -0
  158. package/plugins/no-anchor-element.grit +0 -11
  159. package/plugins/no-relative-parent-imports.grit +0 -6
  160. package/plugins/no-unnecessary-forwardref.grit +0 -5
  161. package/src/helpers/integrate/sanity/files/lib/scripts/copy-sanity-mcp.ts +0 -23
  162. package/src/helpers/integrate/sanity/files/lib/scripts/generate-page.ts +0 -297
  163. 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,7 @@
1
+ import { draftMode } from "next/headers"
2
+ import { type NextRequest, NextResponse } from "next/server"
3
+
4
+ export async function GET(request: NextRequest) {
5
+ ;(await draftMode()).disable()
6
+ return NextResponse.redirect(new URL("/", request.url))
7
+ }
@@ -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
+ }
@@ -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("&", "&amp;")
27
+ .replaceAll("<", "&lt;")
28
+ .replaceAll(">", "&gt;")
29
+ .replaceAll('"', "&quot;")
30
+ .replaceAll("'", "&apos;")
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
+ }
@@ -0,0 +1,8 @@
1
+ "use client"
2
+
3
+ import { NextStudio } from "next-sanity/studio"
4
+ import config from "@/lib/integrations/sanity/sanity.config"
5
+
6
+ const StudioPage = () => <NextStudio config={config} />
7
+
8
+ export default StudioPage