bsmnt 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/dist/application/add-integration/index.d.ts +24 -6
  2. package/dist/application/add-integration/index.d.ts.map +1 -1
  3. package/dist/application/add-integration/index.js +130 -82
  4. package/dist/application/add-integration/index.js.map +1 -1
  5. package/dist/application/create-project/map-selection-to-registry.js +1 -1
  6. package/dist/application/create-project/map-selection-to-registry.js.map +1 -1
  7. package/dist/application/shared/project-detection.d.ts +2 -2
  8. package/dist/application/shared/project-detection.d.ts.map +1 -1
  9. package/dist/application/shared/project-detection.js +2 -2
  10. package/dist/application/shared/project-detection.js.map +1 -1
  11. package/dist/core/create/internal-ids.d.ts +4 -1
  12. package/dist/core/create/internal-ids.d.ts.map +1 -1
  13. package/dist/core/create/internal-ids.js +6 -1
  14. package/dist/core/create/internal-ids.js.map +1 -1
  15. package/dist/domain/cms.d.ts +0 -7
  16. package/dist/domain/cms.d.ts.map +1 -1
  17. package/dist/domain/cms.js.map +1 -1
  18. package/dist/domain/forms.d.ts +17 -0
  19. package/dist/domain/forms.d.ts.map +1 -0
  20. package/dist/domain/forms.js +13 -0
  21. package/dist/domain/forms.js.map +1 -0
  22. package/dist/domain/integration.d.ts +30 -0
  23. package/dist/domain/integration.d.ts.map +1 -0
  24. package/dist/domain/integration.js +12 -0
  25. package/dist/domain/integration.js.map +1 -0
  26. package/dist/domain/skills.d.ts +32 -0
  27. package/dist/domain/skills.d.ts.map +1 -1
  28. package/dist/domain/skills.js +39 -0
  29. package/dist/domain/skills.js.map +1 -1
  30. package/dist/index.js +95 -37
  31. package/dist/index.js.map +1 -1
  32. package/dist/infrastructure/agents/register-sanity-mcp.js +9 -6
  33. package/dist/infrastructure/agents/register-sanity-mcp.js.map +1 -1
  34. package/dist/infrastructure/create/apply-package-json.d.ts.map +1 -1
  35. package/dist/infrastructure/create/apply-package-json.js +19 -0
  36. package/dist/infrastructure/create/apply-package-json.js.map +1 -1
  37. package/dist/infrastructure/create/install-dependencies.d.ts.map +1 -1
  38. package/dist/infrastructure/create/install-dependencies.js +4 -0
  39. package/dist/infrastructure/create/install-dependencies.js.map +1 -1
  40. package/dist/infrastructure/create/merge-registry.d.ts.map +1 -1
  41. package/dist/infrastructure/create/merge-registry.js +8 -0
  42. package/dist/infrastructure/create/merge-registry.js.map +1 -1
  43. package/dist/infrastructure/create/setup-agent.d.ts.map +1 -1
  44. package/dist/infrastructure/create/setup-agent.js +29 -6
  45. package/dist/infrastructure/create/setup-agent.js.map +1 -1
  46. package/dist/infrastructure/create/setup-sanity.d.ts.map +1 -1
  47. package/dist/infrastructure/create/setup-sanity.js +3 -0
  48. package/dist/infrastructure/create/setup-sanity.js.map +1 -1
  49. package/dist/infrastructure/integrations/inject-integration.d.ts +3 -3
  50. package/dist/infrastructure/integrations/inject-integration.d.ts.map +1 -1
  51. package/dist/infrastructure/integrations/inject-integration.js +81 -37
  52. package/dist/infrastructure/integrations/inject-integration.js.map +1 -1
  53. package/dist/modules/features/cms/sanity/config.d.ts.map +1 -1
  54. package/dist/modules/features/cms/sanity/config.js +11 -5
  55. package/dist/modules/features/cms/sanity/config.js.map +1 -1
  56. package/dist/modules/features/cms/sanity/index.js +8 -2
  57. package/dist/modules/features/cms/sanity/index.js.map +1 -1
  58. package/dist/modules/features/cms/sanity/mergers/ci-workflow.d.ts +13 -0
  59. package/dist/modules/features/cms/sanity/mergers/ci-workflow.d.ts.map +1 -0
  60. package/dist/modules/features/cms/sanity/mergers/ci-workflow.js +38 -0
  61. package/dist/modules/features/cms/sanity/mergers/ci-workflow.js.map +1 -0
  62. package/dist/modules/features/cms/sanity/mergers/knip.d.ts +12 -0
  63. package/dist/modules/features/cms/sanity/mergers/knip.d.ts.map +1 -0
  64. package/dist/modules/features/cms/sanity/mergers/knip.js +38 -0
  65. package/dist/modules/features/cms/sanity/mergers/knip.js.map +1 -0
  66. package/dist/modules/features/cms/sanity/mergers/layout.d.ts.map +1 -1
  67. package/dist/modules/features/cms/sanity/mergers/layout.js +7 -3
  68. package/dist/modules/features/cms/sanity/mergers/layout.js.map +1 -1
  69. package/dist/modules/features/cms/sanity/mergers/sitemap.d.ts +3 -3
  70. package/dist/modules/features/cms/sanity/mergers/sitemap.d.ts.map +1 -1
  71. package/dist/modules/features/cms/sanity/mergers/sitemap.js +48 -62
  72. package/dist/modules/features/cms/sanity/mergers/sitemap.js.map +1 -1
  73. package/dist/modules/features/cms/sanity-pagebuilder/config.d.ts.map +1 -1
  74. package/dist/modules/features/cms/sanity-pagebuilder/config.js +8 -5
  75. package/dist/modules/features/cms/sanity-pagebuilder/config.js.map +1 -1
  76. package/dist/modules/features/cms/sanity-pagebuilder/index.js +2 -2
  77. package/dist/modules/features/cms/sanity-pagebuilder/index.js.map +1 -1
  78. package/dist/modules/features/forms/hubspot/config.d.ts +31 -0
  79. package/dist/modules/features/forms/hubspot/config.d.ts.map +1 -0
  80. package/dist/modules/features/forms/hubspot/config.js +25 -0
  81. package/dist/modules/features/forms/hubspot/config.js.map +1 -0
  82. package/dist/paths.d.ts +8 -0
  83. package/dist/paths.d.ts.map +1 -1
  84. package/dist/paths.js +8 -0
  85. package/dist/paths.js.map +1 -1
  86. package/index.js +2 -2
  87. package/package.json +17 -9
  88. package/src/assets/check-node-version.mjs +22 -0
  89. package/src/modules/features/cms/sanity/files/app/blog/page.tsx +54 -0
  90. package/src/modules/features/cms/sanity/files/app/feed.xml/route.ts +36 -0
  91. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/components/disable-draft-mode.tsx +3 -3
  92. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/schemas/article.ts +7 -7
  93. package/src/modules/features/cms/sanity/files/lib/scripts/setup-ci-secrets.ts +117 -0
  94. package/src/modules/features/cms/sanity/files/lib/utils/rss.ts +96 -0
  95. package/src/modules/features/cms/sanity-pagebuilder/files/.knip.json +8 -0
  96. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/[[...slug]]/route.ts +100 -0
  97. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/draft-mode/disable/route.ts +7 -0
  98. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/draft-mode/enable/route.ts +20 -0
  99. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/revalidate/route.ts +126 -0
  100. package/src/modules/features/cms/sanity-pagebuilder/files/app/feed.xml/route.ts +40 -0
  101. package/src/modules/features/cms/sanity-pagebuilder/files/app/sitemap.md/route.ts +124 -0
  102. package/src/modules/features/cms/sanity-pagebuilder/files/app/studio/[[...tool]]/page.tsx +8 -0
  103. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/draft-mode-toggle.tsx +27 -0
  104. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +87 -0
  105. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/visual-editing.tsx +28 -0
  106. package/src/modules/features/cms/sanity-pagebuilder/files/components/ui/sanity-image/index.tsx +41 -0
  107. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/check-integration.ts +5 -0
  108. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/client.ts +27 -0
  109. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
  110. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-input.tsx +39 -0
  111. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-category-input.tsx +50 -0
  112. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-field.tsx +18 -0
  113. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-input.tsx +14 -0
  114. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/confirm-publish-action.ts +40 -0
  115. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/env.ts +34 -0
  116. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/layout.ts +48 -0
  117. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/redirects.ts +17 -0
  118. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +62 -0
  119. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/live/index.tsx +88 -0
  120. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
  121. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +130 -0
  122. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-category.ts +28 -0
  123. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-tree-pane.tsx +420 -0
  124. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/pinned-pages.ts +54 -0
  125. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/presentation.ts +119 -0
  126. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +385 -0
  127. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/sanity.cli.ts +29 -0
  128. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/sanity.config.ts +113 -0
  129. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +4 -0
  130. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
  131. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
  132. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
  133. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
  134. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/content/author.ts +70 -0
  135. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
  136. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +96 -0
  137. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/company-data.ts +62 -0
  138. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
  139. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/navbar.ts +75 -0
  140. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/redirect.ts +120 -0
  141. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/link.ts +125 -0
  142. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
  143. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/metadata.ts +68 -0
  144. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
  145. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page-builder.ts +39 -0
  146. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
  147. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +242 -0
  148. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
  149. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/singletons.ts +39 -0
  150. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +330 -0
  151. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/image.ts +8 -0
  152. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/link.ts +140 -0
  153. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/page-builder-markdown.ts +81 -0
  154. package/src/modules/features/cms/sanity-pagebuilder/files/lib/scripts/sanity-typegen.ts +45 -0
  155. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/check-redirect.ts +30 -0
  156. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/json-ld.tsx +212 -0
  157. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/metadata.ts +167 -0
  158. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/rss.ts +96 -0
  159. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/sitemap.ts +40 -0
  160. package/src/modules/features/cms/sanity-pagebuilder/files/proxy.ts +81 -0
  161. package/src/templates/next-default/.github/workflows/ci.yml +62 -0
  162. package/src/templates/next-default/README.md +22 -0
  163. package/src/templates/next-default/knip.json +14 -0
  164. package/src/templates/next-default/lefthook.yml +5 -0
  165. package/src/templates/next-default/package.json +8 -4
  166. package/src/templates/next-experiments/.github/workflows/ci.yml +62 -0
  167. package/src/templates/next-experiments/README.md +22 -0
  168. package/src/templates/next-experiments/knip.json +14 -0
  169. package/src/templates/next-experiments/lefthook.yml +30 -0
  170. package/src/templates/next-experiments/package.json +10 -2
  171. package/src/templates/next-pagebuilder/.github/workflows/ci.yml +70 -0
  172. package/src/templates/next-pagebuilder/README.md +34 -0
  173. package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +6 -1
  174. package/src/templates/next-pagebuilder/app/(content)/layout.tsx +3 -12
  175. package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +11 -10
  176. package/src/templates/next-pagebuilder/components/layout/header/index.tsx +5 -3
  177. package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +9 -10
  178. package/src/templates/next-pagebuilder/components/layout/wrapper/index.tsx +3 -11
  179. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/index.tsx +1 -1
  180. package/src/templates/next-pagebuilder/knip.json +27 -0
  181. package/src/templates/next-pagebuilder/lefthook.yml +5 -0
  182. package/src/templates/next-pagebuilder/lib/scripts/setup-ci-secrets.ts +117 -0
  183. package/src/templates/next-pagebuilder/package.json +9 -3
  184. package/src/templates/next-webgl/.github/workflows/ci.yml +62 -0
  185. package/src/templates/next-webgl/README.md +22 -0
  186. package/src/templates/next-webgl/knip.json +13 -0
  187. package/src/templates/next-webgl/lefthook.yml +5 -0
  188. package/src/templates/next-webgl/package.json +8 -4
