bsmnt 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) 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/package.json +17 -9
  87. package/src/assets/check-node-version.mjs +22 -0
  88. package/src/modules/features/cms/sanity/files/app/blog/page.tsx +54 -0
  89. package/src/modules/features/cms/sanity/files/app/feed.xml/route.ts +36 -0
  90. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/components/disable-draft-mode.tsx +3 -3
  91. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/schemas/article.ts +7 -7
  92. package/src/modules/features/cms/sanity/files/lib/scripts/setup-ci-secrets.ts +117 -0
  93. package/src/modules/features/cms/sanity/files/lib/utils/rss.ts +96 -0
  94. package/src/modules/features/cms/sanity-pagebuilder/files/.knip.json +8 -0
  95. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/[[...slug]]/route.ts +100 -0
  96. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/draft-mode/disable/route.ts +7 -0
  97. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/draft-mode/enable/route.ts +20 -0
  98. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/revalidate/route.ts +126 -0
  99. package/src/modules/features/cms/sanity-pagebuilder/files/app/feed.xml/route.ts +40 -0
  100. package/src/modules/features/cms/sanity-pagebuilder/files/app/sitemap.md/route.ts +124 -0
  101. package/src/modules/features/cms/sanity-pagebuilder/files/app/studio/[[...tool]]/page.tsx +8 -0
  102. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/draft-mode-toggle.tsx +27 -0
  103. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +87 -0
  104. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/visual-editing.tsx +28 -0
  105. package/src/modules/features/cms/sanity-pagebuilder/files/components/ui/sanity-image/index.tsx +41 -0
  106. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/check-integration.ts +5 -0
  107. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/client.ts +27 -0
  108. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
  109. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-input.tsx +39 -0
  110. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-category-input.tsx +50 -0
  111. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-field.tsx +18 -0
  112. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-input.tsx +14 -0
  113. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/confirm-publish-action.ts +40 -0
  114. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/env.ts +34 -0
  115. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/layout.ts +48 -0
  116. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/redirects.ts +17 -0
  117. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +62 -0
  118. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/live/index.tsx +88 -0
  119. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
  120. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +130 -0
  121. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-category.ts +28 -0
  122. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-tree-pane.tsx +420 -0
  123. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/pinned-pages.ts +54 -0
  124. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/presentation.ts +119 -0
  125. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +385 -0
  126. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/sanity.cli.ts +29 -0
  127. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/sanity.config.ts +113 -0
  128. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +4 -0
  129. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
  130. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
  131. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
  132. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
  133. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/content/author.ts +70 -0
  134. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
  135. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +96 -0
  136. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/company-data.ts +62 -0
  137. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
  138. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/navbar.ts +75 -0
  139. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/redirect.ts +120 -0
  140. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/link.ts +125 -0
  141. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
  142. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/metadata.ts +68 -0
  143. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
  144. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page-builder.ts +39 -0
  145. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
  146. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +242 -0
  147. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
  148. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/singletons.ts +39 -0
  149. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +330 -0
  150. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/image.ts +8 -0
  151. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/link.ts +140 -0
  152. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/page-builder-markdown.ts +81 -0
  153. package/src/modules/features/cms/sanity-pagebuilder/files/lib/scripts/sanity-typegen.ts +45 -0
  154. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/check-redirect.ts +30 -0
  155. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/json-ld.tsx +212 -0
  156. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/metadata.ts +167 -0
  157. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/rss.ts +96 -0
  158. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/sitemap.ts +40 -0
  159. package/src/modules/features/cms/sanity-pagebuilder/files/proxy.ts +81 -0
  160. package/src/templates/next-default/.github/workflows/ci.yml +62 -0
  161. package/src/templates/next-default/README.md +22 -0
  162. package/src/templates/next-default/knip.json +14 -0
  163. package/src/templates/next-default/lefthook.yml +5 -0
  164. package/src/templates/next-default/package.json +8 -4
  165. package/src/templates/next-experiments/.github/workflows/ci.yml +62 -0
  166. package/src/templates/next-experiments/README.md +22 -0
  167. package/src/templates/next-experiments/knip.json +14 -0
  168. package/src/templates/next-experiments/lefthook.yml +30 -0
  169. package/src/templates/next-experiments/package.json +10 -2
  170. package/src/templates/next-pagebuilder/.github/workflows/ci.yml +70 -0
  171. package/src/templates/next-pagebuilder/README.md +34 -0
  172. package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +6 -1
  173. package/src/templates/next-pagebuilder/app/(content)/layout.tsx +3 -12
  174. package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +11 -10
  175. package/src/templates/next-pagebuilder/components/layout/header/index.tsx +5 -3
  176. package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +9 -10
  177. package/src/templates/next-pagebuilder/components/layout/wrapper/index.tsx +3 -11
  178. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/index.tsx +1 -1
  179. package/src/templates/next-pagebuilder/knip.json +27 -0
  180. package/src/templates/next-pagebuilder/lefthook.yml +5 -0
  181. package/src/templates/next-pagebuilder/lib/scripts/setup-ci-secrets.ts +117 -0
  182. package/src/templates/next-pagebuilder/package.json +9 -3
  183. package/src/templates/next-webgl/.github/workflows/ci.yml +62 -0
  184. package/src/templates/next-webgl/README.md +22 -0
  185. package/src/templates/next-webgl/knip.json +13 -0
  186. package/src/templates/next-webgl/lefthook.yml +5 -0
  187. package/src/templates/next-webgl/package.json +8 -4
