bsmnt 0.2.11 → 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 (115) hide show
  1. package/dist/helpers/create/copy-template.d.ts +1 -1
  2. package/dist/helpers/create/copy-template.d.ts.map +1 -1
  3. package/dist/helpers/create/index.d.ts.map +1 -1
  4. package/dist/helpers/create/index.js +2 -1
  5. package/dist/helpers/create/index.js.map +1 -1
  6. package/dist/helpers/integrate/merge-config.d.ts.map +1 -1
  7. package/dist/helpers/integrate/merge-config.js +0 -2
  8. package/dist/helpers/integrate/merge-config.js.map +1 -1
  9. package/dist/helpers/integrate/sanity/config.d.ts.map +1 -1
  10. package/dist/helpers/integrate/sanity/config.js +3 -14
  11. package/dist/helpers/integrate/sanity/config.js.map +1 -1
  12. package/dist/index.js +84 -35
  13. package/dist/index.js.map +1 -1
  14. package/package.json +1 -1
  15. package/src/templates/next-pagebuilder/.env.example +11 -0
  16. package/src/templates/next-pagebuilder/README.md +23 -0
  17. package/src/templates/next-pagebuilder/_gitignore +67 -0
  18. package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +68 -0
  19. package/src/templates/next-pagebuilder/app/(content)/layout.tsx +13 -0
  20. package/src/templates/next-pagebuilder/app/api/[[...slug]]/route.ts +100 -0
  21. package/src/templates/next-pagebuilder/app/api/draft-mode/disable/route.ts +7 -0
  22. package/src/templates/next-pagebuilder/app/api/draft-mode/enable/route.ts +20 -0
  23. package/src/templates/next-pagebuilder/app/api/revalidate/route.ts +121 -0
  24. package/src/templates/next-pagebuilder/app/favicon.ico +0 -0
  25. package/src/templates/next-pagebuilder/app/layout.tsx +80 -0
  26. package/src/templates/next-pagebuilder/app/robots.ts +15 -0
  27. package/src/templates/next-pagebuilder/app/sitemap.md/route.ts +124 -0
  28. package/src/templates/next-pagebuilder/app/sitemap.xml/route.ts +80 -0
  29. package/src/templates/next-pagebuilder/app/studio/[[...tool]]/page.tsx +8 -0
  30. package/src/templates/next-pagebuilder/biome.json +239 -0
  31. package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +95 -0
  32. package/src/templates/next-pagebuilder/components/layout/header/components/cta-button.tsx +28 -0
  33. package/src/templates/next-pagebuilder/components/layout/header/components/mega-menu-panel.tsx +90 -0
  34. package/src/templates/next-pagebuilder/components/layout/header/components/nav-item-renderer.tsx +98 -0
  35. package/src/templates/next-pagebuilder/components/layout/header/components/nav-leaf-item.tsx +33 -0
  36. package/src/templates/next-pagebuilder/components/layout/header/components/types.ts +7 -0
  37. package/src/templates/next-pagebuilder/components/layout/header/header-client.tsx +110 -0
  38. package/src/templates/next-pagebuilder/components/layout/header/index.tsx +8 -0
  39. package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +45 -0
  40. package/src/templates/next-pagebuilder/components/layout/wrapper/index.tsx +30 -0
  41. package/src/templates/next-pagebuilder/components/page-builder/components/article-content/index.tsx +83 -0
  42. package/src/templates/next-pagebuilder/components/page-builder/components/article-content/related-post-item.tsx +27 -0
  43. package/src/templates/next-pagebuilder/components/page-builder/components/description.tsx +17 -0
  44. package/src/templates/next-pagebuilder/components/page-builder/components/hero.tsx +17 -0
  45. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/content-card.tsx +66 -0
  46. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/content-grid.tsx +42 -0
  47. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/index.tsx +28 -0
  48. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/types.ts +16 -0
  49. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +36 -0
  50. package/src/templates/next-pagebuilder/components/page-builder/types.ts +23 -0
  51. package/src/templates/next-pagebuilder/components/page-document/index.tsx +91 -0
  52. package/src/templates/next-pagebuilder/components/sanity/draft-mode-toggle.tsx +27 -0
  53. package/src/templates/next-pagebuilder/components/sanity/rich-text.tsx +87 -0
  54. package/src/templates/next-pagebuilder/components/sanity/visual-editing.tsx +27 -0
  55. package/src/templates/next-pagebuilder/components/ui/image/index.tsx +216 -0
  56. package/src/templates/next-pagebuilder/components/ui/link/index.tsx +152 -0
  57. package/src/templates/next-pagebuilder/components/ui/sanity-image/index.tsx +41 -0
  58. package/src/templates/next-pagebuilder/lib/integrations/check-integration.ts +5 -0
  59. package/src/templates/next-pagebuilder/lib/integrations/sanity/client.ts +27 -0
  60. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
  61. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/page-builder-input.tsx +36 -0
  62. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/page-category-input.tsx +50 -0
  63. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/rich-text.tsx +84 -0
  64. package/src/templates/next-pagebuilder/lib/integrations/sanity/confirm-publish-action.ts +40 -0
  65. package/src/templates/next-pagebuilder/lib/integrations/sanity/env.ts +34 -0
  66. package/src/templates/next-pagebuilder/lib/integrations/sanity/fetchers/layout.ts +35 -0
  67. package/src/templates/next-pagebuilder/lib/integrations/sanity/icons.ts +58 -0
  68. package/src/templates/next-pagebuilder/lib/integrations/sanity/live/index.tsx +61 -0
  69. package/src/templates/next-pagebuilder/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
  70. package/src/templates/next-pagebuilder/lib/integrations/sanity/page-builder-config.ts +132 -0
  71. package/src/templates/next-pagebuilder/lib/integrations/sanity/page-category.ts +28 -0
  72. package/src/templates/next-pagebuilder/lib/integrations/sanity/queries.ts +281 -0
  73. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.cli.ts +29 -0
  74. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.config.ts +211 -0
  75. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/index.ts +4 -0
  76. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
  77. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
  78. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
  79. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
  80. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/content/author.ts +70 -0
  81. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
  82. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/index.ts +96 -0
  83. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/company-data.ts +62 -0
  84. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
  85. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/navbar.ts +74 -0
  86. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/link.ts +125 -0
  87. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
  88. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/metadata.ts +68 -0
  89. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
  90. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page-builder.ts +39 -0
  91. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
  92. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page.ts +232 -0
  93. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
  94. package/src/templates/next-pagebuilder/lib/integrations/sanity/singletons.ts +44 -0
  95. package/src/templates/next-pagebuilder/lib/integrations/sanity/structure.ts +453 -0
  96. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/image.ts +8 -0
  97. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/link.ts +137 -0
  98. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/page-builder-markdown.ts +81 -0
  99. package/src/templates/next-pagebuilder/lib/scripts/sanity-typegen.ts +45 -0
  100. package/src/templates/next-pagebuilder/lib/styles/cn.ts +5 -0
  101. package/src/templates/next-pagebuilder/lib/styles/global.css +70 -0
  102. package/src/templates/next-pagebuilder/lib/utils/base-url.ts +17 -0
  103. package/src/templates/next-pagebuilder/lib/utils/format-date.ts +8 -0
  104. package/src/templates/next-pagebuilder/lib/utils/json-ld.tsx +213 -0
  105. package/src/templates/next-pagebuilder/lib/utils/metadata.ts +167 -0
  106. package/src/templates/next-pagebuilder/lib/utils/sitemap.ts +37 -0
  107. package/src/templates/next-pagebuilder/lib/utils/slug-tag.ts +6 -0
  108. package/src/templates/next-pagebuilder/next.config.ts +134 -0
  109. package/src/templates/next-pagebuilder/package.json +71 -0
  110. package/src/templates/next-pagebuilder/postcss.config.mjs +39 -0
  111. package/src/templates/next-pagebuilder/proxy.ts +81 -0
  112. package/src/templates/next-pagebuilder/svg.d.ts +5 -0
  113. package/src/templates/next-pagebuilder/tsconfig.json +38 -0
  114. package/src/helpers/integrate/sanity/files/lib/scripts/copy-sanity-mcp.ts +0 -23
  115. package/src/helpers/integrate/sanity/files/lib/scripts/generate-page.ts +0 -297