@@ -0,0 +1,96 @@
1
+ export interface RssFeedItem {
2
+ title: string;
3
+ link: string;
4
+ description?: string | undefined;
5
+ pubDate?: string | undefined;
6
+ authors?: string[] | undefined;
7
+ category?: string | undefined;
8
+ }
9
+
10
+ export interface RssFeedConfig {
11
+ title: string;
12
+ link: string;
13
+ description: string;
14
+ feedUrl: string;
15
+ language?: string;
16
+ items: RssFeedItem[];
17
+ }
18
+
19
+ function escapeXml(str: string): string {
20
+ return str
21
+ .replace(/&/g, "&")
22
+ .replace(/</g, "&lt;")
23
+ .replace(/>/g, "&gt;")
24
+ .replace(/"/g, "&quot;")
25
+ .replace(/'/g, "&apos;");
26
+ }
27
+
28
+ export function generateRssFeed(config: RssFeedConfig): Response {
29
+ const {
30
+ title,
31
+ link,
32
+ description,
33
+ feedUrl,
34
+ language = "en-us",
35
+ items,
36
+ } = config;
37
+
38
+ const itemsXml = items
39
+ .map((item) => {
40
+ const lines = [
41
+ ` <title>${escapeXml(item.title)}</title>`,
42
+ ` <link>${escapeXml(item.link)}</link>`,
43
+ ` <guid isPermaLink="true">${escapeXml(item.link)}</guid>`,
44
+ ];
45
+
46
+ if (item.description) {
47
+ lines.push(
48
+ ` <description>${escapeXml(item.description)}</description>`,
49
+ );
50
+ }
51
+
52
+ const pubDate = item.pubDate ? new Date(item.pubDate) : null;
53
+ if (pubDate && !Number.isNaN(pubDate.getTime())) {
54
+ lines.push(` <pubDate>${pubDate.toUTCString()}</pubDate>`);
55
+ }
56
+
57
+ for (const author of item.authors ?? []) {
58
+ lines.push(` <dc:creator>${escapeXml(author)}</dc:creator>`);
59
+ }
60
+
61
+ if (item.category) {
62
+ lines.push(` <category>${escapeXml(item.category)}</category>`);
63
+ }
64
+
65
+ return ` <item>\n${lines.join("\n")}\n </item>`;
66
+ })
67
+ .join("\n");
68
+
69
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
70
+ <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">
71
+ <channel>
72
+ <title>${escapeXml(title)}</title>
73
+ <link>${escapeXml(link)}</link>
74
+ <description>${escapeXml(description)}</description>
75
+ <language>${language}</language>
76
+ <atom:link href="${escapeXml(feedUrl)}" rel="self" type="application/rss+xml" />
77
+ ${itemsXml}
78
+ </channel>
79
+ </rss>`;
80
+
81
+ return new Response(xml, {
82
+ headers: {
83
+ "Content-Type": "application/rss+xml",
84
+ },
85
+ });
86
+ }
87
+
88
+ export function rssErrorResponse(): Response {
89
+ return new Response(
90
+ '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>Error</title></channel></rss>',
91
+ {
92
+ status: 500,
93
+ headers: { "Content-Type": "application/xml" },
94
+ },
95
+ );
96
+ }
@@ -0,0 +1,40 @@
1
+ import { PINNED_PAGE_ENTRIES } from "@/lib/integrations/sanity/pinned-pages";
2
+ import { PAGE_SITEMAP_QUERY } from "@/lib/integrations/sanity/queries";
3
+ import type { PAGE_SITEMAP_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types";
4
+ import { getBaseUrl } from "./base-url";
5
+
6
+ export const getSitemapBaseUrl = getBaseUrl;
7
+
8
+ const getNormalizedPageSlug = (slug: string | null) => {
9
+ if (!slug || slug === "/") return "";
10
+ return slug.replace(/^\/+|\/+$/g, "");
11
+ };
12
+
13
+ export const getPagePath = (slug: string | null) => {
14
+ const normalizedSlug = getNormalizedPageSlug(slug);
15
+ return normalizedSlug ? `/${normalizedSlug}` : "/";
16
+ };
17
+
18
+ export const getPageUrl = (baseUrl: string, slug: string | null) =>
19
+ new URL(getPagePath(slug), baseUrl).toString();
20
+
21
+ export const getMarkdownPath = (baseUrl: string, slug: string | null) => {
22
+ const normalizedSlug = getNormalizedPageSlug(slug);
23
+
24
+ return normalizedSlug
25
+ ? `${baseUrl}/${normalizedSlug}.md`
26
+ : `${baseUrl}/index.md`;
27
+ };
28
+
29
+ export const getSitemap = async () => {
30
+ const sanityModule = await import("@/lib/integrations/sanity/client");
31
+ const client = sanityModule?.client;
32
+
33
+ if (!client) return null;
34
+
35
+ const p = await client.fetch<PAGE_SITEMAP_QUERY_RESULT>(PAGE_SITEMAP_QUERY, {
36
+ pinnedIds: PINNED_PAGE_ENTRIES.map((entry) => entry.id),
37
+ });
38
+
39
+ return p;
40
+ };
@@ -0,0 +1,81 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { NextResponse } from "next/server";
3
+ import {
4
+ acceptHeaderRoutes,
5
+ ignoredPaths,
6
+ mdExtensionRoutes,
7
+ } from "@/lib/integrations/sanity/markdown-proxy.config";
8
+
9
+ export function proxy(request: NextRequest) {
10
+ const { pathname } = request.nextUrl;
11
+ const acceptHeader = request.headers.get("accept") || "";
12
+
13
+ if (ignoredPaths.some((p) => pathname.startsWith(p))) {
14
+ return NextResponse.next();
15
+ }
16
+
17
+ if (pathname === "/" && acceptHeader.includes("text/markdown")) {
18
+ const url = request.nextUrl.clone();
19
+ url.pathname = "/api/index.md";
20
+ return NextResponse.rewrite(url);
21
+ }
22
+
23
+ // 1. Rewrite .md extension URLs to API routes
24
+ // e.g. /about.md → /api/about.md
25
+ for (const route of mdExtensionRoutes) {
26
+ const match = route.regex.exec(pathname);
27
+ const slug = match?.[1];
28
+ if (slug) {
29
+ const url = request.nextUrl.clone();
30
+ url.pathname = route.apiPath.replace("[slug]", slug);
31
+ return NextResponse.rewrite(url);
32
+ }
33
+ }
34
+
35
+ // 2. Accept: text/markdown — rewrite to markdown API route
36
+ if (acceptHeader.includes("text/markdown")) {
37
+ for (const route of acceptHeaderRoutes) {
38
+ const match = route.regex.exec(pathname);
39
+ const slug = match?.[1];
40
+ if (slug) {
41
+ const url = request.nextUrl.clone();
42
+ url.pathname = route.apiPath.replace("[slug]", slug);
43
+ return NextResponse.rewrite(url);
44
+ }
45
+ }
46
+ }
47
+
48
+ // 3. Add Link header for markdown discoverability on HTML page requests
49
+ const response = NextResponse.next();
50
+
51
+ if (pathname === "/") {
52
+ response.headers.set(
53
+ "Link",
54
+ '</index.md>; rel="alternate"; type="text/markdown"',
55
+ );
56
+ response.headers.set("Vary", "Accept");
57
+ return response;
58
+ }
59
+
60
+ for (const route of acceptHeaderRoutes) {
61
+ const match = route.regex.exec(pathname);
62
+ const slug = match?.[1];
63
+ if (slug) {
64
+ const mdUrl = route.publicPath.replace("[slug]", slug);
65
+ response.headers.set(
66
+ "Link",
67
+ `<${mdUrl}>; rel="alternate"; type="text/markdown"`,
68
+ );
69
+ response.headers.set("Vary", "Accept");
70
+ break;
71
+ }
72
+ }
73
+
74
+ return response;
75
+ }
76
+
77
+ export const config = {
78
+ matcher: [
79
+ "/((?!_next/static|_next/image|favicon.ico|sitemap.xml|sitemap.md|robots.txt).*)",
80
+ ],
81
+ };
@@ -0,0 +1,62 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - main
7
+ push:
8
+ branches:
9
+ - main
10
+
11
+ jobs:
12
+ ci:
13
+ runs-on: ubuntu-latest
14
+
15
+ steps:
16
+ - name: Checkout
17
+ uses: actions/checkout@v4
18
+
19
+ # Node version is read from .nvmrc — the single source of truth.
20
+ - name: Setup Node
21
+ uses: actions/setup-node@v4
22
+ with:
23
+ node-version-file: ".nvmrc"
24
+
25
+ # Bun version is read from package.json's "packageManager" field — the
26
+ # single source of truth. Do NOT pin a version here, or the two can drift.
27
+ - name: Setup Bun
28
+ uses: oven-sh/setup-bun@v2
29
+
30
+ # Cache Bun's global install cache so dependencies aren't re-downloaded
31
+ # on every run. Keyed on the lockfile so it busts when deps change.
32
+ - name: Cache Bun dependencies
33
+ uses: actions/cache@v4
34
+ with:
35
+ path: ~/.bun/install/cache
36
+ key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
37
+ restore-keys: |
38
+ ${{ runner.os }}-bun-
39
+
40
+ - name: Install dependencies
41
+ run: bun install --frozen-lockfile
42
+
43
+ - name: Type check
44
+ run: bun run typecheck:compare
45
+
46
+ - name: Lint
47
+ run: bun run lint
48
+
49
+ - name: Build
50
+ run: bun run build
51
+
52
+ # Runs your tests if any exist; a fresh project with none passes cleanly.
53
+ - name: Test
54
+ run: |
55
+ if git ls-files '*.test.*' '*.spec.*' '*_test.*' '*_spec.*' | grep -q .; then
56
+ bun test
57
+ else
58
+ echo "No test files found — skipping bun test."
59
+ fi
60
+
61
+ - name: Knip
62
+ run: bun run knip
@@ -48,3 +48,25 @@ lib/
48
48
  - Edit `lib/styles/tokens.css` for theme variables, breakpoints, and custom Tailwind utilities.
49
49
  - Edit `lib/styles/global.css` for reset rules and app-wide global styles.
50
50
  - `lib/styles/fonts.ts` handles font variables.
51
+
52
+ ## Continuous Integration
53
+
54
+ A GitHub Actions workflow (`.github/workflows/ci.yml`) runs on every pull request to `main` and on pushes to `main`. It runs type checking, linting, the production build, tests, and `knip`.
55
+
56
+ The workflow takes its runtime versions from your manifests, so there's a single source of truth and nothing to keep in sync by hand:
57
+
58
+ - **Node** comes from `.nvmrc`.
59
+ - **Bun** comes from the `packageManager` field in `package.json`.
60
+
61
+ It also caches Bun's install cache between runs, and the test step is a no-op until you add real test files.
62
+
63
+ ### Branch protection
64
+
65
+ To block merges when CI fails, enable branch protection on `main`:
66
+
67
+ 1. Go to **Settings → Branches → Add branch ruleset** (or **Add rule**).
68
+ 2. Target the `main` branch.
69
+ 3. Enable **Require status checks to pass before merging** and select the **CI** check.
70
+ 4. (Recommended) Enable **Require a pull request before merging**.
71
+
72
+ Pull requests then cannot be merged until the CI workflow passes.
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://unpkg.com/knip@6/schema.json",
3
+ "entry": [
4
+ "lib/scripts/**/*.{ts,tsx}",
5
+ "lib/hooks/**/*.{ts,tsx}",
6
+ "lib/utils/**/*.{ts,tsx}",
7
+ "lib/renderer.ts",
8
+ "components/**/*.{ts,tsx}"
9
+ ],
10
+ "ignoreDependencies": [
11
+ "zod",
12
+ "@svgr/webpack"
13
+ ]
14
+ }
@@ -14,6 +14,11 @@ pre-commit:
14
14
  run: bunx tsgo --noEmit --incremental false
15
15
  stage_fixed: false
16
16
 
17
+ pre-push:
18
+ commands:
19
+ knip:
20
+ run: bun run knip
21
+
17
22
  post-merge:
18
23
  commands:
19
24
  install-deps:
@@ -3,7 +3,10 @@
3
3
  "description": "Basement Next.js starter template",
4
4
  "version": "0.1.0",
5
5
  "private": true,
6
- "packageManager": "bun@1.3.5",
6
+ "packageManager": "bun@1.3.14",
7
+ "engines": {
8
+ "node": ">=24"
9
+ },
7
10
  "scripts": {
8
11
  "analyze": "cross-env ANALYZE=true bun run build",
9
12
  "analyze:experimental": "next experimental-analyze",
@@ -12,8 +15,8 @@
12
15
  "dev:https": "bun ./lib/scripts/dev.ts --https",
13
16
  "dev:inspect": "bun ./lib/scripts/dev.ts --inspect",
14
17
  "format": "biome format --write .",
15
- "knip": "bunx knip",
16
- "knip:fix": "bunx knip --fix",
18
+ "knip": "knip",
19
+ "knip:fix": "knip --fix",
17
20
  "lighthouse": "bunx @unlighthouse/cli --site http://localhost:3000",
18
21
  "lint": "biome lint --max-diagnostics=200",
19
22
  "lint:fix": "biome check --write --unsafe --max-diagnostics=200",
@@ -39,12 +42,13 @@
39
42
  "@svgr/webpack": "^8.1.0",
40
43
  "@tailwindcss/postcss": "^4",
41
44
  "@types/bun": "^1.3.6",
42
- "@types/node": "^20.19.30",
45
+ "@types/node": "^24.13.1",
43
46
  "@types/react": "^19.2.9",
44
47
  "@types/react-dom": "^19.2.3",
45
48
  "@typescript/native-preview": "^7.0.0-dev.20260122.3",
46
49
  "babel-plugin-react-compiler": "^1.0.0",
47
50
  "cross-env": "^10.1.0",
51
+ "knip": "^6.16.1",
48
52
  "lefthook": "^1.13.0",
49
53
  "postcss-preset-env": "^10.6.1",
50
54
  "schema-dts": "^2.0.0",
@@ -0,0 +1,62 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - main
7
+ push:
8
+ branches:
9
+ - main
10
+
11
+ jobs:
12
+ ci:
13
+ runs-on: ubuntu-latest
14
+
15
+ steps:
16
+ - name: Checkout
17
+ uses: actions/checkout@v4
18
+
19
+ # Node version is read from .nvmrc — the single source of truth.
20
+ - name: Setup Node
21
+ uses: actions/setup-node@v4
22
+ with:
23
+ node-version-file: ".nvmrc"
24
+
25
+ # Bun version is read from package.json's "packageManager" field — the
26
+ # single source of truth. Do NOT pin a version here, or the two can drift.
27
+ - name: Setup Bun
28
+ uses: oven-sh/setup-bun@v2
29
+
30
+ # Cache Bun's global install cache so dependencies aren't re-downloaded
31
+ # on every run. Keyed on the lockfile so it busts when deps change.
32
+ - name: Cache Bun dependencies
33
+ uses: actions/cache@v4
34
+ with:
35
+ path: ~/.bun/install/cache
36
+ key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
37
+ restore-keys: |
38
+ ${{ runner.os }}-bun-
39
+
40
+ - name: Install dependencies
41
+ run: bun install --frozen-lockfile
42
+
43
+ - name: Type check
44
+ run: bun run typecheck:compare
45
+
46
+ - name: Lint
47
+ run: bun run lint
48
+
49
+ - name: Build
50
+ run: bun run build
51
+
52
+ # Runs your tests if any exist; a fresh project with none passes cleanly.
53
+ - name: Test
54
+ run: |
55
+ if git ls-files '*.test.*' '*.spec.*' '*_test.*' '*_spec.*' | grep -q .; then
56
+ bun test
57
+ else
58
+ echo "No test files found — skipping bun test."
59
+ fi
60
+
61
+ - name: Knip
62
+ run: bun run knip
@@ -48,3 +48,25 @@ lib/
48
48
  - Edit `lib/styles/tokens.css` for theme variables, breakpoints, and custom Tailwind utilities.
49
49
  - Edit `lib/styles/global.css` for reset rules and app-wide global styles.
50
50
  - `lib/styles/fonts.ts` handles font variables.
51
+
52
+ ## Continuous Integration
53
+
54
+ A GitHub Actions workflow (`.github/workflows/ci.yml`) runs on every pull request to `main` and on pushes to `main`. It runs type checking, linting, the production build, tests, and `knip`.
55
+
56
+ The workflow takes its runtime versions from your manifests, so there's a single source of truth and nothing to keep in sync by hand:
57
+
58
+ - **Node** comes from `.nvmrc`.
59
+ - **Bun** comes from the `packageManager` field in `package.json`.
60
+
61
+ It also caches Bun's install cache between runs, and the test step is a no-op until you add real test files.
62
+
63
+ ### Branch protection
64
+
65
+ To block merges when CI fails, enable branch protection on `main`:
66
+
67
+ 1. Go to **Settings → Branches → Add branch ruleset** (or **Add rule**).
68
+ 2. Target the `main` branch.
69
+ 3. Enable **Require status checks to pass before merging** and select the **CI** check.
70
+ 4. (Recommended) Enable **Require a pull request before merging**.
71
+
72
+ Pull requests then cannot be merged until the CI workflow passes.
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://unpkg.com/knip@6/schema.json",
3
+ "entry": [
4
+ "lib/scripts/**/*.{ts,tsx}",
5
+ "lib/hooks/**/*.{ts,tsx}",
6
+ "lib/utils/**/*.{ts,tsx}",
7
+ "components/**/*.{ts,tsx}"
8
+ ],
9
+ "ignoreDependencies": [
10
+ "zod",
11
+ "three",
12
+ "@svgr/webpack"
13
+ ]
14
+ }
@@ -0,0 +1,30 @@
1
+ # Lefthook configuration for git hooks
2
+ # Cross-platform compatible (Windows, macOS, Linux)
3
+ # https://github.com/evilmartians/lefthook
4
+
5
+ pre-commit:
6
+ parallel: true
7
+ commands:
8
+ biome:
9
+ glob: '*.{js,mjs,ts,jsx,tsx,css,scss}'
10
+ run: bun biome check --max-diagnostics=200 --write --unsafe {staged_files}
11
+ stage_fixed: true
12
+ typecheck:
13
+ glob: '*.{ts,tsx}'
14
+ run: bunx tsgo --noEmit --incremental false
15
+ stage_fixed: false
16
+
17
+ pre-push:
18
+ commands:
19
+ knip:
20
+ run: bun run knip
21
+
22
+ post-merge:
23
+ commands:
24
+ install-deps:
25
+ run: bun i
26
+
27
+ post-checkout:
28
+ commands:
29
+ install-deps:
30
+ run: '[ "{3}" = "1" ] && bun i || true'
@@ -3,7 +3,10 @@
3
3
  "description": "Basement Next.js starter template",
4
4
  "version": "0.1.0",
5
5
  "private": true,
6
- "packageManager": "bun@1.3.5",
6
+ "packageManager": "bun@1.3.14",
7
+ "engines": {
8
+ "node": ">=24"
9
+ },
7
10
  "scripts": {
8
11
  "analyze": "cross-env ANALYZE=true bun run build",
9
12
  "analyze:experimental": "next experimental-analyze",
@@ -12,9 +15,12 @@
12
15
  "dev:https": "bun ./lib/scripts/dev.ts --https",
13
16
  "dev:inspect": "bun ./lib/scripts/dev.ts --inspect",
14
17
  "format": "biome format --write .",
18
+ "knip": "knip",
19
+ "knip:fix": "knip --fix",
15
20
  "lighthouse": "bunx @unlighthouse/cli --site http://localhost:3000",
16
21
  "lint": "biome lint --max-diagnostics=200",
17
22
  "lint:fix": "biome check --write --unsafe --max-diagnostics=200",
23
+ "prepare": "lefthook install",
18
24
  "start": "next start",
19
25
  "typecheck": "tsgo --noEmit",
20
26
  "typecheck:compare": "bun run typecheck:tsc && bun run typecheck",
@@ -38,12 +44,14 @@
38
44
  "@svgr/webpack": "^8.1.0",
39
45
  "@tailwindcss/postcss": "^4",
40
46
  "@types/bun": "^1.3.6",
41
- "@types/node": "^20.19.30",
47
+ "@types/node": "^24.13.1",
42
48
  "@types/react": "^19.2.9",
43
49
  "@types/react-dom": "^19.2.3",
44
50
  "@typescript/native-preview": "^7.0.0-dev.20260122.3",
45
51
  "babel-plugin-react-compiler": "^1.0.0",
46
52
  "cross-env": "^10.1.0",
53
+ "knip": "^6.16.1",
54
+ "lefthook": "^1.13.0",
47
55
  "postcss-preset-env": "^10.6.1",
48
56
  "schema-dts": "^2.0.0",
49
57
  "tailwindcss": "^4",
@@ -0,0 +1,70 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - main
7
+ push:
8
+ branches:
9
+ - main
10
+
11
+ jobs:
12
+ ci:
13
+ runs-on: ubuntu-latest
14
+
15
+ steps:
16
+ - name: Checkout
17
+ uses: actions/checkout@v4
18
+
19
+ # Node version is read from .nvmrc — the single source of truth.
20
+ - name: Setup Node
21
+ uses: actions/setup-node@v4
22
+ with:
23
+ node-version-file: ".nvmrc"
24
+
25
+ # Bun version is read from package.json's "packageManager" field — the
26
+ # single source of truth. Do NOT pin a version here, or the two can drift.
27
+ - name: Setup Bun
28
+ uses: oven-sh/setup-bun@v2
29
+
30
+ # Cache Bun's global install cache so dependencies aren't re-downloaded
31
+ # on every run. Keyed on the lockfile so it busts when deps change.
32
+ - name: Cache Bun dependencies
33
+ uses: actions/cache@v4
34
+ with:
35
+ path: ~/.bun/install/cache
36
+ key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
37
+ restore-keys: |
38
+ ${{ runner.os }}-bun-
39
+
40
+ - name: Install dependencies
41
+ run: bun install --frozen-lockfile
42
+
43
+ - name: Type check
44
+ run: bun run typecheck:compare
45
+
46
+ - name: Lint
47
+ run: bun run lint
48
+
49
+ - name: Build
50
+ run: bun run build
51
+ env:
52
+ # Sanity project ID and dataset are public (they ship in the client
53
+ # bundle), so they are stored as repository variables. The Sanity read
54
+ # token is secret — add it under Settings → Secrets and variables →
55
+ # Actions. All three are empty (and harmless) until Sanity is set up.
56
+ NEXT_PUBLIC_SANITY_PROJECT_ID: ${{ vars.NEXT_PUBLIC_SANITY_PROJECT_ID }}
57
+ NEXT_PUBLIC_SANITY_DATASET: ${{ vars.NEXT_PUBLIC_SANITY_DATASET }}
58
+ SANITY_API_READ_TOKEN: ${{ secrets.SANITY_API_READ_TOKEN }}
59
+
60
+ # Runs your tests if any exist; a fresh project with none passes cleanly.
61
+ - name: Test
62
+ run: |
63
+ if git ls-files '*.test.*' '*.spec.*' '*_test.*' '*_spec.*' | grep -q .; then
64
+ bun test
65
+ else
66
+ echo "No test files found — skipping bun test."
67
+ fi
68
+
69
+ - name: Knip
70
+ run: bun run knip
@@ -21,3 +21,37 @@ bun dev
21
21
  - `bun build` - Production build
22
22
  - `bun lint` - Run linter
23
23
  - `bun sanity:typegen` - Generate Sanity types
24
+
25
+ ## Continuous Integration
26
+
27
+ A GitHub Actions workflow (`.github/workflows/ci.yml`) runs on every pull request to `main` and on pushes to `main`. It runs type checking, linting, the production build, tests, and `knip`.
28
+
29
+ The workflow takes its runtime versions from your manifests, so there's a single source of truth and nothing to keep in sync by hand:
30
+
31
+ - **Node** comes from `.nvmrc`.
32
+ - **Bun** comes from the `packageManager` field in `package.json`.
33
+
34
+ It also caches Bun's install cache between runs, and the test step is a no-op until you add real test files.
35
+
36
+ ### Sanity environment
37
+
38
+ The build reads Sanity config from GitHub Actions variables/secrets (empty and harmless until you configure Sanity). Once your repo is on GitHub, the quickest way to set them is:
39
+
40
+ ```bash
41
+ bun run ci:secrets
42
+ ```
43
+
44
+ This reads the values from `.env.local` and pushes them to this repo with the GitHub CLI — `NEXT_PUBLIC_SANITY_PROJECT_ID` / `NEXT_PUBLIC_SANITY_DATASET` as **Variables** (they are public) and `SANITY_API_READ_TOKEN` as a **Secret**. Requires the [GitHub CLI](https://cli.github.com), authenticated (`gh auth login`).
45
+
46
+ To set them by hand instead, go to **Settings → Secrets and variables → Actions** and add the two `NEXT_PUBLIC_*` values under **Variables** and the token under **Secrets**.
47
+
48
+ ### Branch protection
49
+
50
+ To block merges when CI fails, enable branch protection on `main`:
51
+
52
+ 1. Go to **Settings → Branches → Add branch ruleset** (or **Add rule**).
53
+ 2. Target the `main` branch.
54
+ 3. Enable **Require status checks to pass before merging** and select the **CI** check.
55
+ 4. (Recommended) Enable **Require a pull request before merging**.
56
+
57
+ Pull requests then cannot be merged until the CI workflow passes.
@@ -10,6 +10,7 @@ import type {
10
10
  ALL_PAGE_SLUGS_QUERY_RESULT,
11
11
  PAGE_QUERY_RESULT,
12
12
  } from "@/lib/integrations/sanity/sanity.types"
13
+ import { checkRedirect } from "@/lib/utils/check-redirect"
13
14
  import { generateSanityMetadata } from "@/lib/utils/metadata"
14
15
  import { getSlugTag } from "@/lib/utils/slug-tag"
15
16
 
@@ -71,7 +72,11 @@ export default async function CatchAllPage({
71
72
  const path = toPath(slug)
72
73
  const page = await getResolvedPage(path)
73
74
 
74
- if (!page) notFound()
75
+ if (!page) {
76
+ // Fall back to a CMS redirect before 404ing.
77
+ await checkRedirect(path)
78
+ notFound()
79
+ }
75
80
 
76
81
  return <PageDocument page={page} path={path} searchParams={searchParams} />
77
82
  }