@@ -0,0 +1,45 @@
1
+ import { defineField, defineType } from "sanity";
2
+ import type { SanityStudioIcon } from "@/lib/integrations/sanity/icons";
3
+ import { pageListIcon } from "@/lib/integrations/sanity/icons";
4
+
5
+ function createContentCollection(config: {
6
+ name: string;
7
+ title: string;
8
+ icon: SanityStudioIcon;
9
+ label: string;
10
+ }) {
11
+ return defineType({
12
+ name: config.name,
13
+ title: config.title,
14
+ type: "document",
15
+ icon: config.icon,
16
+ description: `This component contains all the ${config.label}`,
17
+ fields: [
18
+ defineField({
19
+ name: "subtitle",
20
+ title: "Subtitle",
21
+ type: "string",
22
+ description:
23
+ "Text displayed between the featured articles and the rest of the collection",
24
+ }),
25
+ ],
26
+ preview: {
27
+ select: {
28
+ title: "title",
29
+ },
30
+ prepare({ title }) {
31
+ return {
32
+ title: title || config.title,
33
+ subtitle: `${config.title} with search and category filters`,
34
+ };
35
+ },
36
+ },
37
+ });
38
+ }
39
+
40
+ export const blogCollection = createContentCollection({
41
+ name: "blogCollection",
42
+ title: "Blog Collection",
43
+ icon: pageListIcon,
44
+ label: "blog articles",
45
+ });
@@ -0,0 +1,70 @@
1
+ import { defineField, defineType } from "sanity";
2
+ import { authorIcon } from "@/lib/integrations/sanity/icons";
3
+
4
+ export const author = defineType({
5
+ name: "author",
6
+ title: "Author",
7
+ type: "document",
8
+ icon: authorIcon,
9
+ fields: [
10
+ defineField({
11
+ name: "name",
12
+ title: "Name",
13
+ type: "string",
14
+ validation: (Rule) => Rule.required(),
15
+ }),
16
+ defineField({
17
+ name: "avatar",
18
+ title: "Avatar",
19
+ type: "image",
20
+ options: {
21
+ hotspot: true,
22
+ },
23
+ fields: [
24
+ {
25
+ name: "alt",
26
+ title: "Alt Text",
27
+ type: "string",
28
+ description: "Alternative text for screen readers",
29
+ },
30
+ ],
31
+ validation: (Rule) => Rule.required(),
32
+ }),
33
+ defineField({
34
+ name: "role",
35
+ title: "Role",
36
+ type: "string",
37
+ description: "Job title or role (e.g. CEO, Engineering Lead)",
38
+ }),
39
+ defineField({
40
+ name: "description",
41
+ title: "Description",
42
+ type: "text",
43
+ rows: 3,
44
+ description: "Short bio displayed on blog articles",
45
+ }),
46
+ defineField({
47
+ name: "linkedInUrl",
48
+ title: "LinkedIn URL",
49
+ type: "url",
50
+ validation: (Rule) =>
51
+ Rule.uri({
52
+ scheme: ["https"],
53
+ }),
54
+ }),
55
+ ],
56
+ preview: {
57
+ select: {
58
+ title: "name",
59
+ subtitle: "role",
60
+ media: "avatar",
61
+ },
62
+ },
63
+ orderings: [
64
+ {
65
+ title: "Name A-Z",
66
+ name: "nameAsc",
67
+ by: [{ field: "name", direction: "asc" }],
68
+ },
69
+ ],
70
+ });
@@ -0,0 +1,55 @@
1
+ import { defineField, defineType } from "sanity";
2
+ import { blogCategoryIcon } from "@/lib/integrations/sanity/icons";
3
+
4
+ export const blogCategory = defineType({
5
+ name: "blogCategory",
6
+ title: "Blog Category",
7
+ type: "document",
8
+ icon: blogCategoryIcon,
9
+ fields: [
10
+ defineField({
11
+ name: "title",
12
+ title: "Title",
13
+ type: "string",
14
+ description: "Category name (e.g. AI, Product Updates, Engineering)",
15
+ validation: (Rule) => Rule.required(),
16
+ }),
17
+ defineField({
18
+ name: "slug",
19
+ title: "Slug",
20
+ type: "slug",
21
+ description: "URL-friendly identifier used for filtering",
22
+ options: {
23
+ source: "title",
24
+ maxLength: 96,
25
+ },
26
+ validation: (Rule) =>
27
+ Rule.required().custom((slug) => {
28
+ if (!slug?.current) return "Required";
29
+ if (!/^[a-z0-9-]+$/.test(slug.current)) {
30
+ return "Slug must be lowercase with hyphens only";
31
+ }
32
+ return true;
33
+ }),
34
+ }),
35
+ defineField({
36
+ name: "description",
37
+ title: "Description",
38
+ type: "text",
39
+ rows: 2,
40
+ description: "Brief description of this category",
41
+ }),
42
+ ],
43
+ preview: {
44
+ select: {
45
+ title: "title",
46
+ },
47
+ },
48
+ orderings: [
49
+ {
50
+ title: "Title A-Z",
51
+ name: "titleAsc",
52
+ by: [{ field: "title", direction: "asc" }],
53
+ },
54
+ ],
55
+ });
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Sanity Schema Types
3
+ *
4
+ * All schema definitions for Sanity CMS.
5
+ * Shared schemas live in `schemas/shared`, content documents live in
6
+ * `schemas/content`, component documents live in `schemas/components`, and
7
+ * layout documents live in `schemas/layout`.
8
+ */
9
+
10
+ import type { SchemaTypeDefinition } from "sanity";
11
+ import { blogCollection, blogContent, description, hero } from "./components";
12
+ import { author } from "./content/author";
13
+ import { blogCategory } from "./content/blog-category";
14
+ import { companyData } from "./layout/company-data";
15
+ import { footer } from "./layout/footer";
16
+ import { navbar } from "./layout/navbar";
17
+ import { redirect } from "./layout/redirect";
18
+ import { externalLink, link, pageReference } from "./shared/link";
19
+ import { metadata } from "./shared/metadata";
20
+ import {
21
+ navColumn,
22
+ navItem,
23
+ navLeafItem,
24
+ navMegaMenu,
25
+ } from "./shared/nav-objects";
26
+ import { page } from "./shared/page";
27
+ import { pageBuilder } from "./shared/page-builder";
28
+ import { pageFolder } from "./shared/page-folder";
29
+ import { richText } from "./shared/richText";
30
+
31
+ // Re-export all schemas for convenience
32
+ export {
33
+ author,
34
+ blogCategory,
35
+ blogCollection,
36
+ blogContent,
37
+ companyData,
38
+ description,
39
+ externalLink,
40
+ footer,
41
+ hero,
42
+ link,
43
+ metadata,
44
+ navbar,
45
+ navColumn,
46
+ navItem,
47
+ navLeafItem,
48
+ navMegaMenu,
49
+ page,
50
+ pageBuilder,
51
+ pageFolder,
52
+ pageReference,
53
+ redirect,
54
+ richText,
55
+ };
56
+
57
+ // Schema collection for Sanity configuration
58
+ export const schema: { types: SchemaTypeDefinition[] } = {
59
+ types: [
60
+ // Object types (reusable field types)
61
+ pageReference,
62
+ externalLink,
63
+ link,
64
+ metadata,
65
+ pageBuilder,
66
+ richText,
67
+
68
+ // Navigation object types (used by navbar)
69
+ navLeafItem,
70
+ navColumn,
71
+ navMegaMenu,
72
+ navItem,
73
+
74
+ // Component types (reusable document components)
75
+ blogCollection,
76
+ blogContent,
77
+ hero,
78
+ description,
79
+
80
+ // Layout types (singletons for site-wide layout)
81
+ companyData,
82
+ navbar,
83
+ footer,
84
+
85
+ // Layout collections (managed lists, not singletons)
86
+ redirect,
87
+
88
+ // Document types (content pages)
89
+ page,
90
+ pageFolder,
91
+
92
+ // Content types (reusable content)
93
+ author,
94
+ blogCategory,
95
+ ],
96
+ };
@@ -0,0 +1,62 @@
1
+ import { defineArrayMember, defineField, defineType } from "sanity";
2
+ import { socialLinksIcon } from "@/lib/integrations/sanity/icons";
3
+
4
+ export const companyData = defineType({
5
+ name: "companyData",
6
+ title: "Company Data",
7
+ type: "document",
8
+ fields: [
9
+ defineField({
10
+ name: "socialLinks",
11
+ title: "Social Links",
12
+ type: "array",
13
+ of: [
14
+ defineArrayMember({
15
+ type: "object",
16
+ icon: socialLinksIcon,
17
+ fields: [
18
+ defineField({
19
+ name: "name",
20
+ title: "Name",
21
+ type: "string",
22
+ description: "Platform name (e.g. LinkedIn, TikTok, Bluesky)",
23
+ validation: (Rule) => Rule.required(),
24
+ }),
25
+ defineField({
26
+ name: "icon",
27
+ title: "Icon",
28
+ type: "image",
29
+ description: "Platform icon (SVG, PNG, WebP, etc.)",
30
+ validation: (Rule) => Rule.required(),
31
+ }),
32
+ defineField({
33
+ name: "url",
34
+ title: "URL",
35
+ type: "url",
36
+ validation: (Rule) => Rule.required(),
37
+ }),
38
+ defineField({
39
+ name: "label",
40
+ title: "Label",
41
+ type: "string",
42
+ description:
43
+ "Accessible label for the link (e.g. 'Follow us on LinkedIn')",
44
+ }),
45
+ ],
46
+ preview: {
47
+ select: {
48
+ title: "name",
49
+ subtitle: "url",
50
+ media: "icon",
51
+ },
52
+ },
53
+ }),
54
+ ],
55
+ }),
56
+ ],
57
+ preview: {
58
+ prepare() {
59
+ return { title: "Company Data" };
60
+ },
61
+ },
62
+ });
@@ -0,0 +1,79 @@
1
+ import { defineField, defineType } from "sanity";
2
+
3
+ export const footer = defineType({
4
+ name: "footer",
5
+ title: "Footer",
6
+ type: "document",
7
+ fields: [
8
+ defineField({
9
+ name: "links",
10
+ title: "Links",
11
+ description: "Navigation columns (e.g. Products, Solutions, Resources)",
12
+ type: "array",
13
+ of: [
14
+ {
15
+ type: "object",
16
+ fields: [
17
+ defineField({
18
+ name: "title",
19
+ title: "Title",
20
+ type: "string",
21
+ validation: (Rule) => Rule.required(),
22
+ }),
23
+ defineField({
24
+ name: "items",
25
+ title: "Items",
26
+ type: "array",
27
+ of: [
28
+ {
29
+ type: "object",
30
+ fields: [
31
+ defineField({
32
+ name: "label",
33
+ title: "Label",
34
+ type: "string",
35
+ validation: (Rule) => Rule.required(),
36
+ }),
37
+ defineField({
38
+ name: "href",
39
+ title: "Path",
40
+ type: "string",
41
+ description: "Relative path (e.g. /products, /about)",
42
+ validation: (Rule) =>
43
+ Rule.required().custom((value) => {
44
+ if (
45
+ typeof value === "string" &&
46
+ !value.startsWith("/")
47
+ ) {
48
+ return "Path must start with /";
49
+ }
50
+ return true;
51
+ }),
52
+ }),
53
+ ],
54
+ preview: {
55
+ select: { title: "label", subtitle: "href" },
56
+ },
57
+ },
58
+ ],
59
+ }),
60
+ ],
61
+ preview: {
62
+ select: { title: "title", items: "items" },
63
+ prepare({ title, items }) {
64
+ return {
65
+ title,
66
+ subtitle: `${items?.length ?? 0} items`,
67
+ };
68
+ },
69
+ },
70
+ },
71
+ ],
72
+ }),
73
+ ],
74
+ preview: {
75
+ prepare() {
76
+ return { title: "Footer" };
77
+ },
78
+ },
79
+ });
@@ -0,0 +1,75 @@
1
+ import { defineArrayMember, defineField, defineType } from "sanity";
2
+
3
+ import { logoField } from "@/lib/integrations/sanity/schemas/shared/logo-field";
4
+
5
+ export const navbar = defineType({
6
+ name: "navbar",
7
+ title: "Navbar",
8
+ type: "document",
9
+ fields: [
10
+ logoField,
11
+ defineField({
12
+ name: "navigationItems",
13
+ title: "Navigation Items",
14
+ type: "array",
15
+ of: [defineArrayMember({ type: "navItem" })],
16
+ description:
17
+ "Top-level navigation items -- direct links or menu triggers",
18
+ }),
19
+ defineField({
20
+ name: "ctaButtons",
21
+ title: "CTA Buttons",
22
+ type: "array",
23
+ of: [
24
+ {
25
+ type: "object",
26
+ fields: [
27
+ defineField({
28
+ name: "label",
29
+ title: "Label",
30
+ type: "string",
31
+ validation: (Rule) => Rule.required(),
32
+ }),
33
+ defineField({
34
+ name: "link",
35
+ title: "Link",
36
+ type: "link",
37
+ validation: (Rule) => Rule.required(),
38
+ }),
39
+ defineField({
40
+ name: "variant",
41
+ title: "Variant",
42
+ type: "string",
43
+ options: {
44
+ list: [
45
+ { title: "Primary", value: "primary" },
46
+ { title: "Secondary", value: "secondary" },
47
+ ],
48
+ },
49
+ initialValue: "primary",
50
+ validation: (Rule) => Rule.required(),
51
+ }),
52
+ ],
53
+ preview: {
54
+ select: {
55
+ title: "label",
56
+ variant: "variant",
57
+ },
58
+ prepare({ title, variant }) {
59
+ return {
60
+ title,
61
+ subtitle: variant || "primary",
62
+ };
63
+ },
64
+ },
65
+ },
66
+ ],
67
+ description: "Action buttons on the right side of the navbar",
68
+ }),
69
+ ],
70
+ preview: {
71
+ prepare() {
72
+ return { title: "Navbar" };
73
+ },
74
+ },
75
+ });
@@ -0,0 +1,120 @@
1
+ import { defineField, defineType } from "sanity";
2
+ import { apiVersion } from "@/lib/integrations/sanity/env";
3
+ import { redirectIcon } from "@/lib/integrations/sanity/icons";
4
+
5
+ type RedirectValidationDocument = {
6
+ _id?: string;
7
+ source?: { current?: string };
8
+ };
9
+
10
+ /**
11
+ * Redirect rule. `source` follows the page/folder slug convention: a slash-free
12
+ * route path (e.g. `old-page`, `blog/old-post`).
13
+ */
14
+ export const redirect = defineType({
15
+ name: "redirect",
16
+ title: "Redirect",
17
+ type: "document",
18
+ icon: redirectIcon,
19
+ fields: [
20
+ defineField({
21
+ name: "source",
22
+ title: "Source",
23
+ type: "slug",
24
+ description:
25
+ "Incoming route path to match, without a leading slash (e.g. old-page or blog/old-post).",
26
+ options: { maxLength: 200 },
27
+ validation: (Rule) =>
28
+ Rule.required().custom(async (slug, context) => {
29
+ if (!slug?.current) {
30
+ return "Source is required";
31
+ }
32
+
33
+ if (!/^[a-z0-9-/]+$/.test(slug.current)) {
34
+ return "Source must be lowercase with hyphens, letters, numbers, and forward slashes only";
35
+ }
36
+
37
+ if (slug.current.startsWith("/") || slug.current.endsWith("/")) {
38
+ return "Source must not start or end with a forward slash";
39
+ }
40
+
41
+ const document = context.document as
42
+ | RedirectValidationDocument
43
+ | undefined;
44
+ const id = document?._id?.replace(/^drafts\./, "");
45
+
46
+ if (!id) return true;
47
+
48
+ const client = context.getClient({ apiVersion });
49
+ const existing = await client.fetch<number>(
50
+ `count(*[
51
+ _type == "redirect" &&
52
+ source.current == $slug &&
53
+ !(_id in [$id, "drafts." + $id])
54
+ ])`,
55
+ { slug: slug.current, id },
56
+ );
57
+
58
+ return existing === 0 || "Another redirect already uses this source";
59
+ }),
60
+ }),
61
+ defineField({
62
+ name: "destination",
63
+ title: "Destination",
64
+ type: "string",
65
+ description:
66
+ "Where to send visitors. An internal route path without a leading slash (e.g. new-page or blog/new-post), or an absolute URL (e.g. https://example.com).",
67
+ validation: (Rule) =>
68
+ Rule.required().custom((value, context) => {
69
+ if (typeof value !== "string") return true;
70
+
71
+ const isAbsolute = /^https?:\/\//.test(value);
72
+ if (isAbsolute) return true;
73
+
74
+ if (!/^[a-z0-9-/]+$/.test(value)) {
75
+ return "Destination must be a slash-free route path (lowercase, hyphens, numbers, forward slashes) or an absolute http(s) URL";
76
+ }
77
+
78
+ if (value.startsWith("/") || value.endsWith("/")) {
79
+ return "Internal destination must not start or end with a forward slash";
80
+ }
81
+
82
+ const source = (
83
+ context.document as RedirectValidationDocument | undefined
84
+ )?.source?.current;
85
+ if (source && source === value) {
86
+ return "Destination must differ from source";
87
+ }
88
+
89
+ return true;
90
+ }),
91
+ }),
92
+ defineField({
93
+ name: "permanent",
94
+ title: "Permanent",
95
+ type: "boolean",
96
+ description:
97
+ "Permanent (308 — cached, SEO transfers). Uncheck for a temporary (307) redirect.",
98
+ initialValue: true,
99
+ options: { layout: "switch" },
100
+ }),
101
+ ],
102
+ preview: {
103
+ select: {
104
+ source: "source.current",
105
+ destination: "destination",
106
+ permanent: "permanent",
107
+ },
108
+ prepare({ source, destination, permanent }) {
109
+ const target = destination
110
+ ? /^https?:\/\//.test(destination)
111
+ ? destination
112
+ : `/${destination}`
113
+ : "?";
114
+ return {
115
+ title: source ? `/${source}` : "Untitled redirect",
116
+ subtitle: `→ ${target} (${permanent ? "308" : "307"})`,
117
+ };
118
+ },
119
+ },
120
+ });