@@ -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
@@ -0,0 +1,239 @@
1
+ {
2
+ "root": true,
3
+ "$schema": "node_modules/@biomejs/biome/configuration_schema.json",
4
+ "assist": {
5
+ "actions": {
6
+ "source": {
7
+ "organizeImports": "on"
8
+ }
9
+ }
10
+ },
11
+ "css": {
12
+ "formatter": {
13
+ "enabled": true
14
+ },
15
+ "linter": {
16
+ "enabled": true
17
+ },
18
+ "parser": {
19
+ "cssModules": true,
20
+ "tailwindDirectives": true
21
+ }
22
+ },
23
+ "files": {
24
+ "ignoreUnknown": true,
25
+ "includes": [
26
+ "**",
27
+ "!node_modules",
28
+ "!**/.next",
29
+ "!**/dist",
30
+ "!**/public",
31
+ "!.github",
32
+ "!.vercel",
33
+ "!pnpm-lock.yaml",
34
+ "!bun.lock",
35
+ "!**/*.md",
36
+ "!**/*.mdx",
37
+ "!**/tailwind.css",
38
+ "!**/*.grit"
39
+ ]
40
+ },
41
+ "formatter": {
42
+ "enabled": true,
43
+ "indentStyle": "space",
44
+ "indentWidth": 2,
45
+ "lineEnding": "lf",
46
+ "lineWidth": 80
47
+ },
48
+ "javascript": {
49
+ "formatter": {
50
+ "enabled": true,
51
+ "quoteStyle": "double",
52
+ "semicolons": "asNeeded",
53
+ "trailingCommas": "es5"
54
+ }
55
+ },
56
+ "json": {
57
+ "parser": {
58
+ "allowComments": true
59
+ }
60
+ },
61
+ "linter": {
62
+ "domains": {
63
+ "next": "recommended",
64
+ "react": "recommended"
65
+ },
66
+ "enabled": true,
67
+ "rules": {
68
+ "a11y": {
69
+ "noAriaUnsupportedElements": "error",
70
+ "noAutofocus": "warn",
71
+ "noDistractingElements": "error",
72
+ "noRedundantAlt": "error",
73
+ "useAltText": "error",
74
+ "useButtonType": "error",
75
+ "useKeyWithClickEvents": "warn",
76
+ "useSemanticElements": "warn",
77
+ "useValidAnchor": "warn",
78
+ "useValidAriaProps": "error",
79
+ "useValidAriaRole": "error",
80
+ "useValidAriaValues": "error"
81
+ },
82
+ "complexity": {
83
+ "noForEach": "off",
84
+ "useFlatMap": "warn",
85
+ "useSimplifiedLogicExpression": "warn"
86
+ },
87
+ "correctness": {
88
+ "noInvalidUseBeforeDeclaration": "error",
89
+ "noUnknownMediaFeatureName": "off",
90
+ "noUnusedFunctionParameters": "warn",
91
+ "noUnusedImports": "error",
92
+ "noUnusedVariables": "error",
93
+ "useExhaustiveDependencies": "warn"
94
+ },
95
+ "nursery": {
96
+ "useSortedClasses": {
97
+ "level": "warn",
98
+ "options": {
99
+ "attributes": ["class", "className"],
100
+ "functions": ["cn", "cva"]
101
+ }
102
+ }
103
+ },
104
+ "performance": {
105
+ "noImgElement": "error"
106
+ },
107
+ "recommended": true,
108
+ "security": {
109
+ "noDangerouslySetInnerHtml": "warn",
110
+ "noDangerouslySetInnerHtmlWithChildren": "error",
111
+ "noGlobalEval": "error"
112
+ },
113
+ "style": {
114
+ "noInferrableTypes": "error",
115
+ "noNestedTernary": "error",
116
+ "noNonNullAssertion": "off",
117
+ "noParameterAssign": "error",
118
+ "noUnusedTemplateLiteral": "off",
119
+ "noUselessElse": "error",
120
+ "useAsConstAssertion": "error",
121
+ "useCollapsedElseIf": "warn",
122
+ "useConsistentArrayType": "error",
123
+ "useConsistentBuiltinInstantiation": "error",
124
+ "useDefaultParameterLast": "error",
125
+ "useEnumInitializers": "error",
126
+ "useExponentiationOperator": "error",
127
+ "useFilenamingConvention": {
128
+ "level": "warn",
129
+ "options": {
130
+ "filenameCases": ["kebab-case", "camelCase"],
131
+ "strictCase": false
132
+ }
133
+ },
134
+ "useForOf": "warn",
135
+ "useNumberNamespace": "error",
136
+ "useSelfClosingElements": "error",
137
+ "useShorthandAssign": "error",
138
+ "useSingleVarDeclarator": "error",
139
+ "useTemplate": "warn"
140
+ },
141
+ "suspicious": {
142
+ "noDebugger": "warn",
143
+ "noDoubleEquals": "error",
144
+ "noEmptyBlockStatements": "warn",
145
+ "noExplicitAny": "error",
146
+ "noGlobalIsFinite": "error",
147
+ "noGlobalIsNan": "error",
148
+ "noMisleadingCharacterClass": "error",
149
+ "noPrototypeBuiltins": "warn",
150
+ "noSelfCompare": "error",
151
+ "noSparseArray": "error",
152
+ "useAwait": "off"
153
+ }
154
+ }
155
+ },
156
+ "overrides": [
157
+ {
158
+ "includes": ["**/*.css"],
159
+ "linter": {
160
+ "rules": {
161
+ "correctness": {
162
+ "noUnknownFunction": "off"
163
+ }
164
+ }
165
+ }
166
+ },
167
+ {
168
+ "includes": ["**/*.tsx", "**/*.jsx"],
169
+ "linter": {
170
+ "rules": {
171
+ "a11y": {
172
+ "useKeyWithClickEvents": "error",
173
+ "useKeyWithMouseEvents": "error",
174
+ "useValidAnchor": "error"
175
+ },
176
+ "correctness": {
177
+ "useJsxKeyInIterable": "error"
178
+ }
179
+ }
180
+ }
181
+ },
182
+ {
183
+ "includes": ["**/*.ts", "**/*.tsx"],
184
+ "linter": {
185
+ "rules": {
186
+ "correctness": {
187
+ "noUndeclaredVariables": "off"
188
+ },
189
+ "style": {
190
+ "useConsistentArrayType": "error",
191
+ "useExportType": "error",
192
+ "useImportType": "error"
193
+ }
194
+ }
195
+ }
196
+ },
197
+ {
198
+ "includes": [
199
+ "app/**/*.tsx",
200
+ "app/**/*.ts",
201
+ "app/**/*.jsx",
202
+ "app/**/*.js"
203
+ ],
204
+ "linter": {
205
+ "rules": {
206
+ "style": {
207
+ "noDefaultExport": "off"
208
+ },
209
+ "suspicious": {
210
+ "useAwait": "off"
211
+ }
212
+ }
213
+ }
214
+ },
215
+ {
216
+ "includes": ["**/*.module.css"],
217
+ "linter": {
218
+ "rules": {
219
+ "correctness": {
220
+ "noUnknownProperty": "off"
221
+ },
222
+ "style": {
223
+ "noDescendingSpecificity": "off"
224
+ }
225
+ }
226
+ }
227
+ }
228
+ ],
229
+ "plugins": [
230
+ "./.biome/plugins/no-anchor-element.grit",
231
+ "./.biome/plugins/no-unnecessary-forwardref.grit",
232
+ "./.biome/plugins/no-relative-parent-imports.grit"
233
+ ],
234
+ "vcs": {
235
+ "clientKind": "git",
236
+ "enabled": true,
237
+ "useIgnoreFile": false
238
+ }
239
+ }