create-turbo-wizard 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -15
- package/dist/cli.js +2708 -727
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ import { z } from "zod";
|
|
|
20
20
|
function applyPreset(preset, overrides = {}) {
|
|
21
21
|
return { ...PRESET_CONFIGS[preset], ...overrides };
|
|
22
22
|
}
|
|
23
|
-
var FRONTENDS, CONTENT_SOURCES, PACKAGE_MANAGERS, MODULES, FrontendSchema, ContentSourceSchema, PackageManagerSchema, ModuleSchema, ExperimentalSchema, ProjectNameSchema, WizardConfigSchema, wizardConfigJsonSchema = () => z.toJSONSchema(WizardConfigSchema), PRESETS, PRESET_CONFIGS;
|
|
23
|
+
var FRONTENDS, CONTENT_SOURCES, PACKAGE_MANAGERS, MODULES, DEPLOY_TARGETS, FrontendSchema, ContentSourceSchema, PackageManagerSchema, ModuleSchema, DeployTargetSchema, ExperimentalSchema, ProjectNameSchema, WizardConfigSchema, wizardConfigJsonSchema = () => z.toJSONSchema(WizardConfigSchema), PRESETS, PRESET_CONFIGS;
|
|
24
24
|
var init_src = __esm(() => {
|
|
25
25
|
FRONTENDS = ["astro", "next"];
|
|
26
26
|
CONTENT_SOURCES = ["sanity", "markdown", "none"];
|
|
@@ -39,10 +39,12 @@ var init_src = __esm(() => {
|
|
|
39
39
|
"forms",
|
|
40
40
|
"llms"
|
|
41
41
|
];
|
|
42
|
+
DEPLOY_TARGETS = ["node", "vercel"];
|
|
42
43
|
FrontendSchema = z.enum(FRONTENDS).describe("Web framework");
|
|
43
44
|
ContentSourceSchema = z.enum(CONTENT_SOURCES).describe("Content source");
|
|
44
45
|
PackageManagerSchema = z.enum(PACKAGE_MANAGERS).describe("Package manager");
|
|
45
46
|
ModuleSchema = z.enum(MODULES).describe("Opt-in module");
|
|
47
|
+
DeployTargetSchema = z.enum(DEPLOY_TARGETS).describe("Deploy target (Astro SSR adapter; ignored by Next)");
|
|
46
48
|
ExperimentalSchema = z.object({
|
|
47
49
|
cacheComponents: z.boolean().default(false),
|
|
48
50
|
reactCompiler: z.boolean().default(false)
|
|
@@ -53,6 +55,7 @@ var init_src = __esm(() => {
|
|
|
53
55
|
frontend: FrontendSchema,
|
|
54
56
|
content: ContentSourceSchema.default("none"),
|
|
55
57
|
packageManager: PackageManagerSchema.default("pnpm"),
|
|
58
|
+
deploy: DeployTargetSchema.default("node"),
|
|
56
59
|
modules: z.array(ModuleSchema).default([]),
|
|
57
60
|
experimental: ExperimentalSchema
|
|
58
61
|
});
|
|
@@ -72,7 +75,7 @@ var package_default;
|
|
|
72
75
|
var init_package = __esm(() => {
|
|
73
76
|
package_default = {
|
|
74
77
|
name: "create-turbo-wizard",
|
|
75
|
-
version: "0.1.
|
|
78
|
+
version: "0.1.2",
|
|
76
79
|
description: "Scaffold a Turborepo monorepo: Astro or Next.js, Sanity or Markdown, opt-in modules.",
|
|
77
80
|
keywords: [
|
|
78
81
|
"turborepo",
|
|
@@ -289,6 +292,7 @@ ${WORKSPACE_GLOBS.map((g) => ` - "${g}"`).join(`
|
|
|
289
292
|
"dist/",
|
|
290
293
|
".next/",
|
|
291
294
|
".astro/",
|
|
295
|
+
".sanity/",
|
|
292
296
|
".turbo/",
|
|
293
297
|
"*.tsbuildinfo",
|
|
294
298
|
"",
|
|
@@ -380,8 +384,7 @@ var init_biome = __esm(() => {
|
|
|
380
384
|
kind: "baseline",
|
|
381
385
|
templates: [{ path: "biome.jsonc", content: BIOME_CONFIG }],
|
|
382
386
|
scripts: { check: "biome check .", format: "biome format --write ." },
|
|
383
|
-
deps: [{ name: "@biomejs/biome", versionKey: "@biomejs/biome", target: "root", dev: true }]
|
|
384
|
-
owns: ["biome.jsonc"]
|
|
387
|
+
deps: [{ name: "@biomejs/biome", versionKey: "@biomejs/biome", target: "root", dev: true }]
|
|
385
388
|
};
|
|
386
389
|
});
|
|
387
390
|
|
|
@@ -426,8 +429,7 @@ var init_env = __esm(() => {
|
|
|
426
429
|
content: j({ extends: "@workspace/typescript-config/base.json", include: ["src"] })
|
|
427
430
|
},
|
|
428
431
|
{ path: "packages/env/src/index.ts", content: ENV_SRC }
|
|
429
|
-
]
|
|
430
|
-
owns: ["packages/env/package.json", "packages/env/tsconfig.json", "packages/env/src/index.ts"]
|
|
432
|
+
]
|
|
431
433
|
};
|
|
432
434
|
});
|
|
433
435
|
|
|
@@ -475,11 +477,6 @@ var init_logger = __esm(() => {
|
|
|
475
477
|
content: j2({ extends: "@workspace/typescript-config/base.json", include: ["src"] })
|
|
476
478
|
},
|
|
477
479
|
{ path: "packages/logger/src/index.ts", content: LOGGER_SRC }
|
|
478
|
-
],
|
|
479
|
-
owns: [
|
|
480
|
-
"packages/logger/package.json",
|
|
481
|
-
"packages/logger/tsconfig.json",
|
|
482
|
-
"packages/logger/src/index.ts"
|
|
483
480
|
]
|
|
484
481
|
};
|
|
485
482
|
});
|
|
@@ -543,12 +540,6 @@ var init_typescript_config = __esm(() => {
|
|
|
543
540
|
compilerOptions: { lib: ["DOM", "DOM.Iterable", "ES2023"], jsx: "react-jsx" }
|
|
544
541
|
})
|
|
545
542
|
}
|
|
546
|
-
],
|
|
547
|
-
owns: [
|
|
548
|
-
"packages/typescript-config/package.json",
|
|
549
|
-
"packages/typescript-config/base.json",
|
|
550
|
-
"packages/typescript-config/nextjs.json",
|
|
551
|
-
"packages/typescript-config/react-library.json"
|
|
552
543
|
]
|
|
553
544
|
};
|
|
554
545
|
});
|
|
@@ -616,7 +607,8 @@ function replaceNextMetadata(content, expr) {
|
|
|
616
607
|
}
|
|
617
608
|
|
|
618
609
|
// ../../packages/registry/src/content/markdown.ts
|
|
619
|
-
var
|
|
610
|
+
var j4 = (value) => `${JSON.stringify(value, null, 2)}
|
|
611
|
+
`, CONTENT_PACKAGE_JSON, NEXT_MDX_COMPONENTS = `import type { MDXComponents } from "mdx/types";
|
|
620
612
|
|
|
621
613
|
const components: MDXComponents = {
|
|
622
614
|
// Override built-ins here, e.g. h1: ({ children }) => <h1 className="...">{children}</h1>,
|
|
@@ -642,15 +634,26 @@ This post is written in **MDX** — Markdown with JSX.
|
|
|
642
634
|
- Mix Markdown prose with interactive components
|
|
643
635
|
- Full JSX expression support
|
|
644
636
|
- Server Components by default
|
|
637
|
+
`, NEXT_CONTENT_SCHEMA = `export interface PostMetadata {
|
|
638
|
+
title: string;
|
|
639
|
+
description?: string;
|
|
640
|
+
date?: string;
|
|
641
|
+
}
|
|
645
642
|
`, NEXT_BLOG_POSTS_TS = `import { readdir } from "node:fs/promises";
|
|
646
643
|
import path from "node:path";
|
|
644
|
+
import type { PostMetadata } from "@workspace/content/schema";
|
|
647
645
|
import type { ComponentType } from "react";
|
|
648
646
|
|
|
649
|
-
|
|
647
|
+
// cwd is apps/web for every next command turbo runs; the posts are two levels up in the
|
|
648
|
+
// workspace. In the default configuration this is read only at build time — the listing
|
|
649
|
+
// prerenders and generateStaticParams enumerates the slugs. With cacheComponents on, the
|
|
650
|
+
// PostList boundary below streams at request time and reads it then, exactly as it did
|
|
651
|
+
// when the posts lived in apps/web/content.
|
|
652
|
+
const POSTS_DIR = path.join(process.cwd(), "../../packages/content/blog");
|
|
650
653
|
|
|
651
654
|
export interface PostModule {
|
|
652
655
|
default: ComponentType;
|
|
653
|
-
metadata:
|
|
656
|
+
metadata: PostMetadata;
|
|
654
657
|
}
|
|
655
658
|
|
|
656
659
|
export async function getPostSlugs(): Promise<string[]> {
|
|
@@ -665,7 +668,7 @@ async function getPosts() {
|
|
|
665
668
|
const slugs = await getPostSlugs();
|
|
666
669
|
return Promise.all(
|
|
667
670
|
slugs.map(async (slug) => {
|
|
668
|
-
const { metadata } = (await import(
|
|
671
|
+
const { metadata } = (await import(\`@workspace/content/blog/\${slug}.mdx\`)) as PostModule;
|
|
669
672
|
return { slug, ...metadata };
|
|
670
673
|
}),
|
|
671
674
|
);
|
|
@@ -719,7 +722,7 @@ export default async function BlogPostPage({
|
|
|
719
722
|
params: Promise<{ slug: string }>;
|
|
720
723
|
}) {
|
|
721
724
|
const { slug } = await params;
|
|
722
|
-
const post = (await import(
|
|
725
|
+
const post = (await import(\`@workspace/content/blog/\${slug}.mdx\`).catch(() => null)) as PostModule | null;
|
|
723
726
|
if (!post) notFound();
|
|
724
727
|
|
|
725
728
|
const { default: Post, metadata } = post;
|
|
@@ -733,18 +736,23 @@ export default async function BlogPostPage({
|
|
|
733
736
|
</main>
|
|
734
737
|
);
|
|
735
738
|
}
|
|
736
|
-
`,
|
|
737
|
-
|
|
739
|
+
`, ASTRO_CONTENT_SCHEMA = `import { z } from "astro:content";
|
|
740
|
+
|
|
741
|
+
export const blogSchema = z.object({
|
|
742
|
+
title: z.string(),
|
|
743
|
+
description: z.string(),
|
|
744
|
+
pubDate: z.coerce.date(),
|
|
745
|
+
updatedDate: z.coerce.date().optional(),
|
|
746
|
+
});
|
|
747
|
+
`, ASTRO_CONTENT_CONFIG = `import { blogSchema } from "@workspace/content/schema";
|
|
748
|
+
import { glob } from "astro/loaders";
|
|
749
|
+
import { defineCollection } from "astro:content";
|
|
738
750
|
|
|
739
751
|
const blog = defineCollection({
|
|
740
|
-
// Pattern includes both .md and .mdx so the collection is mixed-format.
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
description: z.string(),
|
|
745
|
-
pubDate: z.coerce.date(),
|
|
746
|
-
updatedDate: z.coerce.date().optional(),
|
|
747
|
-
}),
|
|
752
|
+
// Pattern includes both .md and .mdx so the collection is mixed-format. \`base\` is
|
|
753
|
+
// resolved from the project root (apps/web), so it reaches out to the package.
|
|
754
|
+
loader: glob({ pattern: "**/*.{md,mdx}", base: "../../packages/content/blog" }),
|
|
755
|
+
schema: blogSchema,
|
|
748
756
|
});
|
|
749
757
|
|
|
750
758
|
export const collections = { blog };
|
|
@@ -822,6 +830,16 @@ const { Content } = await render(post);
|
|
|
822
830
|
</html>
|
|
823
831
|
`, markdown;
|
|
824
832
|
var init_markdown = __esm(() => {
|
|
833
|
+
CONTENT_PACKAGE_JSON = j4({
|
|
834
|
+
name: "@workspace/content",
|
|
835
|
+
version: "0.0.0",
|
|
836
|
+
private: true,
|
|
837
|
+
type: "module",
|
|
838
|
+
exports: {
|
|
839
|
+
"./schema": "./src/schema.ts",
|
|
840
|
+
"./blog/*": "./blog/*"
|
|
841
|
+
}
|
|
842
|
+
});
|
|
825
843
|
markdown = {
|
|
826
844
|
id: "markdown",
|
|
827
845
|
kind: "content",
|
|
@@ -829,21 +847,25 @@ var init_markdown = __esm(() => {
|
|
|
829
847
|
label: "MDX content",
|
|
830
848
|
hint: "Markdown + JSX wired into the frontend (official recipe)"
|
|
831
849
|
},
|
|
850
|
+
templates: [{ path: "packages/content/package.json", content: CONTENT_PACKAGE_JSON }],
|
|
832
851
|
fragments: {
|
|
833
852
|
next: [
|
|
853
|
+
{ path: "packages/content/src/schema.ts", content: NEXT_CONTENT_SCHEMA },
|
|
854
|
+
{ path: "packages/content/blog/hello.mdx", content: NEXT_BLOG_POST_MDX },
|
|
834
855
|
{ path: "apps/web/mdx-components.tsx", content: NEXT_MDX_COMPONENTS },
|
|
835
|
-
{ path: "apps/web/content/blog/hello.mdx", content: NEXT_BLOG_POST_MDX },
|
|
836
856
|
{ path: "apps/web/app/blog/posts.ts", content: NEXT_BLOG_POSTS_TS },
|
|
837
857
|
{ path: "apps/web/app/blog/page.tsx", content: NEXT_BLOG_INDEX_TSX },
|
|
838
858
|
{ path: "apps/web/app/blog/[slug]/page.tsx", content: NEXT_BLOG_SLUG_TSX }
|
|
839
859
|
],
|
|
840
860
|
astro: [
|
|
861
|
+
{ path: "packages/content/src/schema.ts", content: ASTRO_CONTENT_SCHEMA },
|
|
862
|
+
{ path: "packages/content/blog/hello.mdx", content: ASTRO_BLOG_POST },
|
|
841
863
|
{ path: "apps/web/src/content.config.ts", content: ASTRO_CONTENT_CONFIG },
|
|
842
|
-
{ path: "apps/web/src/content/blog/hello.mdx", content: ASTRO_BLOG_POST },
|
|
843
864
|
{ path: "apps/web/src/pages/blog/index.astro", content: ASTRO_BLOG_INDEX },
|
|
844
865
|
{ path: "apps/web/src/pages/blog/[...slug].astro", content: ASTRO_BLOG_SLUG_PAGE }
|
|
845
866
|
]
|
|
846
867
|
},
|
|
868
|
+
deps: [{ name: "@workspace/content", version: "workspace:*", target: "app" }],
|
|
847
869
|
frontendDeps: {
|
|
848
870
|
next: [
|
|
849
871
|
{ name: "@next/mdx", version: "^16.2.0", target: "app", dev: true },
|
|
@@ -853,17 +875,6 @@ var init_markdown = __esm(() => {
|
|
|
853
875
|
],
|
|
854
876
|
astro: [{ name: "@astrojs/mdx", version: "^4.3.14", target: "app" }]
|
|
855
877
|
},
|
|
856
|
-
owns: [
|
|
857
|
-
"apps/web/mdx-components.tsx",
|
|
858
|
-
"apps/web/content/blog/hello.mdx",
|
|
859
|
-
"apps/web/app/blog/posts.ts",
|
|
860
|
-
"apps/web/app/blog/page.tsx",
|
|
861
|
-
"apps/web/app/blog/[slug]/page.tsx",
|
|
862
|
-
"apps/web/src/content.config.ts",
|
|
863
|
-
"apps/web/src/content/blog/hello.mdx",
|
|
864
|
-
"apps/web/src/pages/blog/index.astro",
|
|
865
|
-
"apps/web/src/pages/blog/[...slug].astro"
|
|
866
|
-
],
|
|
867
878
|
patches: [
|
|
868
879
|
{
|
|
869
880
|
path: "apps/web/next.config.ts",
|
|
@@ -879,224 +890,398 @@ var init_markdown = __esm(() => {
|
|
|
879
890
|
};
|
|
880
891
|
});
|
|
881
892
|
|
|
882
|
-
// ../../packages/registry/src/content/sanity.ts
|
|
883
|
-
var
|
|
884
|
-
|
|
885
|
-
// Page-builder blocks are objects, not documents: they only exist inside a page.
|
|
886
|
-
|
|
887
|
-
export const hero = defineType({
|
|
888
|
-
name: "hero",
|
|
889
|
-
title: "Hero",
|
|
890
|
-
type: "object",
|
|
891
|
-
fields: [
|
|
892
|
-
defineField({ name: "heading", type: "string", validation: (rule) => rule.required() }),
|
|
893
|
-
defineField({ name: "subheading", type: "text", rows: 2 }),
|
|
894
|
-
defineField({
|
|
895
|
-
name: "image",
|
|
896
|
-
type: "image",
|
|
897
|
-
options: { hotspot: true },
|
|
898
|
-
fields: [defineField({ name: "alt", type: "string", title: "Alternative text" })],
|
|
899
|
-
}),
|
|
900
|
-
],
|
|
901
|
-
preview: {
|
|
902
|
-
select: { title: "heading", media: "image" },
|
|
903
|
-
prepare: ({ title, media }) => ({ title: title ?? "Hero", subtitle: "Hero", media }),
|
|
904
|
-
},
|
|
905
|
-
});
|
|
906
|
-
|
|
907
|
-
export const richText = defineType({
|
|
908
|
-
name: "richText",
|
|
909
|
-
title: "Rich text",
|
|
910
|
-
type: "object",
|
|
911
|
-
fields: [
|
|
912
|
-
defineField({ name: "heading", type: "string" }),
|
|
913
|
-
defineField({ name: "content", type: "array", of: [defineArrayMember({ type: "block" })] }),
|
|
914
|
-
],
|
|
915
|
-
preview: {
|
|
916
|
-
select: { title: "heading" },
|
|
917
|
-
prepare: ({ title }) => ({ title: title ?? "Rich text", subtitle: "Rich text" }),
|
|
918
|
-
},
|
|
919
|
-
});
|
|
893
|
+
// ../../packages/registry/src/content/sanity/astro.ts
|
|
894
|
+
var ASTRO_ENV = `export const projectId = import.meta.env.PUBLIC_SANITY_PROJECT_ID || "placeholder";
|
|
920
895
|
|
|
921
|
-
export const
|
|
922
|
-
name: "cta",
|
|
923
|
-
title: "Call to action",
|
|
924
|
-
type: "object",
|
|
925
|
-
fields: [
|
|
926
|
-
defineField({ name: "heading", type: "string", validation: (rule) => rule.required() }),
|
|
927
|
-
defineField({ name: "text", type: "text", rows: 2 }),
|
|
928
|
-
defineField({ name: "buttonLabel", type: "string" }),
|
|
929
|
-
defineField({ name: "buttonHref", type: "string" }),
|
|
930
|
-
],
|
|
931
|
-
preview: {
|
|
932
|
-
select: { title: "heading" },
|
|
933
|
-
prepare: ({ title }) => ({ title: title ?? "Call to action", subtitle: "Call to action" }),
|
|
934
|
-
},
|
|
935
|
-
});
|
|
936
|
-
`, SCHEMA_PAGE = `import { defineArrayMember, defineField, defineType } from "sanity";
|
|
896
|
+
export const dataset = import.meta.env.PUBLIC_SANITY_DATASET || "production";
|
|
937
897
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
title: "Page",
|
|
941
|
-
type: "document",
|
|
942
|
-
fields: [
|
|
943
|
-
defineField({ name: "title", type: "string", validation: (rule) => rule.required() }),
|
|
944
|
-
defineField({
|
|
945
|
-
name: "slug",
|
|
946
|
-
type: "slug",
|
|
947
|
-
options: { source: "title" },
|
|
948
|
-
validation: (rule) => rule.required(),
|
|
949
|
-
}),
|
|
950
|
-
defineField({ name: "description", type: "text", rows: 3 }),
|
|
951
|
-
defineField({
|
|
952
|
-
name: "pageBuilder",
|
|
953
|
-
title: "Page builder",
|
|
954
|
-
type: "array",
|
|
955
|
-
of: [
|
|
956
|
-
defineArrayMember({ type: "hero" }),
|
|
957
|
-
defineArrayMember({ type: "richText" }),
|
|
958
|
-
defineArrayMember({ type: "cta" }),
|
|
959
|
-
],
|
|
960
|
-
}),
|
|
961
|
-
],
|
|
962
|
-
// The home page is the \`page\` whose slug is "home" — see sanity/queries.ts.
|
|
963
|
-
preview: { select: { title: "title", subtitle: "slug.current" } },
|
|
964
|
-
});
|
|
965
|
-
`, SCHEMA_SETTINGS = `import { defineField, defineType } from "sanity";
|
|
898
|
+
/** Server-only read token; required (and only read) when Visual Editing is enabled. */
|
|
899
|
+
export const readToken = import.meta.env.SANITY_API_READ_TOKEN ?? "";
|
|
966
900
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
defineField({ name: "siteDescription", type: "text", rows: 3 }),
|
|
974
|
-
defineField({
|
|
975
|
-
name: "socialImage",
|
|
976
|
-
title: "Social share image",
|
|
977
|
-
type: "image",
|
|
978
|
-
options: { hotspot: true },
|
|
979
|
-
}),
|
|
980
|
-
],
|
|
981
|
-
preview: { select: { title: "siteTitle", media: "socialImage" } },
|
|
982
|
-
});
|
|
983
|
-
`, SCHEMA_INDEX = `import type { SchemaTypeDefinition } from "sanity";
|
|
984
|
-
import { cta, hero, richText } from "./blocks";
|
|
985
|
-
import { page } from "./page";
|
|
986
|
-
import { settings } from "./settings";
|
|
901
|
+
/**
|
|
902
|
+
* The @sanity/astro Visual Editing switch. Off by default: overlays need a rendering
|
|
903
|
+
* server (\`astro dev\`, or an SSR adapter in production) plus SANITY_API_READ_TOKEN.
|
|
904
|
+
*/
|
|
905
|
+
export const visualEditingEnabled =
|
|
906
|
+
import.meta.env.PUBLIC_SANITY_VISUAL_EDITING_ENABLED === "true";
|
|
987
907
|
|
|
988
|
-
|
|
989
|
-
|
|
908
|
+
/**
|
|
909
|
+
* False on a freshly scaffolded project. Guards every fetch so \`astro build\` never dials
|
|
910
|
+
* placeholder.api.sanity.io and dies; pages render an empty state instead.
|
|
911
|
+
*/
|
|
912
|
+
export const hasSanityCredentials = projectId !== "placeholder";
|
|
913
|
+
`, ASTRO_FETCH = `import { sanityClient } from "sanity:client";
|
|
914
|
+
import { hasSanityCredentials, readToken, visualEditingEnabled } from "./env";
|
|
990
915
|
|
|
991
916
|
/**
|
|
992
|
-
* \`
|
|
993
|
-
*
|
|
917
|
+
* \`sanity:client\` is a Vite virtual module and this file lives in a linked workspace
|
|
918
|
+
* package, not in apps/web. Both halves of that were measured before the move: the
|
|
919
|
+
* integration's \`resolveId\` fires for any importer in the graph, and Vite still applies
|
|
920
|
+
* its \`import.meta.env\` substitution inside the package (a pnpm workspace link resolves
|
|
921
|
+
* to a real path outside node_modules, so it is treated as source, not as an external
|
|
922
|
+
* dependency). Without the second half every read would silently short-circuit on a
|
|
923
|
+
* "placeholder" project id and every route would still answer 200.
|
|
994
924
|
*
|
|
995
|
-
* The
|
|
996
|
-
*
|
|
925
|
+
* The single read path, following @sanity/astro's Visual Editing recipe: with
|
|
926
|
+
* PUBLIC_SANITY_VISUAL_EDITING_ENABLED=true it fetches the drafts perspective with
|
|
927
|
+
* stega source maps, which is what gives Presentation its click-to-edit overlays.
|
|
928
|
+
*
|
|
929
|
+
* Without credentials it returns null before touching the network, so \`astro build\`
|
|
930
|
+
* succeeds on a fresh scaffold. The catch covers a configured-but-unreachable dataset
|
|
931
|
+
* for the same reason: a starter should render empty, not crash.
|
|
997
932
|
*/
|
|
998
|
-
export
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
locations: [{ title: "Home", href: "/" }],
|
|
1026
|
-
message: "This document is used on all pages",
|
|
1027
|
-
tone: "positive",
|
|
1028
|
-
}),
|
|
1029
|
-
page: defineLocations({
|
|
1030
|
-
select: { title: "title", slug: "slug.current" },
|
|
1031
|
-
resolve: (doc) => ({
|
|
1032
|
-
locations: [
|
|
1033
|
-
{
|
|
1034
|
-
title: doc?.title || "Untitled",
|
|
1035
|
-
href: doc?.slug === "home" ? "/" : \`/\${doc?.slug}\`,
|
|
1036
|
-
},
|
|
1037
|
-
],
|
|
1038
|
-
}),
|
|
1039
|
-
}),
|
|
1040
|
-
};
|
|
1041
|
-
`, DOCUMENT_GUARD = ` document: {
|
|
1042
|
-
newDocumentOptions: (prev, { creationContext }) =>
|
|
1043
|
-
creationContext.type === "global"
|
|
1044
|
-
? prev.filter((template) => !singletonTypes.has(template.templateId))
|
|
1045
|
-
: prev,
|
|
1046
|
-
actions: (prev, { schemaType }) =>
|
|
1047
|
-
singletonTypes.has(schemaType)
|
|
1048
|
-
? prev.filter(({ action }) => action && singletonActions.has(action))
|
|
1049
|
-
: prev,
|
|
1050
|
-
},`, QUERIES = `import { defineQuery } from "groq";
|
|
1051
|
-
|
|
1052
|
-
const PAGE_FIELDS = /* groq */ \`
|
|
1053
|
-
_id,
|
|
1054
|
-
_type,
|
|
1055
|
-
title,
|
|
1056
|
-
description,
|
|
1057
|
-
"slug": slug.current,
|
|
1058
|
-
pageBuilder[]{ ... }
|
|
1059
|
-
\`;
|
|
1060
|
-
|
|
1061
|
-
export const HOME_PAGE_QUERY = defineQuery(
|
|
1062
|
-
\`*[_type == "page" && slug.current == "home"][0]{\${PAGE_FIELDS}}\`,
|
|
1063
|
-
);
|
|
933
|
+
export async function loadQuery<T>(
|
|
934
|
+
query: string,
|
|
935
|
+
params: Record<string, unknown> = {},
|
|
936
|
+
): Promise<T | null> {
|
|
937
|
+
if (!hasSanityCredentials) return null;
|
|
938
|
+
if (visualEditingEnabled && !readToken) {
|
|
939
|
+
throw new Error(
|
|
940
|
+
"SANITY_API_READ_TOKEN is required when PUBLIC_SANITY_VISUAL_EDITING_ENABLED is true.",
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
try {
|
|
944
|
+
const { result } = await sanityClient.fetch<T>(query, params, {
|
|
945
|
+
filterResponse: false,
|
|
946
|
+
perspective: visualEditingEnabled ? "drafts" : "published",
|
|
947
|
+
resultSourceMap: visualEditingEnabled ? "withKeyArraySelector" : false,
|
|
948
|
+
stega: visualEditingEnabled,
|
|
949
|
+
...(visualEditingEnabled ? { token: readToken } : {}),
|
|
950
|
+
useCdn: !visualEditingEnabled,
|
|
951
|
+
});
|
|
952
|
+
return result;
|
|
953
|
+
} catch (error) {
|
|
954
|
+
console.warn("[sanity] query failed:", error);
|
|
955
|
+
return null;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
`, ASTRO_IMAGE = `import { createImageUrlBuilder } from "@sanity/image-url";
|
|
959
|
+
import { dataset, projectId } from "./env";
|
|
1064
960
|
|
|
1065
|
-
|
|
1066
|
-
\`*[_type == "page" && slug.current == $slug][0]{\${PAGE_FIELDS}}\`,
|
|
1067
|
-
);
|
|
961
|
+
const builder = createImageUrlBuilder({ projectId, dataset });
|
|
1068
962
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
);
|
|
963
|
+
/** Build a CDN URL for a Sanity image; null when the block has no asset. */
|
|
964
|
+
export function urlFor(source: unknown, width = 1200): string | null {
|
|
965
|
+
const ref = (source as { asset?: { _ref?: string } } | null)?.asset?._ref;
|
|
966
|
+
if (!ref) return null;
|
|
967
|
+
return builder.image(ref).width(width).auto("format").url();
|
|
968
|
+
}
|
|
969
|
+
`, ASTRO_PAGE_BUILDER = `---
|
|
970
|
+
import { urlFor } from "@workspace/sanity/image";
|
|
971
|
+
import type { PAGE_QUERY_RESULT } from "@workspace/sanity/types";
|
|
972
|
+
import { PortableText } from "astro-portabletext";
|
|
1072
973
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
{"_id":"page-home","_type":"page","title":"Turbo Wizard","description":"The home page, served from Sanity.","slug":{"_type":"slug","current":"home"},"pageBuilder":[{"_key":"hero1","_type":"hero","heading":"Content from Sanity, rendered by your app","subheading":"This page lives in the Studio. Edit it there and watch it change here."},{"_key":"rich1","_type":"richText","heading":"How this fits together","content":[{"_key":"b1","_type":"block","style":"normal","markDefs":[],"children":[{"_key":"s1","_type":"span","marks":[],"text":"The schema lives in apps/web/sanity/schema. Queries live in apps/web/sanity/queries.ts and run through typegen, so results are typed. The page builder renders each block by its _type."}]}]},{"_key":"cta1","_type":"cta","heading":"Open the Studio","text":"Edit this page and reload.","buttonLabel":"Open Studio","buttonHref":"/studio"}]}
|
|
1078
|
-
{"_id":"page-about","_type":"page","title":"About","description":"A second page, to show routing by slug.","slug":{"_type":"slug","current":"about"},"pageBuilder":[{"_key":"hero2","_type":"hero","heading":"About this starter","subheading":"Pages are routed by their slug."},{"_key":"rich2","_type":"richText","content":[{"_key":"b2","_type":"block","style":"normal","markDefs":[],"children":[{"_key":"s2","_type":"span","marks":[],"text":"Add a page in the Studio, give it a slug, and it appears at that path."}]}]}]}
|
|
1079
|
-
`, NEXT_ENV = `export const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || "placeholder";
|
|
974
|
+
// Derived from the generated query result rather than hand-written: the page builder's
|
|
975
|
+
// shape IS what the projection returns, so typegen is the only source that cannot drift.
|
|
976
|
+
type PageBlock = NonNullable<NonNullable<PAGE_QUERY_RESULT>["pageBuilder"]>[number];
|
|
977
|
+
type ButtonLink = NonNullable<Extract<PageBlock, { _type: "hero" }>["buttons"]>[number];
|
|
1080
978
|
|
|
1081
|
-
|
|
979
|
+
interface Props {
|
|
980
|
+
blocks: PageBlock[];
|
|
981
|
+
}
|
|
1082
982
|
|
|
1083
|
-
|
|
1084
|
-
// project was set up; bump deliberately, never to "today").
|
|
1085
|
-
export const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || "2026-02-01";
|
|
983
|
+
const { blocks } = Astro.props;
|
|
1086
984
|
|
|
1087
|
-
|
|
1088
|
-
|
|
985
|
+
const section = "margin: 3rem 0;";
|
|
986
|
+
const grid = "display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));";
|
|
987
|
+
const card = "border: 1px solid #e5e5e5; border-radius: 0.75rem; padding: 1.25rem;";
|
|
1089
988
|
|
|
1090
|
-
|
|
1091
|
-
|
|
989
|
+
const buttonProps = (button: ButtonLink) =>
|
|
990
|
+
button.openInNewTab ? { target: "_blank", rel: "noreferrer" } : {};
|
|
991
|
+
---
|
|
1092
992
|
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
993
|
+
{
|
|
994
|
+
blocks.map((block) => {
|
|
995
|
+
if (block._type === "hero") {
|
|
996
|
+
const src = urlFor(block.image, 1600);
|
|
997
|
+
return (
|
|
998
|
+
<section style={section}>
|
|
999
|
+
{block.badge && <p style="opacity: 0.6; margin: 0;">{block.badge}</p>}
|
|
1000
|
+
<h2>{block.title}</h2>
|
|
1001
|
+
{block.richText && <PortableText value={block.richText} />}
|
|
1002
|
+
{!!block.buttons?.length && (
|
|
1003
|
+
<p style="display: flex; gap: 0.75rem; flex-wrap: wrap;">
|
|
1004
|
+
{block.buttons.map(
|
|
1005
|
+
(button) =>
|
|
1006
|
+
button.href && (
|
|
1007
|
+
<a href={button.href} {...buttonProps(button)}>
|
|
1008
|
+
{button.text ?? button.href}
|
|
1009
|
+
</a>
|
|
1010
|
+
),
|
|
1011
|
+
)}
|
|
1012
|
+
</p>
|
|
1013
|
+
)}
|
|
1014
|
+
{src && <img src={src} alt={block.image?.alt ?? ""} />}
|
|
1015
|
+
</section>
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
if (block._type === "richTextBlock") {
|
|
1019
|
+
return (
|
|
1020
|
+
<section style={section}>
|
|
1021
|
+
{block.eyebrow && <p style="opacity: 0.6; margin: 0;">{block.eyebrow}</p>}
|
|
1022
|
+
{block.title && <h2>{block.title}</h2>}
|
|
1023
|
+
{block.richText && <PortableText value={block.richText} />}
|
|
1024
|
+
</section>
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
if (block._type === "featureCardsIcon") {
|
|
1028
|
+
return (
|
|
1029
|
+
<section style={section}>
|
|
1030
|
+
{block.eyebrow && <p style="opacity: 0.6; margin: 0;">{block.eyebrow}</p>}
|
|
1031
|
+
{block.title && <h2>{block.title}</h2>}
|
|
1032
|
+
{block.richText && <PortableText value={block.richText} />}
|
|
1033
|
+
<div style={grid}>
|
|
1034
|
+
{(block.cards ?? []).map((item) => (
|
|
1035
|
+
<div style={card}>
|
|
1036
|
+
{/* \`icon\` holds a Lucide icon name; render it with your icon set of choice. */}
|
|
1037
|
+
{item.icon && <p style="opacity: 0.6; margin: 0;">{item.icon}</p>}
|
|
1038
|
+
<h3>{item.title}</h3>
|
|
1039
|
+
{item.richText && <PortableText value={item.richText} />}
|
|
1040
|
+
</div>
|
|
1041
|
+
))}
|
|
1042
|
+
</div>
|
|
1043
|
+
</section>
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
if (block._type === "imageLinkCards") {
|
|
1047
|
+
return (
|
|
1048
|
+
<section style={section}>
|
|
1049
|
+
{block.eyebrow && <p style="opacity: 0.6; margin: 0;">{block.eyebrow}</p>}
|
|
1050
|
+
{block.title && <h2>{block.title}</h2>}
|
|
1051
|
+
{block.richText && <PortableText value={block.richText} />}
|
|
1052
|
+
<div style={grid}>
|
|
1053
|
+
{(block.cards ?? []).map((item) => {
|
|
1054
|
+
const src = urlFor(item.image, 800);
|
|
1055
|
+
return (
|
|
1056
|
+
<div style={card}>
|
|
1057
|
+
{src && <img src={src} alt={item.image?.alt ?? ""} />}
|
|
1058
|
+
<h3>
|
|
1059
|
+
{item.href ? (
|
|
1060
|
+
<a
|
|
1061
|
+
href={item.href}
|
|
1062
|
+
{...(item.openInNewTab ? { target: "_blank", rel: "noreferrer" } : {})}
|
|
1063
|
+
>
|
|
1064
|
+
{item.title}
|
|
1065
|
+
</a>
|
|
1066
|
+
) : (
|
|
1067
|
+
item.title
|
|
1068
|
+
)}
|
|
1069
|
+
</h3>
|
|
1070
|
+
{item.description && <p>{item.description}</p>}
|
|
1071
|
+
</div>
|
|
1072
|
+
);
|
|
1073
|
+
})}
|
|
1074
|
+
</div>
|
|
1075
|
+
</section>
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
if (block._type === "faqAccordion") {
|
|
1079
|
+
return (
|
|
1080
|
+
<section style={section}>
|
|
1081
|
+
{block.eyebrow && <p style="opacity: 0.6; margin: 0;">{block.eyebrow}</p>}
|
|
1082
|
+
{block.title && <h2>{block.title}</h2>}
|
|
1083
|
+
{block.subtitle && <p>{block.subtitle}</p>}
|
|
1084
|
+
{(block.faqs ?? []).map((faq) => (
|
|
1085
|
+
<details style="border-top: 1px solid #e5e5e5; padding: 0.75rem 0;">
|
|
1086
|
+
<summary>{faq.title}</summary>
|
|
1087
|
+
{faq.richText && <PortableText value={faq.richText} />}
|
|
1088
|
+
</details>
|
|
1089
|
+
))}
|
|
1090
|
+
</section>
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
if (block._type === "subscribeNewsletter") {
|
|
1094
|
+
// Presentational only: no action, no handler. Wire it to the \`forms\` module's
|
|
1095
|
+
// endpoint (or your own) when you want it to do something.
|
|
1096
|
+
return (
|
|
1097
|
+
<section style={section}>
|
|
1098
|
+
<h2>{block.title}</h2>
|
|
1099
|
+
{block.subTitle && <PortableText value={block.subTitle} />}
|
|
1100
|
+
<form style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
|
1101
|
+
<input type="email" name="email" placeholder="you@example.com" required />
|
|
1102
|
+
<button type="submit">Subscribe</button>
|
|
1103
|
+
</form>
|
|
1104
|
+
{block.helperText && <PortableText value={block.helperText} />}
|
|
1105
|
+
</section>
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
if (block._type === "cta") {
|
|
1109
|
+
return (
|
|
1110
|
+
<section style={section}>
|
|
1111
|
+
{block.eyebrow && <p style="opacity: 0.6; margin: 0;">{block.eyebrow}</p>}
|
|
1112
|
+
<h2>{block.title}</h2>
|
|
1113
|
+
{block.richText && <PortableText value={block.richText} />}
|
|
1114
|
+
{!!block.buttons?.length && (
|
|
1115
|
+
<p style="display: flex; gap: 0.75rem; flex-wrap: wrap;">
|
|
1116
|
+
{block.buttons.map(
|
|
1117
|
+
(button) =>
|
|
1118
|
+
button.href && (
|
|
1119
|
+
<a href={button.href} {...buttonProps(button)}>
|
|
1120
|
+
{button.text ?? button.href}
|
|
1121
|
+
</a>
|
|
1122
|
+
),
|
|
1123
|
+
)}
|
|
1124
|
+
</p>
|
|
1125
|
+
)}
|
|
1126
|
+
</section>
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
return null;
|
|
1130
|
+
})
|
|
1131
|
+
}
|
|
1132
|
+
`, ASTRO_SLUG_PAGE = `---
|
|
1133
|
+
import { VisualEditing } from "@sanity/astro/visual-editing";
|
|
1134
|
+
import { visualEditingEnabled } from "@workspace/sanity/env";
|
|
1135
|
+
import { loadQuery } from "@workspace/sanity/fetch";
|
|
1136
|
+
import { PAGE_QUERY, PAGE_SLUGS_QUERY } from "@workspace/sanity/queries";
|
|
1137
|
+
import type { PAGE_QUERY_RESULT } from "@workspace/sanity/types";
|
|
1138
|
+
import PageBuilder from "@workspace/sanity-blocks/PageBuilder.astro";
|
|
1139
|
+
|
|
1140
|
+
export async function getStaticPaths() {
|
|
1141
|
+
// Returns [] without credentials, so a fresh scaffold builds with no CMS pages.
|
|
1142
|
+
const slugs = await loadQuery<(string | null)[]>(PAGE_SLUGS_QUERY);
|
|
1143
|
+
return (slugs ?? []).filter(Boolean).map((slug) => ({ params: { slug } }));
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
const page = await loadQuery<PAGE_QUERY_RESULT>(PAGE_QUERY, { slug: Astro.params.slug });
|
|
1147
|
+
---
|
|
1148
|
+
|
|
1149
|
+
<html lang="en">
|
|
1150
|
+
<head>
|
|
1151
|
+
<meta charset="utf-8" />
|
|
1152
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1153
|
+
<title>{page?.title ?? "web"}</title>
|
|
1154
|
+
</head>
|
|
1155
|
+
<body style="font-family: system-ui, sans-serif; padding: 3rem;">
|
|
1156
|
+
<h1>{page?.title}</h1>
|
|
1157
|
+
<PageBuilder blocks={page?.pageBuilder ?? []} />
|
|
1158
|
+
<VisualEditing enabled={visualEditingEnabled} />
|
|
1159
|
+
</body>
|
|
1160
|
+
</html>
|
|
1161
|
+
`, ASTRO_BLOG_INDEX_PAGE = `---
|
|
1162
|
+
import { VisualEditing } from "@sanity/astro/visual-editing";
|
|
1163
|
+
import { visualEditingEnabled } from "@workspace/sanity/env";
|
|
1164
|
+
import { loadQuery } from "@workspace/sanity/fetch";
|
|
1165
|
+
import { urlFor } from "@workspace/sanity/image";
|
|
1166
|
+
import { BLOG_INDEX_QUERY } from "@workspace/sanity/queries";
|
|
1167
|
+
import type { BLOG_INDEX_QUERY_RESULT } from "@workspace/sanity/types";
|
|
1168
|
+
|
|
1169
|
+
const posts = (await loadQuery<BLOG_INDEX_QUERY_RESULT>(BLOG_INDEX_QUERY)) ?? [];
|
|
1170
|
+
---
|
|
1171
|
+
|
|
1172
|
+
<html lang="en">
|
|
1173
|
+
<head>
|
|
1174
|
+
<meta charset="utf-8" />
|
|
1175
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1176
|
+
<title>Blog</title>
|
|
1177
|
+
</head>
|
|
1178
|
+
<body style="font-family: system-ui, sans-serif; padding: 3rem;">
|
|
1179
|
+
<h1>Blog</h1>
|
|
1180
|
+
{
|
|
1181
|
+
posts.length === 0 && (
|
|
1182
|
+
<p>
|
|
1183
|
+
No posts yet — seed the dataset with <code>pnpm sanity:seed</code> from apps/studio,
|
|
1184
|
+
or write one in the Studio (<code>pnpm dev:studio</code>).
|
|
1185
|
+
</p>
|
|
1186
|
+
)
|
|
1187
|
+
}
|
|
1188
|
+
{
|
|
1189
|
+
posts.map((post) => {
|
|
1190
|
+
const src = urlFor(post.image, 800);
|
|
1191
|
+
const author = post.authors?.[0];
|
|
1192
|
+
return (
|
|
1193
|
+
<article style="border-top: 1px solid #e5e5e5; padding: 1.5rem 0;">
|
|
1194
|
+
{src && <img src={src} alt={post.image?.alt ?? ""} />}
|
|
1195
|
+
<h2>
|
|
1196
|
+
<a href={\`/blog/\${post.slug}\`}>{post.title}</a>
|
|
1197
|
+
</h2>
|
|
1198
|
+
<p style="opacity: 0.6;">
|
|
1199
|
+
{author?.name}
|
|
1200
|
+
{author?.name && post.publishedAt && " · "}
|
|
1201
|
+
{post.publishedAt && <time datetime={post.publishedAt}>{post.publishedAt}</time>}
|
|
1202
|
+
</p>
|
|
1203
|
+
{post.description && <p>{post.description}</p>}
|
|
1204
|
+
</article>
|
|
1205
|
+
);
|
|
1206
|
+
})
|
|
1207
|
+
}
|
|
1208
|
+
<VisualEditing enabled={visualEditingEnabled} />
|
|
1209
|
+
</body>
|
|
1210
|
+
</html>
|
|
1211
|
+
`, ASTRO_BLOG_POST_PAGE = `---
|
|
1212
|
+
import { VisualEditing } from "@sanity/astro/visual-editing";
|
|
1213
|
+
import { visualEditingEnabled } from "@workspace/sanity/env";
|
|
1214
|
+
import { loadQuery } from "@workspace/sanity/fetch";
|
|
1215
|
+
import { urlFor } from "@workspace/sanity/image";
|
|
1216
|
+
import { BLOG_QUERY, BLOG_SLUGS_QUERY } from "@workspace/sanity/queries";
|
|
1217
|
+
import type { BLOG_QUERY_RESULT } from "@workspace/sanity/types";
|
|
1218
|
+
import { PortableText } from "astro-portabletext";
|
|
1219
|
+
|
|
1220
|
+
export async function getStaticPaths() {
|
|
1221
|
+
// Returns [] without credentials, so a fresh scaffold builds with no posts.
|
|
1222
|
+
const slugs = await loadQuery<(string | null)[]>(BLOG_SLUGS_QUERY);
|
|
1223
|
+
return (slugs ?? []).filter(Boolean).map((slug) => ({ params: { slug } }));
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
const post = await loadQuery<BLOG_QUERY_RESULT>(BLOG_QUERY, { slug: Astro.params.slug });
|
|
1227
|
+
const src = urlFor(post?.image, 1600);
|
|
1228
|
+
const author = post?.authors?.[0];
|
|
1229
|
+
---
|
|
1230
|
+
|
|
1231
|
+
<html lang="en">
|
|
1232
|
+
<head>
|
|
1233
|
+
<meta charset="utf-8" />
|
|
1234
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1235
|
+
<title>{post?.title ?? "Blog"}</title>
|
|
1236
|
+
{post?.description && <meta name="description" content={post.description} />}
|
|
1237
|
+
</head>
|
|
1238
|
+
<body style="font-family: system-ui, sans-serif; padding: 3rem;">
|
|
1239
|
+
<p><a href="/blog">← Blog</a></p>
|
|
1240
|
+
<h1>{post?.title}</h1>
|
|
1241
|
+
<p style="opacity: 0.6;">
|
|
1242
|
+
{author?.name && (author.position ? \`\${author.name}, \${author.position}\` : author.name)}
|
|
1243
|
+
{author?.name && post?.publishedAt && " · "}
|
|
1244
|
+
{post?.publishedAt && <time datetime={post.publishedAt}>{post.publishedAt}</time>}
|
|
1245
|
+
</p>
|
|
1246
|
+
{src && <img src={src} alt={post?.image?.alt ?? ""} />}
|
|
1247
|
+
{post?.richText && <PortableText value={post.richText} />}
|
|
1248
|
+
<VisualEditing enabled={visualEditingEnabled} />
|
|
1249
|
+
</body>
|
|
1250
|
+
</html>
|
|
1251
|
+
`, ASTRO_MODULE_REF = '/// <reference types="@sanity/astro/module" />', ASTRO_ENV_LOADER = `// Astro loads .env after this config runs, so pull it in ourselves for the Sanity ids.
|
|
1252
|
+
try {
|
|
1253
|
+
process.loadEnvFile(".env");
|
|
1254
|
+
} catch {
|
|
1255
|
+
// No .env — the placeholder ids below keep the build working.
|
|
1256
|
+
}
|
|
1257
|
+
`;
|
|
1258
|
+
|
|
1259
|
+
// ../../packages/registry/src/content/sanity/next.ts
|
|
1260
|
+
var NEXT_ENV = `export const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || "placeholder";
|
|
1261
|
+
|
|
1262
|
+
export const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET || "production";
|
|
1263
|
+
|
|
1264
|
+
// A recent pinned date, per Sanity's API-versioning guidance (the docs pin the date the
|
|
1265
|
+
// project was set up; bump deliberately, never to "today").
|
|
1266
|
+
export const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || "2026-02-01";
|
|
1267
|
+
|
|
1268
|
+
/** Server-only read token; empty when unset (draft mode and live previews need it). */
|
|
1269
|
+
export const readToken = process.env.SANITY_API_READ_TOKEN ?? "";
|
|
1270
|
+
|
|
1271
|
+
/**
|
|
1272
|
+
* Where the Studio lives — stega encodes it into every string it returns, so a
|
|
1273
|
+
* Visual Editing click-through lands on the right field. Absolute since the Studio
|
|
1274
|
+
* became its own app (\`sanity dev\` defaults to :3333).
|
|
1275
|
+
*/
|
|
1276
|
+
export const studioUrl = process.env.NEXT_PUBLIC_SANITY_STUDIO_URL || "http://localhost:3333";
|
|
1277
|
+
|
|
1278
|
+
/**
|
|
1279
|
+
* False on a freshly scaffolded project. Guards every fetch so the build never dials
|
|
1280
|
+
* placeholder.api.sanity.io and dies; pages render an empty state instead.
|
|
1281
|
+
*/
|
|
1282
|
+
export const hasSanityCredentials = projectId !== "placeholder";
|
|
1283
|
+
`, NEXT_CLIENT = `import { createClient } from "next-sanity";
|
|
1284
|
+
import { apiVersion, dataset, projectId, studioUrl } from "./env";
|
|
1100
1285
|
|
|
1101
1286
|
export const client = createClient({
|
|
1102
1287
|
projectId,
|
|
@@ -1109,17 +1294,33 @@ export const client = createClient({
|
|
|
1109
1294
|
stega: { studioUrl },
|
|
1110
1295
|
});
|
|
1111
1296
|
`, NEXT_LIVE = `import { defineLive } from "next-sanity/live";
|
|
1112
|
-
import { readToken } from "./env";
|
|
1297
|
+
import { hasSanityCredentials, readToken } from "./env";
|
|
1113
1298
|
import { client } from "./client";
|
|
1114
1299
|
|
|
1115
1300
|
// defineLive gives us live content plus draft-mode reads. The token is passed as
|
|
1116
1301
|
// undefined rather than "" when unset — an empty string reads as "provided but invalid".
|
|
1117
|
-
export const { sanityFetch, SanityLive } = defineLive({
|
|
1302
|
+
export const { sanityFetch, SanityLive: LiveComponent } = defineLive({
|
|
1118
1303
|
client,
|
|
1119
1304
|
serverToken: readToken || undefined,
|
|
1120
1305
|
browserToken: readToken || undefined,
|
|
1121
1306
|
});
|
|
1122
|
-
|
|
1307
|
+
|
|
1308
|
+
/**
|
|
1309
|
+
* On a fresh scaffold \`projectId\` is "placeholder", so the live socket dials
|
|
1310
|
+
* placeholder.api.sanity.io and next-sanity retries forever — the dev log fills with
|
|
1311
|
+
* "<SanityLive> is attempting to reconnect" and nothing else is readable. Render
|
|
1312
|
+
* nothing until there are credentials, the same guard \`loadQuery\` applies to reads.
|
|
1313
|
+
*/
|
|
1314
|
+
export const SanityLive = hasSanityCredentials ? LiveComponent : () => null;
|
|
1315
|
+
`, NEXT_FETCH = `/// <reference path="./sanity.types.ts" />
|
|
1316
|
+
// ^ \`sanity typegen generate\` writes sanity.types.ts here (see apps/studio/sanity.cli.ts) and
|
|
1317
|
+
// its \`declare module "@sanity/client"\` block is what types every query result. Now that
|
|
1318
|
+
// the file lives outside apps/web it is no longer swept up by the app's tsconfig include,
|
|
1319
|
+
// so this reference is what pulls it into the program — without it every loadQuery result
|
|
1320
|
+
// silently degrades to \`unknown\` and the pages stop type-checking. A path reference cannot
|
|
1321
|
+
// be elided the way an unused import can. The file ships pre-generated; regenerate it with
|
|
1322
|
+
// \`extract\` + \`type\` from apps/studio.
|
|
1323
|
+
import type { QueryParams } from "next-sanity";
|
|
1123
1324
|
import { client } from "./client";
|
|
1124
1325
|
import { hasSanityCredentials } from "./env";
|
|
1125
1326
|
import { sanityFetch } from "./live";
|
|
@@ -1173,52 +1374,234 @@ export function urlFor(source: unknown, width = 1200): string | null {
|
|
|
1173
1374
|
return builder.image(ref).width(width).auto("format").url();
|
|
1174
1375
|
}
|
|
1175
1376
|
`, NEXT_PAGE_BUILDER = `import { PortableText, type PortableTextProps } from "@portabletext/react";
|
|
1377
|
+
import { urlFor } from "@workspace/sanity/image";
|
|
1176
1378
|
import Image from "next/image";
|
|
1177
|
-
import { urlFor } from "./image";
|
|
1178
1379
|
|
|
1179
1380
|
type SanityImage = { asset?: { _ref?: string } | null; alt?: string | null } | null;
|
|
1381
|
+
type Rich = PortableTextProps["value"] | null;
|
|
1382
|
+
|
|
1383
|
+
/** queries.ts flattens every \`customUrl\` to an href, so nothing here branches on link type. */
|
|
1384
|
+
type ButtonLink = {
|
|
1385
|
+
_key: string;
|
|
1386
|
+
text?: string | null;
|
|
1387
|
+
variant?: string | null;
|
|
1388
|
+
href?: string | null;
|
|
1389
|
+
openInNewTab?: boolean | null;
|
|
1390
|
+
};
|
|
1180
1391
|
|
|
1181
1392
|
export type PageBlock =
|
|
1182
|
-
| {
|
|
1183
|
-
|
|
1393
|
+
| {
|
|
1394
|
+
_key: string;
|
|
1395
|
+
_type: "hero";
|
|
1396
|
+
badge?: string | null;
|
|
1397
|
+
title?: string | null;
|
|
1398
|
+
richText?: Rich;
|
|
1399
|
+
image?: SanityImage;
|
|
1400
|
+
buttons?: ButtonLink[] | null;
|
|
1401
|
+
}
|
|
1402
|
+
| {
|
|
1403
|
+
_key: string;
|
|
1404
|
+
_type: "richTextBlock";
|
|
1405
|
+
eyebrow?: string | null;
|
|
1406
|
+
title?: string | null;
|
|
1407
|
+
richText?: Rich;
|
|
1408
|
+
}
|
|
1409
|
+
| {
|
|
1410
|
+
_key: string;
|
|
1411
|
+
_type: "featureCardsIcon";
|
|
1412
|
+
eyebrow?: string | null;
|
|
1413
|
+
title?: string | null;
|
|
1414
|
+
richText?: Rich;
|
|
1415
|
+
cards?:
|
|
1416
|
+
| { _key: string; icon?: string | null; title?: string | null; richText?: Rich }[]
|
|
1417
|
+
| null;
|
|
1418
|
+
}
|
|
1419
|
+
| {
|
|
1420
|
+
_key: string;
|
|
1421
|
+
_type: "imageLinkCards";
|
|
1422
|
+
eyebrow?: string | null;
|
|
1423
|
+
title?: string | null;
|
|
1424
|
+
richText?: Rich;
|
|
1425
|
+
buttons?: ButtonLink[] | null;
|
|
1426
|
+
cards?:
|
|
1427
|
+
| {
|
|
1428
|
+
_key: string;
|
|
1429
|
+
title?: string | null;
|
|
1430
|
+
description?: string | null;
|
|
1431
|
+
image?: SanityImage;
|
|
1432
|
+
href?: string | null;
|
|
1433
|
+
openInNewTab?: boolean | null;
|
|
1434
|
+
}[]
|
|
1435
|
+
| null;
|
|
1436
|
+
}
|
|
1437
|
+
| {
|
|
1438
|
+
_key: string;
|
|
1439
|
+
_type: "faqAccordion";
|
|
1440
|
+
eyebrow?: string | null;
|
|
1441
|
+
title?: string | null;
|
|
1442
|
+
subtitle?: string | null;
|
|
1443
|
+
faqs?: { _id: string; title?: string | null; richText?: Rich }[] | null;
|
|
1444
|
+
}
|
|
1445
|
+
| {
|
|
1446
|
+
_key: string;
|
|
1447
|
+
_type: "subscribeNewsletter";
|
|
1448
|
+
title?: string | null;
|
|
1449
|
+
subTitle?: Rich;
|
|
1450
|
+
helperText?: Rich;
|
|
1451
|
+
}
|
|
1184
1452
|
| {
|
|
1185
1453
|
_key: string;
|
|
1186
1454
|
_type: "cta";
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1455
|
+
eyebrow?: string | null;
|
|
1456
|
+
title?: string | null;
|
|
1457
|
+
richText?: Rich;
|
|
1458
|
+
buttons?: ButtonLink[] | null;
|
|
1191
1459
|
};
|
|
1192
1460
|
|
|
1461
|
+
const section = { margin: "3rem 0" } as const;
|
|
1462
|
+
const grid = {
|
|
1463
|
+
display: "grid",
|
|
1464
|
+
gap: "1.5rem",
|
|
1465
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(16rem, 1fr))",
|
|
1466
|
+
} as const;
|
|
1467
|
+
const card = { border: "1px solid #e5e5e5", borderRadius: "0.75rem", padding: "1.25rem" } as const;
|
|
1468
|
+
|
|
1469
|
+
function Eyebrow({ children }: { children?: string | null }) {
|
|
1470
|
+
return children ? <p style={{ opacity: 0.6, margin: 0 }}>{children}</p> : null;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
function Buttons({ items }: { items?: ButtonLink[] | null }) {
|
|
1474
|
+
if (!items?.length) return null;
|
|
1475
|
+
return (
|
|
1476
|
+
<p style={{ display: "flex", gap: "0.75rem", flexWrap: "wrap" }}>
|
|
1477
|
+
{items.map((button) =>
|
|
1478
|
+
button.href ? (
|
|
1479
|
+
<a
|
|
1480
|
+
key={button._key}
|
|
1481
|
+
href={button.href}
|
|
1482
|
+
{...(button.openInNewTab ? { target: "_blank", rel: "noreferrer" } : {})}
|
|
1483
|
+
>
|
|
1484
|
+
{button.text ?? button.href}
|
|
1485
|
+
</a>
|
|
1486
|
+
) : null,
|
|
1487
|
+
)}
|
|
1488
|
+
</p>
|
|
1489
|
+
);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1193
1492
|
function Block({ block }: { block: PageBlock }) {
|
|
1194
1493
|
switch (block._type) {
|
|
1195
1494
|
case "hero": {
|
|
1196
1495
|
const src = urlFor(block.image, 1600);
|
|
1197
1496
|
return (
|
|
1198
|
-
<section>
|
|
1199
|
-
<
|
|
1200
|
-
|
|
1497
|
+
<section style={section}>
|
|
1498
|
+
<Eyebrow>{block.badge}</Eyebrow>
|
|
1499
|
+
<h2>{block.title}</h2>
|
|
1500
|
+
{block.richText ? <PortableText value={block.richText} /> : null}
|
|
1501
|
+
<Buttons items={block.buttons} />
|
|
1201
1502
|
{src ? (
|
|
1202
1503
|
<Image src={src} alt={block.image?.alt ?? ""} width={1600} height={900} />
|
|
1203
1504
|
) : null}
|
|
1204
1505
|
</section>
|
|
1205
1506
|
);
|
|
1206
1507
|
}
|
|
1207
|
-
case "
|
|
1508
|
+
case "richTextBlock":
|
|
1509
|
+
return (
|
|
1510
|
+
<section style={section}>
|
|
1511
|
+
<Eyebrow>{block.eyebrow}</Eyebrow>
|
|
1512
|
+
{block.title ? <h2>{block.title}</h2> : null}
|
|
1513
|
+
{block.richText ? <PortableText value={block.richText} /> : null}
|
|
1514
|
+
</section>
|
|
1515
|
+
);
|
|
1516
|
+
case "featureCardsIcon":
|
|
1517
|
+
return (
|
|
1518
|
+
<section style={section}>
|
|
1519
|
+
<Eyebrow>{block.eyebrow}</Eyebrow>
|
|
1520
|
+
{block.title ? <h2>{block.title}</h2> : null}
|
|
1521
|
+
{block.richText ? <PortableText value={block.richText} /> : null}
|
|
1522
|
+
<div style={grid}>
|
|
1523
|
+
{(block.cards ?? []).map((item) => (
|
|
1524
|
+
<div key={item._key} style={card}>
|
|
1525
|
+
{/* \`icon\` holds a Lucide icon name; render it with your icon set of choice. */}
|
|
1526
|
+
{item.icon ? <p style={{ opacity: 0.6, margin: 0 }}>{item.icon}</p> : null}
|
|
1527
|
+
<h3>{item.title}</h3>
|
|
1528
|
+
{item.richText ? <PortableText value={item.richText} /> : null}
|
|
1529
|
+
</div>
|
|
1530
|
+
))}
|
|
1531
|
+
</div>
|
|
1532
|
+
</section>
|
|
1533
|
+
);
|
|
1534
|
+
case "imageLinkCards":
|
|
1535
|
+
return (
|
|
1536
|
+
<section style={section}>
|
|
1537
|
+
<Eyebrow>{block.eyebrow}</Eyebrow>
|
|
1538
|
+
{block.title ? <h2>{block.title}</h2> : null}
|
|
1539
|
+
{block.richText ? <PortableText value={block.richText} /> : null}
|
|
1540
|
+
<div style={grid}>
|
|
1541
|
+
{(block.cards ?? []).map((item) => {
|
|
1542
|
+
const src = urlFor(item.image, 800);
|
|
1543
|
+
return (
|
|
1544
|
+
<div key={item._key} style={card}>
|
|
1545
|
+
{src ? (
|
|
1546
|
+
<Image src={src} alt={item.image?.alt ?? ""} width={800} height={450} />
|
|
1547
|
+
) : null}
|
|
1548
|
+
<h3>
|
|
1549
|
+
{item.href ? (
|
|
1550
|
+
<a
|
|
1551
|
+
href={item.href}
|
|
1552
|
+
{...(item.openInNewTab ? { target: "_blank", rel: "noreferrer" } : {})}
|
|
1553
|
+
>
|
|
1554
|
+
{item.title}
|
|
1555
|
+
</a>
|
|
1556
|
+
) : (
|
|
1557
|
+
item.title
|
|
1558
|
+
)}
|
|
1559
|
+
</h3>
|
|
1560
|
+
{item.description ? <p>{item.description}</p> : null}
|
|
1561
|
+
</div>
|
|
1562
|
+
);
|
|
1563
|
+
})}
|
|
1564
|
+
</div>
|
|
1565
|
+
<Buttons items={block.buttons} />
|
|
1566
|
+
</section>
|
|
1567
|
+
);
|
|
1568
|
+
case "faqAccordion":
|
|
1569
|
+
// <details> is the accordion — it needs no client component and behaves the same
|
|
1570
|
+
// in the Astro build, where this same markup is duplicated.
|
|
1571
|
+
return (
|
|
1572
|
+
<section style={section}>
|
|
1573
|
+
<Eyebrow>{block.eyebrow}</Eyebrow>
|
|
1574
|
+
{block.title ? <h2>{block.title}</h2> : null}
|
|
1575
|
+
{block.subtitle ? <p>{block.subtitle}</p> : null}
|
|
1576
|
+
{(block.faqs ?? []).map((faq) => (
|
|
1577
|
+
<details key={faq._id} style={{ borderTop: "1px solid #e5e5e5", padding: "0.75rem 0" }}>
|
|
1578
|
+
<summary>{faq.title}</summary>
|
|
1579
|
+
{faq.richText ? <PortableText value={faq.richText} /> : null}
|
|
1580
|
+
</details>
|
|
1581
|
+
))}
|
|
1582
|
+
</section>
|
|
1583
|
+
);
|
|
1584
|
+
case "subscribeNewsletter":
|
|
1585
|
+
// Presentational only: no action, no handler. Wire it to the \`forms\` module's
|
|
1586
|
+
// endpoint (or your own) when you want it to do something.
|
|
1208
1587
|
return (
|
|
1209
|
-
<section>
|
|
1210
|
-
|
|
1211
|
-
{block.
|
|
1588
|
+
<section style={section}>
|
|
1589
|
+
<h2>{block.title}</h2>
|
|
1590
|
+
{block.subTitle ? <PortableText value={block.subTitle} /> : null}
|
|
1591
|
+
<form style={{ display: "flex", gap: "0.5rem", flexWrap: "wrap" }}>
|
|
1592
|
+
<input type="email" name="email" placeholder="you@example.com" required />
|
|
1593
|
+
<button type="submit">Subscribe</button>
|
|
1594
|
+
</form>
|
|
1595
|
+
{block.helperText ? <PortableText value={block.helperText} /> : null}
|
|
1212
1596
|
</section>
|
|
1213
1597
|
);
|
|
1214
1598
|
case "cta":
|
|
1215
1599
|
return (
|
|
1216
|
-
<section>
|
|
1217
|
-
<
|
|
1218
|
-
|
|
1219
|
-
{block.
|
|
1220
|
-
|
|
1221
|
-
) : null}
|
|
1600
|
+
<section style={section}>
|
|
1601
|
+
<Eyebrow>{block.eyebrow}</Eyebrow>
|
|
1602
|
+
<h2>{block.title}</h2>
|
|
1603
|
+
{block.richText ? <PortableText value={block.richText} /> : null}
|
|
1604
|
+
<Buttons items={block.buttons} />
|
|
1222
1605
|
</section>
|
|
1223
1606
|
);
|
|
1224
1607
|
default:
|
|
@@ -1236,34 +1619,9 @@ export function PageBuilder({ blocks }: { blocks: PageBlock[] }) {
|
|
|
1236
1619
|
</>
|
|
1237
1620
|
);
|
|
1238
1621
|
}
|
|
1239
|
-
`,
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
// build-time \`prebuild\` hook works on a fresh clone.
|
|
1243
|
-
export default defineCliConfig({
|
|
1244
|
-
api: {
|
|
1245
|
-
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || "placeholder",
|
|
1246
|
-
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || "production",
|
|
1247
|
-
},
|
|
1248
|
-
typegen: {
|
|
1249
|
-
path: "./sanity/**/*.{ts,tsx}",
|
|
1250
|
-
schema: "sanity/schema.json",
|
|
1251
|
-
generates: "sanity/sanity.types.ts",
|
|
1252
|
-
},
|
|
1253
|
-
});
|
|
1254
|
-
`, NEXT_STUDIO_PAGE = `import { NextStudio } from "next-sanity/studio";
|
|
1255
|
-
import config from "../../../sanity.config";
|
|
1256
|
-
|
|
1257
|
-
export const dynamic = "force-static";
|
|
1258
|
-
|
|
1259
|
-
export { metadata, viewport } from "next-sanity/studio";
|
|
1260
|
-
|
|
1261
|
-
export default function StudioPage() {
|
|
1262
|
-
return <NextStudio config={config} />;
|
|
1263
|
-
}
|
|
1264
|
-
`, NEXT_DRAFT_MODE_ROUTE = `import { defineEnableDraftMode } from "next-sanity/draft-mode";
|
|
1265
|
-
import { readToken } from "../../../../sanity/env";
|
|
1266
|
-
import { client } from "../../../../sanity/client";
|
|
1622
|
+
`, NEXT_DRAFT_MODE_ROUTE = `import { client } from "@workspace/sanity/client";
|
|
1623
|
+
import { readToken } from "@workspace/sanity/env";
|
|
1624
|
+
import { defineEnableDraftMode } from "next-sanity/draft-mode";
|
|
1267
1625
|
|
|
1268
1626
|
export const { GET } = defineEnableDraftMode({
|
|
1269
1627
|
client: client.withConfig({ token: readToken }),
|
|
@@ -1301,10 +1659,10 @@ export function DisableDraftMode() {
|
|
|
1301
1659
|
</a>
|
|
1302
1660
|
);
|
|
1303
1661
|
}
|
|
1304
|
-
`, NEXT_HOME_PAGE = `import
|
|
1305
|
-
import {
|
|
1306
|
-
import { PageBuilder, type PageBlock } from "
|
|
1307
|
-
import {
|
|
1662
|
+
`, NEXT_HOME_PAGE = `import { loadQuery } from "@workspace/sanity/fetch";
|
|
1663
|
+
import { HOME_PAGE_QUERY, SETTINGS_QUERY } from "@workspace/sanity/queries";
|
|
1664
|
+
import { PageBuilder, type PageBlock } from "@workspace/sanity-blocks/blocks";
|
|
1665
|
+
import type { Metadata } from "next";
|
|
1308
1666
|
|
|
1309
1667
|
// stega: false — encoded metadata would leak invisible characters into <title>.
|
|
1310
1668
|
export async function generateMetadata(): Promise<Metadata> {
|
|
@@ -1324,7 +1682,8 @@ export default async function HomePage() {
|
|
|
1324
1682
|
<h1>No content yet</h1>
|
|
1325
1683
|
<p>
|
|
1326
1684
|
Set NEXT_PUBLIC_SANITY_PROJECT_ID in apps/web/.env.local, then seed the dataset:
|
|
1327
|
-
<code> pnpm sanity:seed</code> from apps/
|
|
1685
|
+
<code> pnpm sanity:seed</code> from apps/studio. Edit it in the Studio:
|
|
1686
|
+
<code> pnpm dev:studio</code>.
|
|
1328
1687
|
</p>
|
|
1329
1688
|
</main>
|
|
1330
1689
|
);
|
|
@@ -1337,10 +1696,10 @@ export default async function HomePage() {
|
|
|
1337
1696
|
</main>
|
|
1338
1697
|
);
|
|
1339
1698
|
}
|
|
1340
|
-
`, NEXT_SLUG_PAGE = `import {
|
|
1341
|
-
import {
|
|
1342
|
-
import { PageBuilder, type PageBlock } from "
|
|
1343
|
-
import {
|
|
1699
|
+
`, NEXT_SLUG_PAGE = `import { loadQuery, loadStaticQuery } from "@workspace/sanity/fetch";
|
|
1700
|
+
import { PAGE_QUERY, PAGE_SLUGS_QUERY } from "@workspace/sanity/queries";
|
|
1701
|
+
import { PageBuilder, type PageBlock } from "@workspace/sanity-blocks/blocks";
|
|
1702
|
+
import { notFound } from "next/navigation";
|
|
1344
1703
|
|
|
1345
1704
|
export async function generateStaticParams() {
|
|
1346
1705
|
const slugs = await loadStaticQuery(PAGE_SLUGS_QUERY);
|
|
@@ -1359,193 +1718,1877 @@ export default async function Page({ params }: { params: Promise<{ slug: string
|
|
|
1359
1718
|
</main>
|
|
1360
1719
|
);
|
|
1361
1720
|
}
|
|
1362
|
-
`,
|
|
1721
|
+
`, NEXT_BLOG_INDEX_PAGE = `import { loadQuery } from "@workspace/sanity/fetch";
|
|
1722
|
+
import { urlFor } from "@workspace/sanity/image";
|
|
1723
|
+
import { BLOG_INDEX_QUERY } from "@workspace/sanity/queries";
|
|
1724
|
+
import type { Metadata } from "next";
|
|
1725
|
+
import Image from "next/image";
|
|
1363
1726
|
|
|
1364
|
-
export const
|
|
1727
|
+
export const metadata: Metadata = { title: "Blog" };
|
|
1365
1728
|
|
|
1366
|
-
|
|
1367
|
-
|
|
1729
|
+
export default async function BlogIndexPage() {
|
|
1730
|
+
const posts = (await loadQuery(BLOG_INDEX_QUERY)) ?? [];
|
|
1368
1731
|
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1732
|
+
return (
|
|
1733
|
+
<main style={{ fontFamily: "system-ui, sans-serif", padding: "3rem" }}>
|
|
1734
|
+
<h1>Blog</h1>
|
|
1735
|
+
{posts.length === 0 ? (
|
|
1736
|
+
<p>
|
|
1737
|
+
No posts yet — seed the dataset with <code>pnpm sanity:seed</code> from apps/studio,
|
|
1738
|
+
or write one in the Studio (<code>pnpm dev:studio</code>).
|
|
1739
|
+
</p>
|
|
1740
|
+
) : null}
|
|
1741
|
+
{posts.map((post) => {
|
|
1742
|
+
const src = urlFor(post.image, 800);
|
|
1743
|
+
const author = post.authors?.[0];
|
|
1744
|
+
return (
|
|
1745
|
+
<article key={post._id} style={{ borderTop: "1px solid #e5e5e5", padding: "1.5rem 0" }}>
|
|
1746
|
+
{src ? <Image src={src} alt={post.image?.alt ?? ""} width={800} height={450} /> : null}
|
|
1747
|
+
<h2>
|
|
1748
|
+
<a href={\`/blog/\${post.slug}\`}>{post.title}</a>
|
|
1749
|
+
</h2>
|
|
1750
|
+
<p style={{ opacity: 0.6 }}>
|
|
1751
|
+
{author?.name ? <span>{author.name}</span> : null}
|
|
1752
|
+
{author?.name && post.publishedAt ? " · " : null}
|
|
1753
|
+
{post.publishedAt ? <time dateTime={post.publishedAt}>{post.publishedAt}</time> : null}
|
|
1754
|
+
</p>
|
|
1755
|
+
{post.description ? <p>{post.description}</p> : null}
|
|
1756
|
+
</article>
|
|
1757
|
+
);
|
|
1758
|
+
})}
|
|
1759
|
+
</main>
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
`, NEXT_BLOG_POST_PAGE = `import { PortableText } from "@portabletext/react";
|
|
1763
|
+
import { loadQuery, loadStaticQuery } from "@workspace/sanity/fetch";
|
|
1764
|
+
import { urlFor } from "@workspace/sanity/image";
|
|
1765
|
+
import { BLOG_QUERY, BLOG_SLUGS_QUERY } from "@workspace/sanity/queries";
|
|
1766
|
+
import type { Metadata } from "next";
|
|
1767
|
+
import Image from "next/image";
|
|
1768
|
+
import { notFound } from "next/navigation";
|
|
1375
1769
|
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1770
|
+
export async function generateStaticParams() {
|
|
1771
|
+
const slugs = await loadStaticQuery(BLOG_SLUGS_QUERY);
|
|
1772
|
+
return (slugs ?? []).filter((slug): slug is string => Boolean(slug)).map((slug) => ({ slug }));
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// stega: false — encoded metadata would leak invisible characters into <title>.
|
|
1776
|
+
export async function generateMetadata({
|
|
1777
|
+
params,
|
|
1778
|
+
}: {
|
|
1779
|
+
params: Promise<{ slug: string }>;
|
|
1780
|
+
}): Promise<Metadata> {
|
|
1781
|
+
const { slug } = await params;
|
|
1782
|
+
const post = await loadQuery(BLOG_QUERY, { slug }, { stega: false });
|
|
1783
|
+
return { title: post?.title ?? "Blog", description: post?.description ?? undefined };
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
export default async function BlogPostPage({ params }: { params: Promise<{ slug: string }> }) {
|
|
1787
|
+
const { slug } = await params;
|
|
1788
|
+
const post = await loadQuery(BLOG_QUERY, { slug });
|
|
1789
|
+
if (!post) notFound();
|
|
1790
|
+
|
|
1791
|
+
const src = urlFor(post.image, 1600);
|
|
1792
|
+
const author = post.authors?.[0];
|
|
1793
|
+
|
|
1794
|
+
return (
|
|
1795
|
+
<main style={{ fontFamily: "system-ui, sans-serif", padding: "3rem" }}>
|
|
1796
|
+
<p>
|
|
1797
|
+
<a href="/blog">← Blog</a>
|
|
1798
|
+
</p>
|
|
1799
|
+
<h1>{post.title}</h1>
|
|
1800
|
+
<p style={{ opacity: 0.6 }}>
|
|
1801
|
+
{author?.name ? <span>{author.position ? \`\${author.name}, \${author.position}\` : author.name}</span> : null}
|
|
1802
|
+
{author?.name && post.publishedAt ? " · " : null}
|
|
1803
|
+
{post.publishedAt ? <time dateTime={post.publishedAt}>{post.publishedAt}</time> : null}
|
|
1804
|
+
</p>
|
|
1805
|
+
{src ? <Image src={src} alt={post.image?.alt ?? ""} width={1600} height={900} /> : null}
|
|
1806
|
+
{post.richText ? <PortableText value={post.richText} /> : null}
|
|
1807
|
+
</main>
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
`;
|
|
1811
|
+
|
|
1812
|
+
// ../../packages/registry/src/content/sanity/schema.ts
|
|
1813
|
+
var SCHEMA_DEFINITIONS = `import { defineArrayMember, defineField, defineType } from "sanity";
|
|
1383
1814
|
|
|
1384
1815
|
/**
|
|
1385
|
-
*
|
|
1386
|
-
*
|
|
1387
|
-
|
|
1816
|
+
* A link that resolves to either an internal document or an external URL. Blocks store the
|
|
1817
|
+
* choice; queries.ts turns it into a plain \`href\` string so components never branch on it.
|
|
1818
|
+
*/
|
|
1819
|
+
export const customUrl = defineType({
|
|
1820
|
+
name: "customUrl",
|
|
1821
|
+
title: "URL",
|
|
1822
|
+
type: "object",
|
|
1823
|
+
fields: [
|
|
1824
|
+
defineField({
|
|
1825
|
+
name: "type",
|
|
1826
|
+
type: "string",
|
|
1827
|
+
initialValue: () => "external",
|
|
1828
|
+
options: { list: ["internal", "external"], layout: "radio", direction: "horizontal" },
|
|
1829
|
+
validation: (rule) => rule.required(),
|
|
1830
|
+
}),
|
|
1831
|
+
defineField({
|
|
1832
|
+
name: "openInNewTab",
|
|
1833
|
+
title: "Open in new tab",
|
|
1834
|
+
type: "boolean",
|
|
1835
|
+
initialValue: () => false,
|
|
1836
|
+
}),
|
|
1837
|
+
defineField({
|
|
1838
|
+
name: "external",
|
|
1839
|
+
title: "URL",
|
|
1840
|
+
type: "string",
|
|
1841
|
+
description: "A full address (https://…) or a path (/about).",
|
|
1842
|
+
hidden: ({ parent }) => parent?.type !== "external",
|
|
1843
|
+
validation: (rule) =>
|
|
1844
|
+
rule.custom((value, { parent }) =>
|
|
1845
|
+
(parent as { type?: string })?.type === "external" && !value
|
|
1846
|
+
? "URL can't be empty"
|
|
1847
|
+
: true,
|
|
1848
|
+
),
|
|
1849
|
+
}),
|
|
1850
|
+
defineField({
|
|
1851
|
+
name: "internal",
|
|
1852
|
+
type: "reference",
|
|
1853
|
+
to: [{ type: "page" }, { type: "blog" }],
|
|
1854
|
+
options: { disableNew: true },
|
|
1855
|
+
hidden: ({ parent }) => parent?.type !== "internal",
|
|
1856
|
+
validation: (rule) =>
|
|
1857
|
+
rule.custom((value, { parent }) =>
|
|
1858
|
+
(parent as { type?: string })?.type === "internal" && !value?._ref
|
|
1859
|
+
? "Pick a document to link to"
|
|
1860
|
+
: true,
|
|
1861
|
+
),
|
|
1862
|
+
}),
|
|
1863
|
+
],
|
|
1864
|
+
preview: {
|
|
1865
|
+
select: { type: "type", external: "external", internal: "internal.slug.current" },
|
|
1866
|
+
prepare: ({ type, external, internal }) => ({
|
|
1867
|
+
title: type === "external" ? "External link" : "Internal link",
|
|
1868
|
+
subtitle: type === "external" ? external : internal,
|
|
1869
|
+
}),
|
|
1870
|
+
},
|
|
1871
|
+
});
|
|
1872
|
+
|
|
1873
|
+
export const button = defineType({
|
|
1874
|
+
name: "button",
|
|
1875
|
+
title: "Button",
|
|
1876
|
+
type: "object",
|
|
1877
|
+
fields: [
|
|
1878
|
+
defineField({
|
|
1879
|
+
name: "variant",
|
|
1880
|
+
type: "string",
|
|
1881
|
+
initialValue: () => "default",
|
|
1882
|
+
options: {
|
|
1883
|
+
list: ["default", "secondary", "outline", "link"],
|
|
1884
|
+
layout: "radio",
|
|
1885
|
+
direction: "horizontal",
|
|
1886
|
+
},
|
|
1887
|
+
}),
|
|
1888
|
+
defineField({ name: "text", title: "Button text", type: "string" }),
|
|
1889
|
+
defineField({ name: "url", title: "URL", type: "customUrl" }),
|
|
1890
|
+
],
|
|
1891
|
+
preview: {
|
|
1892
|
+
select: { title: "text", variant: "variant" },
|
|
1893
|
+
prepare: ({ title, variant }) => ({
|
|
1894
|
+
title: title ?? "Untitled button",
|
|
1895
|
+
subtitle: variant ?? "default",
|
|
1896
|
+
}),
|
|
1897
|
+
},
|
|
1898
|
+
});
|
|
1899
|
+
|
|
1900
|
+
/**
|
|
1901
|
+
* Portable Text. Registered as a named type so documents and blocks can say
|
|
1902
|
+
* \`type: "richText"\` — the same indirection the reference uses.
|
|
1903
|
+
*/
|
|
1904
|
+
export const richText = defineType({
|
|
1905
|
+
name: "richText",
|
|
1906
|
+
type: "array",
|
|
1907
|
+
of: [
|
|
1908
|
+
defineArrayMember({
|
|
1909
|
+
name: "block",
|
|
1910
|
+
type: "block",
|
|
1911
|
+
styles: [
|
|
1912
|
+
{ title: "Normal", value: "normal" },
|
|
1913
|
+
{ title: "H2", value: "h2" },
|
|
1914
|
+
{ title: "H3", value: "h3" },
|
|
1915
|
+
{ title: "H4", value: "h4" },
|
|
1916
|
+
],
|
|
1917
|
+
lists: [
|
|
1918
|
+
{ title: "Bullet", value: "bullet" },
|
|
1919
|
+
{ title: "Numbered", value: "number" },
|
|
1920
|
+
],
|
|
1921
|
+
marks: {
|
|
1922
|
+
decorators: [
|
|
1923
|
+
{ title: "Strong", value: "strong" },
|
|
1924
|
+
{ title: "Emphasis", value: "em" },
|
|
1925
|
+
{ title: "Code", value: "code" },
|
|
1926
|
+
],
|
|
1927
|
+
annotations: [
|
|
1928
|
+
{
|
|
1929
|
+
name: "customLink",
|
|
1930
|
+
title: "Link",
|
|
1931
|
+
type: "object",
|
|
1932
|
+
fields: [defineField({ name: "customLink", type: "customUrl" })],
|
|
1933
|
+
},
|
|
1934
|
+
],
|
|
1935
|
+
},
|
|
1936
|
+
}),
|
|
1937
|
+
defineArrayMember({
|
|
1938
|
+
name: "image",
|
|
1939
|
+
title: "Image",
|
|
1940
|
+
type: "image",
|
|
1941
|
+
options: { hotspot: true },
|
|
1942
|
+
fields: [defineField({ name: "caption", title: "Caption", type: "string" })],
|
|
1943
|
+
}),
|
|
1944
|
+
],
|
|
1945
|
+
});
|
|
1946
|
+
`, SCHEMA_BLOCKS = `import { defineArrayMember, defineField, defineType } from "sanity";
|
|
1947
|
+
|
|
1948
|
+
// Page-builder blocks are objects, not documents: they only exist inside a page.
|
|
1949
|
+
// Field names match turbo-start-sanity's, so its components drop in unchanged.
|
|
1950
|
+
|
|
1951
|
+
const imageField = defineField({
|
|
1952
|
+
name: "image",
|
|
1953
|
+
type: "image",
|
|
1954
|
+
options: { hotspot: true },
|
|
1955
|
+
fields: [
|
|
1956
|
+
defineField({
|
|
1957
|
+
name: "alt",
|
|
1958
|
+
title: "Alt text",
|
|
1959
|
+
type: "string",
|
|
1960
|
+
validation: (rule) =>
|
|
1961
|
+
rule.custom((value, context) =>
|
|
1962
|
+
(context.parent as { asset?: unknown })?.asset && !value?.trim()
|
|
1963
|
+
? "Alt text is required when an image is set"
|
|
1964
|
+
: true,
|
|
1965
|
+
),
|
|
1966
|
+
}),
|
|
1967
|
+
],
|
|
1968
|
+
});
|
|
1969
|
+
|
|
1970
|
+
const buttonsField = defineField({
|
|
1971
|
+
name: "buttons",
|
|
1972
|
+
type: "array",
|
|
1973
|
+
of: [defineArrayMember({ type: "button" })],
|
|
1974
|
+
});
|
|
1975
|
+
|
|
1976
|
+
export const hero = defineType({
|
|
1977
|
+
name: "hero",
|
|
1978
|
+
title: "Hero",
|
|
1979
|
+
type: "object",
|
|
1980
|
+
fields: [
|
|
1981
|
+
defineField({ name: "badge", type: "string" }),
|
|
1982
|
+
defineField({ name: "title", type: "string" }),
|
|
1983
|
+
defineField({ name: "richText", type: "richText" }),
|
|
1984
|
+
imageField,
|
|
1985
|
+
buttonsField,
|
|
1986
|
+
],
|
|
1987
|
+
preview: {
|
|
1988
|
+
select: { title: "title", media: "image" },
|
|
1989
|
+
prepare: ({ title, media }) => ({ title: title ?? "Hero", subtitle: "Hero", media }),
|
|
1990
|
+
},
|
|
1991
|
+
});
|
|
1992
|
+
|
|
1993
|
+
export const cta = defineType({
|
|
1994
|
+
name: "cta",
|
|
1995
|
+
title: "Call to action",
|
|
1996
|
+
type: "object",
|
|
1997
|
+
fields: [
|
|
1998
|
+
defineField({ name: "eyebrow", type: "string" }),
|
|
1999
|
+
defineField({ name: "title", type: "string" }),
|
|
2000
|
+
defineField({ name: "richText", type: "richText" }),
|
|
2001
|
+
buttonsField,
|
|
2002
|
+
],
|
|
2003
|
+
preview: {
|
|
2004
|
+
select: { title: "title" },
|
|
2005
|
+
prepare: ({ title }) => ({ title: title ?? "Call to action", subtitle: "CTA" }),
|
|
2006
|
+
},
|
|
2007
|
+
});
|
|
2008
|
+
|
|
2009
|
+
export const richTextBlock = defineType({
|
|
2010
|
+
name: "richTextBlock",
|
|
2011
|
+
title: "Rich text",
|
|
2012
|
+
type: "object",
|
|
2013
|
+
fields: [
|
|
2014
|
+
defineField({ name: "eyebrow", type: "string" }),
|
|
2015
|
+
defineField({ name: "title", type: "string" }),
|
|
2016
|
+
defineField({ name: "richText", type: "richText" }),
|
|
2017
|
+
],
|
|
2018
|
+
preview: {
|
|
2019
|
+
select: { title: "title" },
|
|
2020
|
+
prepare: ({ title }) => ({ title: title ?? "Rich text", subtitle: "Rich text" }),
|
|
2021
|
+
},
|
|
2022
|
+
});
|
|
2023
|
+
|
|
2024
|
+
// The reference stores \`icon\` as a \`lucide-icon\`, a custom input from
|
|
2025
|
+
// sanity-plugin-lucide-icon-picker. A plain string keeps the field (and the GROQ) the same
|
|
2026
|
+
// shape without a Studio plugin — add the plugin later and only this field changes.
|
|
2027
|
+
const featureCardIcon = defineArrayMember({
|
|
2028
|
+
name: "featureCardIcon",
|
|
2029
|
+
type: "object",
|
|
2030
|
+
fields: [
|
|
2031
|
+
defineField({
|
|
2032
|
+
name: "icon",
|
|
2033
|
+
type: "string",
|
|
2034
|
+
description: 'A Lucide icon name, e.g. "zap" or "shield-check".',
|
|
2035
|
+
}),
|
|
2036
|
+
defineField({ name: "title", type: "string" }),
|
|
2037
|
+
defineField({ name: "richText", type: "richText" }),
|
|
2038
|
+
],
|
|
2039
|
+
preview: { select: { title: "title", subtitle: "icon" } },
|
|
2040
|
+
});
|
|
2041
|
+
|
|
2042
|
+
export const featureCardsIcon = defineType({
|
|
2043
|
+
name: "featureCardsIcon",
|
|
2044
|
+
title: "Feature cards",
|
|
2045
|
+
type: "object",
|
|
2046
|
+
fields: [
|
|
2047
|
+
defineField({ name: "eyebrow", type: "string" }),
|
|
2048
|
+
defineField({ name: "title", type: "string" }),
|
|
2049
|
+
defineField({ name: "richText", type: "richText" }),
|
|
2050
|
+
defineField({ name: "cards", type: "array", of: [featureCardIcon] }),
|
|
2051
|
+
],
|
|
2052
|
+
preview: {
|
|
2053
|
+
select: { title: "title", cards: "cards" },
|
|
2054
|
+
prepare: ({ title, cards }) => ({
|
|
2055
|
+
title: title ?? "Feature cards",
|
|
2056
|
+
subtitle: \`\${cards?.length ?? 0} cards\`,
|
|
2057
|
+
}),
|
|
2058
|
+
},
|
|
2059
|
+
});
|
|
2060
|
+
|
|
2061
|
+
const imageLinkCard = defineArrayMember({
|
|
2062
|
+
name: "imageLinkCard",
|
|
2063
|
+
type: "object",
|
|
2064
|
+
fields: [
|
|
2065
|
+
defineField({ name: "title", type: "string", validation: (rule) => rule.required() }),
|
|
2066
|
+
defineField({ name: "description", type: "text", rows: 2 }),
|
|
2067
|
+
imageField,
|
|
2068
|
+
defineField({ name: "url", title: "Link", type: "customUrl" }),
|
|
2069
|
+
],
|
|
2070
|
+
preview: { select: { title: "title", subtitle: "description", media: "image" } },
|
|
2071
|
+
});
|
|
2072
|
+
|
|
2073
|
+
export const imageLinkCards = defineType({
|
|
2074
|
+
name: "imageLinkCards",
|
|
2075
|
+
title: "Image link cards",
|
|
2076
|
+
type: "object",
|
|
2077
|
+
fields: [
|
|
2078
|
+
defineField({ name: "eyebrow", type: "string" }),
|
|
2079
|
+
defineField({ name: "title", type: "string", validation: (rule) => rule.required() }),
|
|
2080
|
+
defineField({ name: "richText", type: "richText" }),
|
|
2081
|
+
buttonsField,
|
|
2082
|
+
defineField({ name: "cards", type: "array", of: [imageLinkCard] }),
|
|
2083
|
+
],
|
|
2084
|
+
preview: {
|
|
2085
|
+
select: { title: "title", cards: "cards" },
|
|
2086
|
+
prepare: ({ title, cards }) => ({
|
|
2087
|
+
title: title ?? "Image link cards",
|
|
2088
|
+
subtitle: \`\${cards?.length ?? 0} cards\`,
|
|
2089
|
+
}),
|
|
2090
|
+
},
|
|
2091
|
+
});
|
|
2092
|
+
|
|
2093
|
+
export const faqAccordion = defineType({
|
|
2094
|
+
name: "faqAccordion",
|
|
2095
|
+
title: "FAQ accordion",
|
|
2096
|
+
type: "object",
|
|
2097
|
+
fields: [
|
|
2098
|
+
defineField({ name: "eyebrow", type: "string" }),
|
|
2099
|
+
defineField({ name: "title", type: "string", validation: (rule) => rule.required() }),
|
|
2100
|
+
defineField({ name: "subtitle", type: "string" }),
|
|
2101
|
+
defineField({
|
|
2102
|
+
name: "faqs",
|
|
2103
|
+
title: "FAQs",
|
|
2104
|
+
type: "array",
|
|
2105
|
+
of: [defineArrayMember({ type: "reference", to: [{ type: "faq" }] })],
|
|
2106
|
+
validation: (rule) => rule.unique(),
|
|
2107
|
+
}),
|
|
2108
|
+
],
|
|
2109
|
+
preview: {
|
|
2110
|
+
select: { title: "title", faqs: "faqs" },
|
|
2111
|
+
prepare: ({ title, faqs }) => ({
|
|
2112
|
+
title: title ?? "FAQ accordion",
|
|
2113
|
+
subtitle: \`\${faqs?.length ?? 0} questions\`,
|
|
2114
|
+
}),
|
|
2115
|
+
},
|
|
2116
|
+
});
|
|
2117
|
+
|
|
2118
|
+
export const subscribeNewsletter = defineType({
|
|
2119
|
+
name: "subscribeNewsletter",
|
|
2120
|
+
title: "Subscribe newsletter",
|
|
2121
|
+
type: "object",
|
|
2122
|
+
fields: [
|
|
2123
|
+
defineField({ name: "title", type: "string" }),
|
|
2124
|
+
defineField({ name: "subTitle", title: "Subtitle", type: "richText" }),
|
|
2125
|
+
defineField({ name: "helperText", title: "Helper text", type: "richText" }),
|
|
2126
|
+
],
|
|
2127
|
+
preview: {
|
|
2128
|
+
select: { title: "title" },
|
|
2129
|
+
prepare: ({ title }) => ({ title: title ?? "Newsletter", subtitle: "Newsletter" }),
|
|
2130
|
+
},
|
|
2131
|
+
});
|
|
2132
|
+
|
|
2133
|
+
/**
|
|
2134
|
+
* Every page-builder block, in the order the \`page\` document offers them. The Studio
|
|
2135
|
+
* imports this and composes it with its own document schemas — one list to keep in step
|
|
2136
|
+
* with the renderers that live beside it, instead of two.
|
|
2137
|
+
*/
|
|
2138
|
+
export const blockSchemas = [
|
|
2139
|
+
hero,
|
|
2140
|
+
richTextBlock,
|
|
2141
|
+
featureCardsIcon,
|
|
2142
|
+
imageLinkCards,
|
|
2143
|
+
faqAccordion,
|
|
2144
|
+
subscribeNewsletter,
|
|
2145
|
+
cta,
|
|
2146
|
+
];
|
|
2147
|
+
`, SCHEMA_PAGE = `import { defineArrayMember, defineField, defineType } from "sanity";
|
|
2148
|
+
|
|
2149
|
+
export const page = defineType({
|
|
2150
|
+
name: "page",
|
|
2151
|
+
title: "Page",
|
|
2152
|
+
type: "document",
|
|
2153
|
+
fields: [
|
|
2154
|
+
defineField({ name: "title", type: "string", validation: (rule) => rule.required() }),
|
|
2155
|
+
defineField({
|
|
2156
|
+
name: "slug",
|
|
2157
|
+
type: "slug",
|
|
2158
|
+
options: { source: "title" },
|
|
2159
|
+
validation: (rule) => rule.required(),
|
|
2160
|
+
}),
|
|
2161
|
+
defineField({ name: "description", type: "text", rows: 3 }),
|
|
2162
|
+
defineField({ name: "image", type: "image", options: { hotspot: true } }),
|
|
2163
|
+
defineField({
|
|
2164
|
+
name: "pageBuilder",
|
|
2165
|
+
title: "Page builder",
|
|
2166
|
+
type: "array",
|
|
2167
|
+
of: [
|
|
2168
|
+
defineArrayMember({ type: "hero" }),
|
|
2169
|
+
defineArrayMember({ type: "richTextBlock" }),
|
|
2170
|
+
defineArrayMember({ type: "featureCardsIcon" }),
|
|
2171
|
+
defineArrayMember({ type: "imageLinkCards" }),
|
|
2172
|
+
defineArrayMember({ type: "faqAccordion" }),
|
|
2173
|
+
defineArrayMember({ type: "subscribeNewsletter" }),
|
|
2174
|
+
defineArrayMember({ type: "cta" }),
|
|
2175
|
+
],
|
|
2176
|
+
}),
|
|
2177
|
+
],
|
|
2178
|
+
// The home page is the \`page\` whose slug is "home" — see sanity/queries.ts.
|
|
2179
|
+
preview: { select: { title: "title", subtitle: "slug.current", media: "image" } },
|
|
2180
|
+
});
|
|
2181
|
+
`, SCHEMA_BLOG = `import { defineArrayMember, defineField, defineType } from "sanity";
|
|
2182
|
+
|
|
2183
|
+
export const author = defineType({
|
|
2184
|
+
name: "author",
|
|
2185
|
+
title: "Author",
|
|
2186
|
+
type: "document",
|
|
2187
|
+
fields: [
|
|
2188
|
+
defineField({ name: "name", type: "string", validation: (rule) => rule.required() }),
|
|
2189
|
+
defineField({ name: "position", type: "string" }),
|
|
2190
|
+
defineField({ name: "image", type: "image", options: { hotspot: true } }),
|
|
2191
|
+
defineField({ name: "bio", type: "text", rows: 3 }),
|
|
2192
|
+
],
|
|
2193
|
+
preview: { select: { title: "name", subtitle: "position", media: "image" } },
|
|
2194
|
+
});
|
|
2195
|
+
|
|
2196
|
+
export const blog = defineType({
|
|
2197
|
+
name: "blog",
|
|
2198
|
+
title: "Blog post",
|
|
2199
|
+
type: "document",
|
|
2200
|
+
fields: [
|
|
2201
|
+
defineField({ name: "title", type: "string", validation: (rule) => rule.required() }),
|
|
2202
|
+
defineField({
|
|
2203
|
+
name: "slug",
|
|
2204
|
+
type: "slug",
|
|
2205
|
+
options: { source: "title" },
|
|
2206
|
+
validation: (rule) => rule.required(),
|
|
2207
|
+
}),
|
|
2208
|
+
defineField({ name: "description", type: "text", rows: 3 }),
|
|
2209
|
+
defineField({
|
|
2210
|
+
name: "authors",
|
|
2211
|
+
type: "array",
|
|
2212
|
+
of: [
|
|
2213
|
+
defineArrayMember({
|
|
2214
|
+
type: "reference",
|
|
2215
|
+
to: [{ type: "author" }],
|
|
2216
|
+
options: { disableNew: true },
|
|
2217
|
+
}),
|
|
2218
|
+
],
|
|
2219
|
+
validation: (rule) => rule.unique().max(1),
|
|
2220
|
+
}),
|
|
2221
|
+
defineField({
|
|
2222
|
+
name: "publishedAt",
|
|
2223
|
+
title: "Published at",
|
|
2224
|
+
type: "date",
|
|
2225
|
+
// slice, not split("T")[0] — under noUncheckedIndexedAccess the index gives
|
|
2226
|
+
// \`string | undefined\` and initialValue will not take it.
|
|
2227
|
+
initialValue: () => new Date().toISOString().slice(0, 10),
|
|
2228
|
+
}),
|
|
2229
|
+
// Optional, unlike the reference's required image: seeded posts ship without image
|
|
2230
|
+
// assets, and the renderers have to handle a post with no image regardless.
|
|
2231
|
+
defineField({
|
|
2232
|
+
name: "image",
|
|
2233
|
+
type: "image",
|
|
2234
|
+
options: { hotspot: true },
|
|
2235
|
+
fields: [defineField({ name: "alt", title: "Alt text", type: "string" })],
|
|
2236
|
+
}),
|
|
2237
|
+
defineField({ name: "richText", title: "Content", type: "richText" }),
|
|
2238
|
+
],
|
|
2239
|
+
preview: {
|
|
2240
|
+
select: { title: "title", author: "authors.0.name", date: "publishedAt", media: "image" },
|
|
2241
|
+
prepare: ({ title, author, date, media }) => ({
|
|
2242
|
+
title: title ?? "Untitled post",
|
|
2243
|
+
subtitle: [author, date].filter(Boolean).join(" · "),
|
|
2244
|
+
media,
|
|
2245
|
+
}),
|
|
2246
|
+
},
|
|
2247
|
+
});
|
|
2248
|
+
|
|
2249
|
+
export const faq = defineType({
|
|
2250
|
+
name: "faq",
|
|
2251
|
+
title: "FAQ",
|
|
2252
|
+
type: "document",
|
|
2253
|
+
fields: [
|
|
2254
|
+
defineField({
|
|
2255
|
+
name: "title",
|
|
2256
|
+
title: "Question",
|
|
2257
|
+
type: "string",
|
|
2258
|
+
validation: (rule) => rule.required(),
|
|
2259
|
+
}),
|
|
2260
|
+
defineField({ name: "richText", title: "Answer", type: "richText" }),
|
|
2261
|
+
],
|
|
2262
|
+
preview: { select: { title: "title" } },
|
|
2263
|
+
});
|
|
2264
|
+
`, SCHEMA_SETTINGS = `import { defineField, defineType } from "sanity";
|
|
2265
|
+
|
|
2266
|
+
export const settings = defineType({
|
|
2267
|
+
name: "settings",
|
|
2268
|
+
title: "Settings",
|
|
2269
|
+
type: "document",
|
|
2270
|
+
fields: [
|
|
2271
|
+
defineField({ name: "siteTitle", type: "string", validation: (rule) => rule.required() }),
|
|
2272
|
+
defineField({ name: "siteDescription", type: "text", rows: 3 }),
|
|
2273
|
+
defineField({
|
|
2274
|
+
name: "socialImage",
|
|
2275
|
+
title: "Social share image",
|
|
2276
|
+
type: "image",
|
|
2277
|
+
options: { hotspot: true },
|
|
2278
|
+
}),
|
|
2279
|
+
],
|
|
2280
|
+
preview: { select: { title: "siteTitle", media: "socialImage" } },
|
|
2281
|
+
});
|
|
2282
|
+
`, SCHEMA_INDEX = `import { blockSchemas } from "@workspace/sanity-blocks";
|
|
2283
|
+
import type { SchemaTypeDefinition } from "sanity";
|
|
2284
|
+
import { author, blog, faq } from "./blog";
|
|
2285
|
+
import { button, customUrl, richText } from "./definitions";
|
|
2286
|
+
import { page } from "./page";
|
|
2287
|
+
import { settings } from "./settings";
|
|
2288
|
+
|
|
2289
|
+
export const schemaTypes: SchemaTypeDefinition[] = [
|
|
2290
|
+
// Documents
|
|
2291
|
+
page,
|
|
2292
|
+
blog,
|
|
2293
|
+
author,
|
|
2294
|
+
faq,
|
|
2295
|
+
settings,
|
|
2296
|
+
// Reusable definitions
|
|
2297
|
+
customUrl,
|
|
2298
|
+
button,
|
|
2299
|
+
richText,
|
|
2300
|
+
// Page-builder blocks — declared in @workspace/sanity-blocks, next to their renderers.
|
|
2301
|
+
...blockSchemas,
|
|
2302
|
+
];
|
|
2303
|
+
`, STRUCTURE = `import type { StructureResolver } from "sanity/structure";
|
|
2304
|
+
|
|
2305
|
+
/**
|
|
2306
|
+
* \`settings\` is a singleton: one document with a fixed id, edited in place instead of
|
|
2307
|
+
* listed. Everything else falls through to the default document-type lists.
|
|
1388
2308
|
*
|
|
1389
|
-
*
|
|
1390
|
-
*
|
|
1391
|
-
|
|
2309
|
+
* The two sets feed sanity.config.ts's \`document.newDocumentOptions\` / \`document.actions\`
|
|
2310
|
+
* guards (the official singleton recipe), so a second settings document cannot be created.
|
|
2311
|
+
*/
|
|
2312
|
+
export const singletonTypes = new Set(["settings"]);
|
|
2313
|
+
|
|
2314
|
+
/** Actions that keep a singleton editable without allowing create/delete/duplicate. */
|
|
2315
|
+
export const singletonActions = new Set(["publish", "discardChanges", "restore"]);
|
|
2316
|
+
|
|
2317
|
+
export const structure: StructureResolver = (S) =>
|
|
2318
|
+
S.list()
|
|
2319
|
+
.title("Content")
|
|
2320
|
+
.items([
|
|
2321
|
+
S.listItem()
|
|
2322
|
+
.title("Settings")
|
|
2323
|
+
.id("settings")
|
|
2324
|
+
.child(S.document().schemaType("settings").documentId("settings")),
|
|
2325
|
+
S.divider(),
|
|
2326
|
+
S.documentTypeListItem("page").title("Pages"),
|
|
2327
|
+
S.documentTypeListItem("blog").title("Blog posts"),
|
|
2328
|
+
S.documentTypeListItem("author").title("Authors"),
|
|
2329
|
+
S.documentTypeListItem("faq").title("FAQs"),
|
|
2330
|
+
]);
|
|
2331
|
+
`, PRESENTATION = `import { defineDocuments, defineLocations } from "sanity/presentation";
|
|
2332
|
+
|
|
2333
|
+
/** Route → document: lets Presentation open the right document for the URL in the iframe. */
|
|
2334
|
+
export const mainDocuments = defineDocuments([
|
|
2335
|
+
{ route: "/", filter: \`_type == "page" && slug.current == "home"\` },
|
|
2336
|
+
{ route: "/blog/:slug", filter: \`_type == "blog" && slug.current == $slug\` },
|
|
2337
|
+
{ route: "/:slug", filter: \`_type == "page" && slug.current == $slug\` },
|
|
2338
|
+
]);
|
|
2339
|
+
|
|
2340
|
+
/** Document → routes: the "Used on …" links shown when editing a document. */
|
|
2341
|
+
export const locations = {
|
|
2342
|
+
settings: defineLocations({
|
|
2343
|
+
locations: [{ title: "Home", href: "/" }],
|
|
2344
|
+
message: "This document is used on all pages",
|
|
2345
|
+
tone: "positive",
|
|
2346
|
+
}),
|
|
2347
|
+
page: defineLocations({
|
|
2348
|
+
select: { title: "title", slug: "slug.current" },
|
|
2349
|
+
resolve: (doc) => ({
|
|
2350
|
+
locations: [
|
|
2351
|
+
{
|
|
2352
|
+
title: doc?.title || "Untitled",
|
|
2353
|
+
href: doc?.slug === "home" ? "/" : \`/\${doc?.slug}\`,
|
|
2354
|
+
},
|
|
2355
|
+
],
|
|
2356
|
+
}),
|
|
2357
|
+
}),
|
|
2358
|
+
blog: defineLocations({
|
|
2359
|
+
select: { title: "title", slug: "slug.current" },
|
|
2360
|
+
resolve: (doc) => ({
|
|
2361
|
+
locations: [
|
|
2362
|
+
{ title: doc?.title || "Untitled", href: \`/blog/\${doc?.slug}\` },
|
|
2363
|
+
{ title: "Blog", href: "/blog" },
|
|
2364
|
+
],
|
|
2365
|
+
}),
|
|
2366
|
+
}),
|
|
2367
|
+
};
|
|
2368
|
+
`, DOCUMENT_GUARD = ` document: {
|
|
2369
|
+
newDocumentOptions: (prev, { creationContext }) =>
|
|
2370
|
+
creationContext.type === "global"
|
|
2371
|
+
? prev.filter((template) => !singletonTypes.has(template.templateId))
|
|
2372
|
+
: prev,
|
|
2373
|
+
actions: (prev, { schemaType }) =>
|
|
2374
|
+
singletonTypes.has(schemaType)
|
|
2375
|
+
? prev.filter(({ action }) => action && singletonActions.has(action))
|
|
2376
|
+
: prev,
|
|
2377
|
+
},`, QUERIES = `import { defineQuery } from "groq";
|
|
2378
|
+
|
|
2379
|
+
/**
|
|
2380
|
+
* A \`customUrl\` collapsed to a plain href, so components never branch on internal vs
|
|
2381
|
+
* external. Written against a field literally named \`url\` — the shape \`button\` and
|
|
2382
|
+
* \`imageLinkCard\` both use.
|
|
1392
2383
|
*/
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
console.warn("[sanity] query failed:", error);
|
|
1415
|
-
return null;
|
|
2384
|
+
const LINK_FIELDS = /* groq */ \`
|
|
2385
|
+
"openInNewTab": url.openInNewTab,
|
|
2386
|
+
"href": select(
|
|
2387
|
+
url.type == "external" => url.external,
|
|
2388
|
+
url.internal->_type == "blog" => "/blog/" + url.internal->slug.current,
|
|
2389
|
+
url.internal->slug.current == "home" => "/",
|
|
2390
|
+
defined(url.internal->slug.current) => "/" + url.internal->slug.current,
|
|
2391
|
+
"#"
|
|
2392
|
+
)
|
|
2393
|
+
\`;
|
|
2394
|
+
|
|
2395
|
+
const BUTTONS = /* groq */ \`buttons[]{ _key, _type, text, variant, \${LINK_FIELDS} }\`;
|
|
2396
|
+
|
|
2397
|
+
// \`...\` first, then the overrides: every block keeps its plain fields, and the two that
|
|
2398
|
+
// hold references or links get resolved on top.
|
|
2399
|
+
const PAGE_BUILDER = /* groq */ \`
|
|
2400
|
+
pageBuilder[]{
|
|
2401
|
+
...,
|
|
2402
|
+
\${BUTTONS},
|
|
2403
|
+
_type == "faqAccordion" => { faqs[]->{ _id, title, richText } },
|
|
2404
|
+
_type == "imageLinkCards" => { cards[]{ ..., \${LINK_FIELDS} } }
|
|
1416
2405
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
2406
|
+
\`;
|
|
2407
|
+
|
|
2408
|
+
const PAGE_FIELDS = /* groq */ \`
|
|
2409
|
+
_id,
|
|
2410
|
+
_type,
|
|
2411
|
+
title,
|
|
2412
|
+
description,
|
|
2413
|
+
"slug": slug.current,
|
|
2414
|
+
\${PAGE_BUILDER}
|
|
2415
|
+
\`;
|
|
2416
|
+
|
|
2417
|
+
const BLOG_CARD_FIELDS = /* groq */ \`
|
|
2418
|
+
_id,
|
|
2419
|
+
title,
|
|
2420
|
+
description,
|
|
2421
|
+
publishedAt,
|
|
2422
|
+
image,
|
|
2423
|
+
"slug": slug.current,
|
|
2424
|
+
"authors": authors[]->{ _id, name, position, image }
|
|
2425
|
+
\`;
|
|
2426
|
+
|
|
2427
|
+
export const HOME_PAGE_QUERY = defineQuery(
|
|
2428
|
+
\`*[_type == "page" && slug.current == "home"][0]{\${PAGE_FIELDS}}\`,
|
|
2429
|
+
);
|
|
2430
|
+
|
|
2431
|
+
export const PAGE_QUERY = defineQuery(
|
|
2432
|
+
\`*[_type == "page" && slug.current == $slug][0]{\${PAGE_FIELDS}}\`,
|
|
2433
|
+
);
|
|
2434
|
+
|
|
2435
|
+
export const PAGE_SLUGS_QUERY = defineQuery(
|
|
2436
|
+
\`*[_type == "page" && defined(slug.current) && slug.current != "home"].slug.current\`,
|
|
2437
|
+
);
|
|
2438
|
+
|
|
2439
|
+
export const BLOG_INDEX_QUERY = defineQuery(
|
|
2440
|
+
\`*[_type == "blog" && defined(slug.current)]|order(publishedAt desc){\${BLOG_CARD_FIELDS}}\`,
|
|
2441
|
+
);
|
|
2442
|
+
|
|
2443
|
+
export const BLOG_QUERY = defineQuery(
|
|
2444
|
+
\`*[_type == "blog" && slug.current == $slug][0]{\${BLOG_CARD_FIELDS}, richText}\`,
|
|
2445
|
+
);
|
|
2446
|
+
|
|
2447
|
+
export const BLOG_SLUGS_QUERY = defineQuery(
|
|
2448
|
+
\`*[_type == "blog" && defined(slug.current)].slug.current\`,
|
|
2449
|
+
);
|
|
2450
|
+
|
|
2451
|
+
export const SETTINGS_QUERY = defineQuery(
|
|
2452
|
+
\`*[_type == "settings"][0]{ siteTitle, siteDescription, socialImage }\`,
|
|
2453
|
+
);
|
|
2454
|
+
`;
|
|
2455
|
+
|
|
2456
|
+
// ../../packages/registry/src/content/sanity/seed.ts
|
|
2457
|
+
var para = (key, text) => ({
|
|
2458
|
+
_key: key,
|
|
2459
|
+
_type: "block",
|
|
2460
|
+
style: "normal",
|
|
2461
|
+
markDefs: [],
|
|
2462
|
+
children: [{ _key: `${key}s`, _type: "span", marks: [], text }]
|
|
2463
|
+
}), internal = (id) => ({
|
|
2464
|
+
_type: "customUrl",
|
|
2465
|
+
type: "internal",
|
|
2466
|
+
openInNewTab: false,
|
|
2467
|
+
internal: { _type: "reference", _ref: id }
|
|
2468
|
+
}), external = (href) => ({
|
|
2469
|
+
_type: "customUrl",
|
|
2470
|
+
type: "external",
|
|
2471
|
+
openInNewTab: true,
|
|
2472
|
+
external: href
|
|
2473
|
+
}), button = (key, text, url, variant = "default") => ({
|
|
2474
|
+
_key: key,
|
|
2475
|
+
_type: "button",
|
|
2476
|
+
text,
|
|
2477
|
+
variant,
|
|
2478
|
+
url
|
|
2479
|
+
}), DOCS, SEED_NDJSON;
|
|
2480
|
+
var init_seed = __esm(() => {
|
|
2481
|
+
DOCS = [
|
|
2482
|
+
{
|
|
2483
|
+
_id: "settings",
|
|
2484
|
+
_type: "settings",
|
|
2485
|
+
siteTitle: "Turbo Wizard",
|
|
2486
|
+
siteDescription: "A Turborepo monorepo scaffolded with create-turbo-wizard, with Sanity wired end to end."
|
|
2487
|
+
},
|
|
2488
|
+
{
|
|
2489
|
+
_id: "author-nova",
|
|
2490
|
+
_type: "author",
|
|
2491
|
+
name: "Nova Lang",
|
|
2492
|
+
position: "Staff engineer",
|
|
2493
|
+
bio: "Writes about build systems and the parts of a monorepo nobody reads until they break."
|
|
2494
|
+
},
|
|
2495
|
+
{
|
|
2496
|
+
_id: "author-atlas",
|
|
2497
|
+
_type: "author",
|
|
2498
|
+
name: "Atlas Reyes",
|
|
2499
|
+
position: "Content lead",
|
|
2500
|
+
bio: "Turns a schema into something an editor can actually use."
|
|
2501
|
+
},
|
|
2502
|
+
{
|
|
2503
|
+
_id: "faq-content",
|
|
2504
|
+
_type: "faq",
|
|
2505
|
+
title: "Where does this content live?",
|
|
2506
|
+
richText: [
|
|
2507
|
+
para("a1", "In your Sanity dataset. The document schemas are in apps/studio/schemaTypes, the queries are in packages/sanity/src/queries.ts, and the Studio edits what they describe.")
|
|
2508
|
+
]
|
|
2509
|
+
},
|
|
2510
|
+
{
|
|
2511
|
+
_id: "faq-blocks",
|
|
2512
|
+
_type: "faq",
|
|
2513
|
+
title: "How do I add a page-builder block?",
|
|
2514
|
+
richText: [
|
|
2515
|
+
para("a2", "Define it in packages/sanity-blocks/src/schema.ts, add it to the page document's pageBuilder array, then render it in the page-builder component beside it. Three files, no engine changes.")
|
|
2516
|
+
]
|
|
2517
|
+
},
|
|
2518
|
+
{
|
|
2519
|
+
_id: "faq-credentials",
|
|
2520
|
+
_type: "faq",
|
|
2521
|
+
title: "What happens without a project id?",
|
|
2522
|
+
richText: [
|
|
2523
|
+
para("a3", "Every read short-circuits and the site renders an empty state. The build never dials the API, so a fresh scaffold ships green before anyone signs up for anything.")
|
|
2524
|
+
]
|
|
2525
|
+
},
|
|
2526
|
+
{
|
|
2527
|
+
_id: "page-home",
|
|
2528
|
+
_type: "page",
|
|
2529
|
+
title: "Turbo Wizard",
|
|
2530
|
+
description: "The home page, served from Sanity.",
|
|
2531
|
+
slug: { _type: "slug", current: "home" },
|
|
2532
|
+
pageBuilder: [
|
|
2533
|
+
{
|
|
2534
|
+
_key: "hero",
|
|
2535
|
+
_type: "hero",
|
|
2536
|
+
badge: "Scaffolded",
|
|
2537
|
+
title: "Content from Sanity, rendered by your app",
|
|
2538
|
+
richText: [
|
|
2539
|
+
para("h1", "This page lives in the Studio. Edit it there and it changes here — every block below came out of the same dataset.")
|
|
2540
|
+
],
|
|
2541
|
+
buttons: [
|
|
2542
|
+
button("hb1", "Read the blog", internal("blog-page-builder")),
|
|
2543
|
+
button("hb2", "Sanity docs", external("https://www.sanity.io/docs"), "outline")
|
|
2544
|
+
]
|
|
2545
|
+
},
|
|
2546
|
+
{
|
|
2547
|
+
_key: "features",
|
|
2548
|
+
_type: "featureCardsIcon",
|
|
2549
|
+
eyebrow: "What you got",
|
|
2550
|
+
title: "Wired end to end",
|
|
2551
|
+
cards: [
|
|
2552
|
+
{
|
|
2553
|
+
_key: "f1",
|
|
2554
|
+
_type: "featureCardIcon",
|
|
2555
|
+
icon: "layers",
|
|
2556
|
+
title: "Page builder",
|
|
2557
|
+
richText: [para("f1a", "Seven block types, composed per page in the Studio.")]
|
|
2558
|
+
},
|
|
2559
|
+
{
|
|
2560
|
+
_key: "f2",
|
|
2561
|
+
_type: "featureCardIcon",
|
|
2562
|
+
icon: "pen-line",
|
|
2563
|
+
title: "Blog",
|
|
2564
|
+
richText: [para("f2a", "Posts with authors, dates and Portable Text bodies.")]
|
|
2565
|
+
},
|
|
2566
|
+
{
|
|
2567
|
+
_key: "f3",
|
|
2568
|
+
_type: "featureCardIcon",
|
|
2569
|
+
icon: "shield-check",
|
|
2570
|
+
title: "Typed queries",
|
|
2571
|
+
richText: [para("f3a", "defineQuery plus typegen, so results are not any.")]
|
|
2572
|
+
}
|
|
2573
|
+
]
|
|
2574
|
+
},
|
|
2575
|
+
{
|
|
2576
|
+
_key: "cards",
|
|
2577
|
+
_type: "imageLinkCards",
|
|
2578
|
+
eyebrow: "Go deeper",
|
|
2579
|
+
title: "Where to look next",
|
|
2580
|
+
cards: [
|
|
2581
|
+
{
|
|
2582
|
+
_key: "c1",
|
|
2583
|
+
_type: "imageLinkCard",
|
|
2584
|
+
title: "The schema",
|
|
2585
|
+
description: "Documents, blocks and the reusable definitions they share.",
|
|
2586
|
+
url: external("https://www.sanity.io/docs/schema-types")
|
|
2587
|
+
},
|
|
2588
|
+
{
|
|
2589
|
+
_key: "c2",
|
|
2590
|
+
_type: "imageLinkCard",
|
|
2591
|
+
title: "About this starter",
|
|
2592
|
+
description: "A second page, routed by its slug.",
|
|
2593
|
+
url: internal("page-about")
|
|
2594
|
+
}
|
|
2595
|
+
]
|
|
2596
|
+
},
|
|
2597
|
+
{
|
|
2598
|
+
_key: "faqs",
|
|
2599
|
+
_type: "faqAccordion",
|
|
2600
|
+
eyebrow: "Questions",
|
|
2601
|
+
title: "The ones that come up first",
|
|
2602
|
+
faqs: [
|
|
2603
|
+
{ _key: "q1", _type: "reference", _ref: "faq-content" },
|
|
2604
|
+
{ _key: "q2", _type: "reference", _ref: "faq-blocks" },
|
|
2605
|
+
{ _key: "q3", _type: "reference", _ref: "faq-credentials" }
|
|
2606
|
+
]
|
|
2607
|
+
},
|
|
2608
|
+
{
|
|
2609
|
+
_key: "prose",
|
|
2610
|
+
_type: "richTextBlock",
|
|
2611
|
+
eyebrow: "How it fits together",
|
|
2612
|
+
title: "One schema, two frontends",
|
|
2613
|
+
richText: [
|
|
2614
|
+
para("p1", "The schema and the GROQ are frontend-agnostic — the same files ship whether you picked Next or Astro. Only the rendering layer differs."),
|
|
2615
|
+
para("p2", "The page builder renders each block by its _type and skips anything it does not recognise, so adding a block to the schema never breaks a deployed page.")
|
|
2616
|
+
]
|
|
2617
|
+
},
|
|
2618
|
+
{
|
|
2619
|
+
_key: "newsletter",
|
|
2620
|
+
_type: "subscribeNewsletter",
|
|
2621
|
+
title: "Ship notes",
|
|
2622
|
+
subTitle: [para("n1", "Occasional posts about the stack this project scaffolds.")],
|
|
2623
|
+
helperText: [para("n2", "No spam. The form is a stub until you wire it up.")]
|
|
2624
|
+
},
|
|
2625
|
+
{
|
|
2626
|
+
_key: "cta",
|
|
2627
|
+
_type: "cta",
|
|
2628
|
+
eyebrow: "Your turn",
|
|
2629
|
+
title: "Open the Studio",
|
|
2630
|
+
richText: [
|
|
2631
|
+
para("ct1", "Edit this page in the Studio — pnpm dev:studio — then publish and reload.")
|
|
2632
|
+
],
|
|
2633
|
+
buttons: [button("cb1", "Read the blog", internal("blog-typed-groq"))]
|
|
2634
|
+
}
|
|
2635
|
+
]
|
|
2636
|
+
},
|
|
2637
|
+
{
|
|
2638
|
+
_id: "page-about",
|
|
2639
|
+
_type: "page",
|
|
2640
|
+
title: "About",
|
|
2641
|
+
description: "A second page, to show routing by slug.",
|
|
2642
|
+
slug: { _type: "slug", current: "about" },
|
|
2643
|
+
pageBuilder: [
|
|
2644
|
+
{
|
|
2645
|
+
_key: "hero",
|
|
2646
|
+
_type: "hero",
|
|
2647
|
+
title: "About this starter",
|
|
2648
|
+
richText: [para("ab1", "Pages are routed by their slug. This one is /about.")]
|
|
2649
|
+
},
|
|
2650
|
+
{
|
|
2651
|
+
_key: "prose",
|
|
2652
|
+
_type: "richTextBlock",
|
|
2653
|
+
title: "Add your own",
|
|
2654
|
+
richText: [
|
|
2655
|
+
para("ab2", "Create a page in the Studio, give it a slug, publish it, and it appears at that path on the next build.")
|
|
2656
|
+
]
|
|
2657
|
+
}
|
|
2658
|
+
]
|
|
2659
|
+
},
|
|
2660
|
+
{
|
|
2661
|
+
_id: "blog-page-builder",
|
|
2662
|
+
_type: "blog",
|
|
2663
|
+
title: "A page builder is just an array",
|
|
2664
|
+
description: "The page-builder pattern in one sentence: a document holds an array of objects, and the frontend renders each one by its _type.",
|
|
2665
|
+
slug: { _type: "slug", current: "a-page-builder-is-just-an-array" },
|
|
2666
|
+
publishedAt: "2026-01-14",
|
|
2667
|
+
authors: [{ _key: "a1", _type: "reference", _ref: "author-nova" }],
|
|
2668
|
+
richText: [
|
|
2669
|
+
para("b1", "Every block in this starter is a Sanity object type. The page document holds them in an ordered array, and the renderer switches on _type — unknown types are skipped rather than thrown on, so a schema change can never take down a live page."),
|
|
2670
|
+
para("b2", "The interesting consequence is that adding a block is a schema edit plus a component, with no routing and no migration. Editors compose; developers supply the vocabulary.")
|
|
2671
|
+
]
|
|
2672
|
+
},
|
|
2673
|
+
{
|
|
2674
|
+
_id: "blog-typed-groq",
|
|
2675
|
+
_type: "blog",
|
|
2676
|
+
title: "Typed GROQ, without hand-writing types",
|
|
2677
|
+
description: "defineQuery marks a string as a query, sanity typegen reads the schema, and query results stop being any.",
|
|
2678
|
+
slug: { _type: "slug", current: "typed-groq" },
|
|
2679
|
+
publishedAt: "2026-01-28",
|
|
2680
|
+
authors: [{ _key: "a1", _type: "reference", _ref: "author-nova" }],
|
|
2681
|
+
richText: [
|
|
2682
|
+
para("b1", "Wrapping a query in defineQuery is what makes it visible to typegen. A plain template literal looks like any other string and its result stays any, which is the failure mode you notice three refactors later."),
|
|
2683
|
+
para("b2", "This project commits the generated types, so a fresh clone type-checks before anything runs; re-run extract and typegen generate from apps/studio after a schema or query edit.")
|
|
2684
|
+
]
|
|
2685
|
+
},
|
|
2686
|
+
{
|
|
2687
|
+
_id: "blog-editors",
|
|
2688
|
+
_type: "blog",
|
|
2689
|
+
title: "Write the schema for the person editing it",
|
|
2690
|
+
description: "Field descriptions, previews and singletons are not polish. They are the difference between a Studio someone uses and one they avoid.",
|
|
2691
|
+
slug: { _type: "slug", current: "write-the-schema-for-the-editor" },
|
|
2692
|
+
publishedAt: "2026-02-09",
|
|
2693
|
+
authors: [{ _key: "a1", _type: "reference", _ref: "author-atlas" }],
|
|
2694
|
+
richText: [
|
|
2695
|
+
para("b1", "A document list showing three rows of Untitled is a schema that forgot it has users. Previews take a prepare function; use it to show the slug, the author, the publish date — whatever tells one row from another."),
|
|
2696
|
+
para("b2", "The same goes for singletons. Settings should be one document you open, not a list you can accidentally add a second row to.")
|
|
2697
|
+
]
|
|
2698
|
+
}
|
|
2699
|
+
];
|
|
2700
|
+
SEED_NDJSON = `${DOCS.map((doc) => JSON.stringify(doc)).join(`
|
|
2701
|
+
`)}
|
|
2702
|
+
`;
|
|
2703
|
+
});
|
|
2704
|
+
|
|
2705
|
+
// ../../packages/registry/src/content/sanity/studio.ts
|
|
2706
|
+
var j5 = (value) => `${JSON.stringify(value, null, 2)}
|
|
2707
|
+
`, STUDIO_PACKAGE_JSON, STUDIO_TSCONFIG, NEXT_SITE_URL = "http://localhost:3000", ASTRO_SITE_URL = "http://localhost:4321", studioEnvExample = (siteUrl) => `# Copy to apps/studio/.env, then fill in the project id.
|
|
2708
|
+
# A Studio only reads SANITY_STUDIO_*-prefixed variables.
|
|
2709
|
+
SANITY_STUDIO_PROJECT_ID=
|
|
2710
|
+
SANITY_STUDIO_DATASET=
|
|
2711
|
+
# Where Presentation points its preview iframe (the site, not the Studio).
|
|
2712
|
+
SANITY_STUDIO_PRESENTATION_URL=${siteUrl}
|
|
2713
|
+
`, STUDIO_ENV_EXAMPLE_NEXT, STUDIO_ENV_EXAMPLE_ASTRO, STUDIO_CLI = `import { defineCliConfig } from "sanity/cli";
|
|
2714
|
+
|
|
2715
|
+
export default defineCliConfig({
|
|
2716
|
+
api: {
|
|
2717
|
+
projectId: process.env.SANITY_STUDIO_PROJECT_ID || "placeholder",
|
|
2718
|
+
dataset: process.env.SANITY_STUDIO_DATASET || "production",
|
|
2719
|
+
},
|
|
2720
|
+
// The Studio owns extraction and typegen — the CLI has to run where sanity.config.ts
|
|
2721
|
+
// lives — but the generated types land beside the queries they type, in
|
|
2722
|
+
// @workspace/sanity. Paths are relative to apps/studio; \`schema\` defaults to the
|
|
2723
|
+
// schema.json that \`sanity schema extract\` writes here.
|
|
2724
|
+
typegen: {
|
|
2725
|
+
path: "../../packages/sanity/src/**/*.{ts,tsx}",
|
|
2726
|
+
generates: "../../packages/sanity/src/sanity.types.ts",
|
|
2727
|
+
},
|
|
2728
|
+
});
|
|
2729
|
+
`, studioConfig = (siteUrl, previewMode) => `import { defineConfig } from "sanity";
|
|
2730
|
+
import { presentationTool } from "sanity/presentation";
|
|
2731
|
+
import { structureTool } from "sanity/structure";
|
|
2732
|
+
import { locations, mainDocuments } from "./presentation";
|
|
2733
|
+
import { schemaTypes } from "./schemaTypes/index";
|
|
2734
|
+
import { singletonActions, singletonTypes, structure } from "./structure";
|
|
2735
|
+
|
|
2736
|
+
export default defineConfig({
|
|
2737
|
+
name: "default",
|
|
2738
|
+
title: "Studio",
|
|
2739
|
+
projectId: process.env.SANITY_STUDIO_PROJECT_ID || "placeholder",
|
|
2740
|
+
dataset: process.env.SANITY_STUDIO_DATASET || "production",
|
|
2741
|
+
plugins: [
|
|
2742
|
+
presentationTool({
|
|
2743
|
+
resolve: { mainDocuments, locations },
|
|
2744
|
+
previewUrl: {
|
|
2745
|
+
// Absolute, unlike an embedded Studio's "/": the site is a different origin now.
|
|
2746
|
+
initial: process.env.SANITY_STUDIO_PRESENTATION_URL || "${siteUrl}",${previewMode}
|
|
2747
|
+
},
|
|
2748
|
+
}),
|
|
2749
|
+
structureTool({ structure }),
|
|
2750
|
+
],
|
|
2751
|
+
schema: { types: schemaTypes },
|
|
2752
|
+
${DOCUMENT_GUARD}
|
|
2753
|
+
});
|
|
2754
|
+
`, STUDIO_CONFIG_NEXT, STUDIO_CONFIG_ASTRO;
|
|
2755
|
+
var init_studio = __esm(() => {
|
|
2756
|
+
STUDIO_PACKAGE_JSON = j5({
|
|
2757
|
+
name: "studio",
|
|
2758
|
+
version: "0.0.0",
|
|
2759
|
+
private: true,
|
|
2760
|
+
type: "module",
|
|
2761
|
+
scripts: {
|
|
2762
|
+
build: "sanity build",
|
|
2763
|
+
"check-types": "tsc --noEmit",
|
|
2764
|
+
dev: "sanity dev",
|
|
2765
|
+
extract: "sanity schema extract --enforce-required-fields",
|
|
2766
|
+
"sanity:seed": "sanity dataset import seed.ndjson",
|
|
2767
|
+
type: "sanity typegen generate"
|
|
2768
|
+
},
|
|
2769
|
+
dependencies: {
|
|
2770
|
+
"@workspace/sanity-blocks": "workspace:*",
|
|
2771
|
+
react: "^19.0.0",
|
|
2772
|
+
"react-dom": "^19.0.0",
|
|
2773
|
+
sanity: "^5.31.1",
|
|
2774
|
+
"styled-components": "^6.1.15"
|
|
2775
|
+
},
|
|
2776
|
+
devDependencies: {
|
|
2777
|
+
"@types/node": "^22.10.0",
|
|
2778
|
+
"@types/react": "^19.0.0",
|
|
2779
|
+
"@workspace/typescript-config": "workspace:*",
|
|
2780
|
+
typescript: "^5.9.0"
|
|
2781
|
+
}
|
|
2782
|
+
});
|
|
2783
|
+
STUDIO_TSCONFIG = j5({
|
|
2784
|
+
extends: "@workspace/typescript-config/react-library.json",
|
|
2785
|
+
include: ["**/*.ts", "**/*.tsx"],
|
|
2786
|
+
exclude: ["node_modules", "dist"]
|
|
2787
|
+
});
|
|
2788
|
+
STUDIO_ENV_EXAMPLE_NEXT = studioEnvExample(NEXT_SITE_URL);
|
|
2789
|
+
STUDIO_ENV_EXAMPLE_ASTRO = studioEnvExample(ASTRO_SITE_URL);
|
|
2790
|
+
STUDIO_CONFIG_NEXT = studioConfig(NEXT_SITE_URL, `
|
|
2791
|
+
previewMode: {
|
|
2792
|
+
enable: "/api/draft-mode/enable",
|
|
2793
|
+
// Presentation never calls this itself (documented as not yet implemented);
|
|
2794
|
+
// editors leave draft mode via <DisableDraftMode/>, which links to the route.
|
|
2795
|
+
disable: "/api/draft-mode/disable",
|
|
2796
|
+
},`);
|
|
2797
|
+
STUDIO_CONFIG_ASTRO = studioConfig(ASTRO_SITE_URL, `
|
|
2798
|
+
// No previewMode: Astro has no draft-mode routes. Draft content comes from
|
|
2799
|
+
// loadQuery's perspective switch (PUBLIC_SANITY_VISUAL_EDITING_ENABLED + a read
|
|
2800
|
+
// token), which needs a rendering server — \`astro dev\`, or an SSR adapter.`);
|
|
2801
|
+
});
|
|
2802
|
+
|
|
2803
|
+
// ../../packages/registry/src/content/sanity/types.ts
|
|
2804
|
+
var SANITY_TYPES = `/**
|
|
2805
|
+
* ---------------------------------------------------------------------------------
|
|
2806
|
+
* This file has been generated by Sanity TypeGen.
|
|
2807
|
+
* Command: \`sanity typegen generate\`
|
|
2808
|
+
*
|
|
2809
|
+
* Any modifications made directly to this file will be overwritten the next time
|
|
2810
|
+
* the TypeScript definitions are generated. Please make changes to the Sanity
|
|
2811
|
+
* schema definitions and/or GROQ queries if you need to update these types.
|
|
2812
|
+
*
|
|
2813
|
+
* For more information on how to use Sanity TypeGen, visit the official documentation:
|
|
2814
|
+
* https://www.sanity.io/docs/sanity-typegen
|
|
2815
|
+
* ---------------------------------------------------------------------------------
|
|
2816
|
+
*/
|
|
2817
|
+
|
|
2818
|
+
export declare const internalGroqTypeReferenceTo: unique symbol;
|
|
2819
|
+
|
|
2820
|
+
// Source: schema.json
|
|
2821
|
+
export type SanityImageAssetReference = {
|
|
2822
|
+
_ref: string;
|
|
2823
|
+
_type: "reference";
|
|
2824
|
+
_weak?: boolean;
|
|
2825
|
+
[internalGroqTypeReferenceTo]?: "sanity.imageAsset";
|
|
2826
|
+
};
|
|
2827
|
+
|
|
2828
|
+
export type Image1 = {
|
|
2829
|
+
asset?: SanityImageAssetReference;
|
|
2830
|
+
media?: unknown; // Unable to locate the referenced type "image.media" in schema
|
|
2831
|
+
hotspot?: SanityImageHotspot;
|
|
2832
|
+
crop?: SanityImageCrop;
|
|
2833
|
+
alt?: string;
|
|
2834
|
+
_type: "image";
|
|
2835
|
+
};
|
|
2836
|
+
|
|
2837
|
+
export type Cta = {
|
|
2838
|
+
_type: "cta";
|
|
2839
|
+
eyebrow?: string;
|
|
2840
|
+
title?: string;
|
|
2841
|
+
richText?: RichText;
|
|
2842
|
+
buttons?: Array<
|
|
2843
|
+
{
|
|
2844
|
+
_key: string;
|
|
2845
|
+
} & Button
|
|
2846
|
+
>;
|
|
2847
|
+
};
|
|
2848
|
+
|
|
2849
|
+
export type SubscribeNewsletter = {
|
|
2850
|
+
_type: "subscribeNewsletter";
|
|
2851
|
+
title?: string;
|
|
2852
|
+
subTitle?: RichText;
|
|
2853
|
+
helperText?: RichText;
|
|
2854
|
+
};
|
|
2855
|
+
|
|
2856
|
+
export type FaqReference = {
|
|
2857
|
+
_ref: string;
|
|
2858
|
+
_type: "reference";
|
|
2859
|
+
_weak?: boolean;
|
|
2860
|
+
[internalGroqTypeReferenceTo]?: "faq";
|
|
2861
|
+
};
|
|
2862
|
+
|
|
2863
|
+
export type FaqAccordion = {
|
|
2864
|
+
_type: "faqAccordion";
|
|
2865
|
+
eyebrow?: string;
|
|
2866
|
+
title: string;
|
|
2867
|
+
subtitle?: string;
|
|
2868
|
+
faqs?: Array<
|
|
2869
|
+
{
|
|
2870
|
+
_key: string;
|
|
2871
|
+
} & FaqReference
|
|
2872
|
+
>;
|
|
2873
|
+
};
|
|
2874
|
+
|
|
2875
|
+
export type ImageLinkCards = {
|
|
2876
|
+
_type: "imageLinkCards";
|
|
2877
|
+
eyebrow?: string;
|
|
2878
|
+
title: string;
|
|
2879
|
+
richText?: RichText;
|
|
2880
|
+
buttons?: Array<
|
|
2881
|
+
{
|
|
2882
|
+
_key: string;
|
|
2883
|
+
} & Button
|
|
2884
|
+
>;
|
|
2885
|
+
cards?: Array<{
|
|
2886
|
+
title: string;
|
|
2887
|
+
description?: string;
|
|
2888
|
+
image?: Image1;
|
|
2889
|
+
url?: CustomUrl;
|
|
2890
|
+
_type: "imageLinkCard";
|
|
2891
|
+
_key: string;
|
|
2892
|
+
}>;
|
|
2893
|
+
};
|
|
2894
|
+
|
|
2895
|
+
export type FeatureCardsIcon = {
|
|
2896
|
+
_type: "featureCardsIcon";
|
|
2897
|
+
eyebrow?: string;
|
|
2898
|
+
title?: string;
|
|
2899
|
+
richText?: RichText;
|
|
2900
|
+
cards?: Array<{
|
|
2901
|
+
icon?: string;
|
|
2902
|
+
title?: string;
|
|
2903
|
+
richText?: RichText;
|
|
2904
|
+
_type: "featureCardIcon";
|
|
2905
|
+
_key: string;
|
|
2906
|
+
}>;
|
|
2907
|
+
};
|
|
2908
|
+
|
|
2909
|
+
export type RichTextBlock = {
|
|
2910
|
+
_type: "richTextBlock";
|
|
2911
|
+
eyebrow?: string;
|
|
2912
|
+
title?: string;
|
|
2913
|
+
richText?: RichText;
|
|
2914
|
+
};
|
|
2915
|
+
|
|
2916
|
+
export type Hero = {
|
|
2917
|
+
_type: "hero";
|
|
2918
|
+
badge?: string;
|
|
2919
|
+
title?: string;
|
|
2920
|
+
richText?: RichText;
|
|
2921
|
+
image?: Image1;
|
|
2922
|
+
buttons?: Array<
|
|
2923
|
+
{
|
|
2924
|
+
_key: string;
|
|
2925
|
+
} & Button
|
|
2926
|
+
>;
|
|
2927
|
+
};
|
|
2928
|
+
|
|
2929
|
+
export type RichText = Array<
|
|
2930
|
+
| {
|
|
2931
|
+
children?: Array<{
|
|
2932
|
+
marks?: Array<string>;
|
|
2933
|
+
text?: string;
|
|
2934
|
+
_type: "span";
|
|
2935
|
+
_key: string;
|
|
2936
|
+
}>;
|
|
2937
|
+
style?: "normal" | "h2" | "h3" | "h4";
|
|
2938
|
+
listItem?: "bullet" | "number";
|
|
2939
|
+
markDefs?: Array<{
|
|
2940
|
+
customLink?: CustomUrl;
|
|
2941
|
+
_type: "customLink";
|
|
2942
|
+
_key: string;
|
|
2943
|
+
}>;
|
|
2944
|
+
level?: number;
|
|
2945
|
+
_type: "block";
|
|
2946
|
+
_key: string;
|
|
2947
|
+
}
|
|
2948
|
+
| {
|
|
2949
|
+
asset?: SanityImageAssetReference;
|
|
2950
|
+
media?: unknown;
|
|
2951
|
+
hotspot?: SanityImageHotspot;
|
|
2952
|
+
crop?: SanityImageCrop;
|
|
2953
|
+
caption?: string;
|
|
2954
|
+
_type: "image";
|
|
2955
|
+
_key: string;
|
|
2956
|
+
}
|
|
2957
|
+
>;
|
|
2958
|
+
|
|
2959
|
+
export type Button = {
|
|
2960
|
+
_type: "button";
|
|
2961
|
+
variant?: "default" | "secondary" | "outline" | "link";
|
|
2962
|
+
text?: string;
|
|
2963
|
+
url?: CustomUrl;
|
|
2964
|
+
};
|
|
2965
|
+
|
|
2966
|
+
export type PageReference = {
|
|
2967
|
+
_ref: string;
|
|
2968
|
+
_type: "reference";
|
|
2969
|
+
_weak?: boolean;
|
|
2970
|
+
[internalGroqTypeReferenceTo]?: "page";
|
|
2971
|
+
};
|
|
2972
|
+
|
|
2973
|
+
export type BlogReference = {
|
|
2974
|
+
_ref: string;
|
|
2975
|
+
_type: "reference";
|
|
2976
|
+
_weak?: boolean;
|
|
2977
|
+
[internalGroqTypeReferenceTo]?: "blog";
|
|
2978
|
+
};
|
|
2979
|
+
|
|
2980
|
+
export type CustomUrl = {
|
|
2981
|
+
_type: "customUrl";
|
|
2982
|
+
type: "internal" | "external";
|
|
2983
|
+
openInNewTab?: boolean;
|
|
2984
|
+
external?: string;
|
|
2985
|
+
internal?: PageReference | BlogReference;
|
|
2986
|
+
};
|
|
1420
2987
|
|
|
1421
|
-
|
|
2988
|
+
export type Settings = {
|
|
2989
|
+
_id: string;
|
|
2990
|
+
_type: "settings";
|
|
2991
|
+
_createdAt: string;
|
|
2992
|
+
_updatedAt: string;
|
|
2993
|
+
_rev: string;
|
|
2994
|
+
siteTitle: string;
|
|
2995
|
+
siteDescription?: string;
|
|
2996
|
+
socialImage?: {
|
|
2997
|
+
asset?: SanityImageAssetReference;
|
|
2998
|
+
media?: unknown;
|
|
2999
|
+
hotspot?: SanityImageHotspot;
|
|
3000
|
+
crop?: SanityImageCrop;
|
|
3001
|
+
_type: "image";
|
|
3002
|
+
};
|
|
3003
|
+
};
|
|
1422
3004
|
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
3005
|
+
export type SanityImageCrop = {
|
|
3006
|
+
_type: "sanity.imageCrop";
|
|
3007
|
+
top: number;
|
|
3008
|
+
bottom: number;
|
|
3009
|
+
left: number;
|
|
3010
|
+
right: number;
|
|
3011
|
+
};
|
|
1430
3012
|
|
|
1431
|
-
type
|
|
3013
|
+
export type SanityImageHotspot = {
|
|
3014
|
+
_type: "sanity.imageHotspot";
|
|
3015
|
+
x: number;
|
|
3016
|
+
y: number;
|
|
3017
|
+
height: number;
|
|
3018
|
+
width: number;
|
|
3019
|
+
};
|
|
1432
3020
|
|
|
1433
|
-
export type
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
3021
|
+
export type Faq = {
|
|
3022
|
+
_id: string;
|
|
3023
|
+
_type: "faq";
|
|
3024
|
+
_createdAt: string;
|
|
3025
|
+
_updatedAt: string;
|
|
3026
|
+
_rev: string;
|
|
3027
|
+
title: string;
|
|
3028
|
+
richText?: RichText;
|
|
3029
|
+
};
|
|
3030
|
+
|
|
3031
|
+
export type Author = {
|
|
3032
|
+
_id: string;
|
|
3033
|
+
_type: "author";
|
|
3034
|
+
_createdAt: string;
|
|
3035
|
+
_updatedAt: string;
|
|
3036
|
+
_rev: string;
|
|
3037
|
+
name: string;
|
|
3038
|
+
position?: string;
|
|
3039
|
+
image?: {
|
|
3040
|
+
asset?: SanityImageAssetReference;
|
|
3041
|
+
media?: unknown;
|
|
3042
|
+
hotspot?: SanityImageHotspot;
|
|
3043
|
+
crop?: SanityImageCrop;
|
|
3044
|
+
_type: "image";
|
|
3045
|
+
};
|
|
3046
|
+
bio?: string;
|
|
3047
|
+
};
|
|
3048
|
+
|
|
3049
|
+
export type AuthorReference = {
|
|
3050
|
+
_ref: string;
|
|
3051
|
+
_type: "reference";
|
|
3052
|
+
_weak?: boolean;
|
|
3053
|
+
[internalGroqTypeReferenceTo]?: "author";
|
|
3054
|
+
};
|
|
3055
|
+
|
|
3056
|
+
export type Blog = {
|
|
3057
|
+
_id: string;
|
|
3058
|
+
_type: "blog";
|
|
3059
|
+
_createdAt: string;
|
|
3060
|
+
_updatedAt: string;
|
|
3061
|
+
_rev: string;
|
|
3062
|
+
title: string;
|
|
3063
|
+
slug: Slug;
|
|
3064
|
+
description?: string;
|
|
3065
|
+
authors?: Array<
|
|
3066
|
+
{
|
|
1437
3067
|
_key: string;
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
3068
|
+
} & AuthorReference
|
|
3069
|
+
>;
|
|
3070
|
+
publishedAt?: string;
|
|
3071
|
+
image?: {
|
|
3072
|
+
asset?: SanityImageAssetReference;
|
|
3073
|
+
media?: unknown;
|
|
3074
|
+
hotspot?: SanityImageHotspot;
|
|
3075
|
+
crop?: SanityImageCrop;
|
|
3076
|
+
alt?: string;
|
|
3077
|
+
_type: "image";
|
|
3078
|
+
};
|
|
3079
|
+
richText?: RichText;
|
|
3080
|
+
};
|
|
3081
|
+
|
|
3082
|
+
export type Slug = {
|
|
3083
|
+
_type: "slug";
|
|
3084
|
+
current: string;
|
|
3085
|
+
source?: string;
|
|
3086
|
+
};
|
|
3087
|
+
|
|
3088
|
+
export type Page = {
|
|
3089
|
+
_id: string;
|
|
3090
|
+
_type: "page";
|
|
3091
|
+
_createdAt: string;
|
|
3092
|
+
_updatedAt: string;
|
|
3093
|
+
_rev: string;
|
|
3094
|
+
title: string;
|
|
3095
|
+
slug: Slug;
|
|
3096
|
+
description?: string;
|
|
3097
|
+
image?: {
|
|
3098
|
+
asset?: SanityImageAssetReference;
|
|
3099
|
+
media?: unknown;
|
|
3100
|
+
hotspot?: SanityImageHotspot;
|
|
3101
|
+
crop?: SanityImageCrop;
|
|
3102
|
+
_type: "image";
|
|
3103
|
+
};
|
|
3104
|
+
pageBuilder?: Array<
|
|
3105
|
+
| ({
|
|
3106
|
+
_key: string;
|
|
3107
|
+
} & Hero)
|
|
3108
|
+
| ({
|
|
3109
|
+
_key: string;
|
|
3110
|
+
} & RichTextBlock)
|
|
3111
|
+
| ({
|
|
3112
|
+
_key: string;
|
|
3113
|
+
} & FeatureCardsIcon)
|
|
3114
|
+
| ({
|
|
3115
|
+
_key: string;
|
|
3116
|
+
} & ImageLinkCards)
|
|
3117
|
+
| ({
|
|
3118
|
+
_key: string;
|
|
3119
|
+
} & FaqAccordion)
|
|
3120
|
+
| ({
|
|
3121
|
+
_key: string;
|
|
3122
|
+
} & SubscribeNewsletter)
|
|
3123
|
+
| ({
|
|
3124
|
+
_key: string;
|
|
3125
|
+
} & Cta)
|
|
3126
|
+
>;
|
|
3127
|
+
};
|
|
3128
|
+
|
|
3129
|
+
export type SanityImagePaletteSwatch = {
|
|
3130
|
+
_type: "sanity.imagePaletteSwatch";
|
|
3131
|
+
background?: string;
|
|
3132
|
+
foreground?: string;
|
|
3133
|
+
population?: number;
|
|
3134
|
+
title?: string;
|
|
3135
|
+
};
|
|
3136
|
+
|
|
3137
|
+
export type SanityImagePalette = {
|
|
3138
|
+
_type: "sanity.imagePalette";
|
|
3139
|
+
darkMuted?: SanityImagePaletteSwatch;
|
|
3140
|
+
lightVibrant?: SanityImagePaletteSwatch;
|
|
3141
|
+
darkVibrant?: SanityImagePaletteSwatch;
|
|
3142
|
+
vibrant?: SanityImagePaletteSwatch;
|
|
3143
|
+
dominant?: SanityImagePaletteSwatch;
|
|
3144
|
+
lightMuted?: SanityImagePaletteSwatch;
|
|
3145
|
+
muted?: SanityImagePaletteSwatch;
|
|
3146
|
+
};
|
|
3147
|
+
|
|
3148
|
+
export type SanityImageDimensions = {
|
|
3149
|
+
_type: "sanity.imageDimensions";
|
|
3150
|
+
height: number;
|
|
3151
|
+
width: number;
|
|
3152
|
+
aspectRatio: number;
|
|
3153
|
+
};
|
|
3154
|
+
|
|
3155
|
+
export type SanityImageMetadata = {
|
|
3156
|
+
_type: "sanity.imageMetadata";
|
|
3157
|
+
location?: Geopoint;
|
|
3158
|
+
dimensions?: SanityImageDimensions;
|
|
3159
|
+
palette?: SanityImagePalette;
|
|
3160
|
+
lqip?: string;
|
|
3161
|
+
blurHash?: string;
|
|
3162
|
+
thumbHash?: string;
|
|
3163
|
+
hasAlpha?: boolean;
|
|
3164
|
+
isOpaque?: boolean;
|
|
3165
|
+
};
|
|
1444
3166
|
|
|
1445
|
-
export type
|
|
3167
|
+
export type SanityFileAsset = {
|
|
1446
3168
|
_id: string;
|
|
3169
|
+
_type: "sanity.fileAsset";
|
|
3170
|
+
_createdAt: string;
|
|
3171
|
+
_updatedAt: string;
|
|
3172
|
+
_rev: string;
|
|
3173
|
+
originalFilename?: string;
|
|
3174
|
+
label?: string;
|
|
1447
3175
|
title?: string;
|
|
1448
3176
|
description?: string;
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
3177
|
+
altText?: string;
|
|
3178
|
+
sha1hash: string;
|
|
3179
|
+
extension: string;
|
|
3180
|
+
mimeType: string;
|
|
3181
|
+
size: number;
|
|
3182
|
+
assetId: string;
|
|
3183
|
+
uploadId?: string;
|
|
3184
|
+
path: string;
|
|
3185
|
+
url: string;
|
|
3186
|
+
source?: SanityAssetSourceData;
|
|
3187
|
+
};
|
|
1456
3188
|
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
3189
|
+
export type SanityAssetSourceData = {
|
|
3190
|
+
_type: "sanity.assetSourceData";
|
|
3191
|
+
name?: string;
|
|
3192
|
+
id?: string;
|
|
3193
|
+
url?: string;
|
|
3194
|
+
};
|
|
1460
3195
|
|
|
1461
|
-
|
|
1462
|
-
|
|
3196
|
+
export type SanityImageAsset = {
|
|
3197
|
+
_id: string;
|
|
3198
|
+
_type: "sanity.imageAsset";
|
|
3199
|
+
_createdAt: string;
|
|
3200
|
+
_updatedAt: string;
|
|
3201
|
+
_rev: string;
|
|
3202
|
+
originalFilename?: string;
|
|
3203
|
+
label?: string;
|
|
3204
|
+
title?: string;
|
|
3205
|
+
description?: string;
|
|
3206
|
+
altText?: string;
|
|
3207
|
+
sha1hash: string;
|
|
3208
|
+
extension: string;
|
|
3209
|
+
mimeType: string;
|
|
3210
|
+
size: number;
|
|
3211
|
+
assetId: string;
|
|
3212
|
+
uploadId?: string;
|
|
3213
|
+
path: string;
|
|
3214
|
+
url: string;
|
|
3215
|
+
metadata?: SanityImageMetadata;
|
|
3216
|
+
source?: SanityAssetSourceData;
|
|
3217
|
+
};
|
|
1463
3218
|
|
|
1464
|
-
{
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
<h2>{block.heading}</h2>
|
|
1471
|
-
{block.subheading && <p>{block.subheading}</p>}
|
|
1472
|
-
{src && <img src={src} alt={block.image?.alt ?? ""} />}
|
|
1473
|
-
</section>
|
|
1474
|
-
);
|
|
1475
|
-
}
|
|
1476
|
-
if (block._type === "richText") {
|
|
1477
|
-
return (
|
|
1478
|
-
<section>
|
|
1479
|
-
{block.heading && <h2>{block.heading}</h2>}
|
|
1480
|
-
{block.content && <PortableText value={block.content} />}
|
|
1481
|
-
</section>
|
|
1482
|
-
);
|
|
1483
|
-
}
|
|
1484
|
-
if (block._type === "cta") {
|
|
1485
|
-
return (
|
|
1486
|
-
<section>
|
|
1487
|
-
<h2>{block.heading}</h2>
|
|
1488
|
-
{block.text && <p>{block.text}</p>}
|
|
1489
|
-
{block.buttonHref && block.buttonLabel && (
|
|
1490
|
-
<a href={block.buttonHref}>{block.buttonLabel}</a>
|
|
1491
|
-
)}
|
|
1492
|
-
</section>
|
|
1493
|
-
);
|
|
1494
|
-
}
|
|
1495
|
-
return null;
|
|
1496
|
-
})
|
|
1497
|
-
}
|
|
1498
|
-
`, ASTRO_SLUG_PAGE = `---
|
|
1499
|
-
import { VisualEditing } from "@sanity/astro/visual-editing";
|
|
1500
|
-
import PageBuilder from "../components/PageBuilder.astro";
|
|
1501
|
-
import { visualEditingEnabled } from "../../sanity/env";
|
|
1502
|
-
import { loadQuery } from "../../sanity/fetch";
|
|
1503
|
-
import { PAGE_QUERY, PAGE_SLUGS_QUERY } from "../../sanity/queries";
|
|
1504
|
-
import type { SanityPage } from "../../sanity/types";
|
|
3219
|
+
export type Geopoint = {
|
|
3220
|
+
_type: "geopoint";
|
|
3221
|
+
lat?: number;
|
|
3222
|
+
lng?: number;
|
|
3223
|
+
alt?: number;
|
|
3224
|
+
};
|
|
1505
3225
|
|
|
1506
|
-
export
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
3226
|
+
export type AllSanitySchemaTypes =
|
|
3227
|
+
| SanityImageAssetReference
|
|
3228
|
+
| Image1
|
|
3229
|
+
| Cta
|
|
3230
|
+
| SubscribeNewsletter
|
|
3231
|
+
| FaqReference
|
|
3232
|
+
| FaqAccordion
|
|
3233
|
+
| ImageLinkCards
|
|
3234
|
+
| FeatureCardsIcon
|
|
3235
|
+
| RichTextBlock
|
|
3236
|
+
| Hero
|
|
3237
|
+
| RichText
|
|
3238
|
+
| Button
|
|
3239
|
+
| PageReference
|
|
3240
|
+
| BlogReference
|
|
3241
|
+
| CustomUrl
|
|
3242
|
+
| Settings
|
|
3243
|
+
| SanityImageCrop
|
|
3244
|
+
| SanityImageHotspot
|
|
3245
|
+
| Faq
|
|
3246
|
+
| Author
|
|
3247
|
+
| AuthorReference
|
|
3248
|
+
| Blog
|
|
3249
|
+
| Slug
|
|
3250
|
+
| Page
|
|
3251
|
+
| SanityImagePaletteSwatch
|
|
3252
|
+
| SanityImagePalette
|
|
3253
|
+
| SanityImageDimensions
|
|
3254
|
+
| SanityImageMetadata
|
|
3255
|
+
| SanityFileAsset
|
|
3256
|
+
| SanityAssetSourceData
|
|
3257
|
+
| SanityImageAsset
|
|
3258
|
+
| Geopoint;
|
|
3259
|
+
|
|
3260
|
+
// Source: ../../packages/sanity/src/queries.ts
|
|
3261
|
+
// Variable: HOME_PAGE_QUERY
|
|
3262
|
+
// Query: *[_type == "page" && slug.current == "home"][0]{ _id, _type, title, description, "slug": slug.current, pageBuilder[]{ ..., buttons[]{ _key, _type, text, variant, "openInNewTab": url.openInNewTab, "href": select( url.type == "external" => url.external, url.internal->_type == "blog" => "/blog/" + url.internal->slug.current, url.internal->slug.current == "home" => "/", defined(url.internal->slug.current) => "/" + url.internal->slug.current, "#" ) }, _type == "faqAccordion" => { faqs[]->{ _id, title, richText } }, _type == "imageLinkCards" => { cards[]{ ..., "openInNewTab": url.openInNewTab, "href": select( url.type == "external" => url.external, url.internal->_type == "blog" => "/blog/" + url.internal->slug.current, url.internal->slug.current == "home" => "/", defined(url.internal->slug.current) => "/" + url.internal->slug.current, "#" ) } } }}
|
|
3263
|
+
export type HOME_PAGE_QUERY_RESULT = {
|
|
3264
|
+
_id: string;
|
|
3265
|
+
_type: "page";
|
|
3266
|
+
title: string;
|
|
3267
|
+
description: string | null;
|
|
3268
|
+
slug: string;
|
|
3269
|
+
pageBuilder: Array<
|
|
3270
|
+
| {
|
|
3271
|
+
_key: string;
|
|
3272
|
+
_type: "cta";
|
|
3273
|
+
eyebrow?: string;
|
|
3274
|
+
title?: string;
|
|
3275
|
+
richText?: RichText;
|
|
3276
|
+
buttons: Array<{
|
|
3277
|
+
_key: string;
|
|
3278
|
+
_type: "button";
|
|
3279
|
+
text: string | null;
|
|
3280
|
+
variant: "default" | "link" | "outline" | "secondary" | null;
|
|
3281
|
+
openInNewTab: boolean | null;
|
|
3282
|
+
href: string | "/" | "#" | null;
|
|
3283
|
+
}> | null;
|
|
3284
|
+
}
|
|
3285
|
+
| {
|
|
3286
|
+
_key: string;
|
|
3287
|
+
_type: "faqAccordion";
|
|
3288
|
+
eyebrow?: string;
|
|
3289
|
+
title: string;
|
|
3290
|
+
subtitle?: string;
|
|
3291
|
+
faqs: Array<{
|
|
3292
|
+
_id: string;
|
|
3293
|
+
title: string;
|
|
3294
|
+
richText: RichText | null;
|
|
3295
|
+
}> | null;
|
|
3296
|
+
buttons: null;
|
|
3297
|
+
}
|
|
3298
|
+
| {
|
|
3299
|
+
_key: string;
|
|
3300
|
+
_type: "featureCardsIcon";
|
|
3301
|
+
eyebrow?: string;
|
|
3302
|
+
title?: string;
|
|
3303
|
+
richText?: RichText;
|
|
3304
|
+
cards?: Array<{
|
|
3305
|
+
icon?: string;
|
|
3306
|
+
title?: string;
|
|
3307
|
+
richText?: RichText;
|
|
3308
|
+
_type: "featureCardIcon";
|
|
3309
|
+
_key: string;
|
|
3310
|
+
}>;
|
|
3311
|
+
buttons: null;
|
|
3312
|
+
}
|
|
3313
|
+
| {
|
|
3314
|
+
_key: string;
|
|
3315
|
+
_type: "hero";
|
|
3316
|
+
badge?: string;
|
|
3317
|
+
title?: string;
|
|
3318
|
+
richText?: RichText;
|
|
3319
|
+
image?: Image1;
|
|
3320
|
+
buttons: Array<{
|
|
3321
|
+
_key: string;
|
|
3322
|
+
_type: "button";
|
|
3323
|
+
text: string | null;
|
|
3324
|
+
variant: "default" | "link" | "outline" | "secondary" | null;
|
|
3325
|
+
openInNewTab: boolean | null;
|
|
3326
|
+
href: string | "/" | "#" | null;
|
|
3327
|
+
}> | null;
|
|
3328
|
+
}
|
|
3329
|
+
| {
|
|
3330
|
+
_key: string;
|
|
3331
|
+
_type: "imageLinkCards";
|
|
3332
|
+
eyebrow?: string;
|
|
3333
|
+
title: string;
|
|
3334
|
+
richText?: RichText;
|
|
3335
|
+
buttons: Array<{
|
|
3336
|
+
_key: string;
|
|
3337
|
+
_type: "button";
|
|
3338
|
+
text: string | null;
|
|
3339
|
+
variant: "default" | "link" | "outline" | "secondary" | null;
|
|
3340
|
+
openInNewTab: boolean | null;
|
|
3341
|
+
href: string | "/" | "#" | null;
|
|
3342
|
+
}> | null;
|
|
3343
|
+
cards: Array<{
|
|
3344
|
+
title: string;
|
|
3345
|
+
description?: string;
|
|
3346
|
+
image?: Image1;
|
|
3347
|
+
url?: CustomUrl;
|
|
3348
|
+
_type: "imageLinkCard";
|
|
3349
|
+
_key: string;
|
|
3350
|
+
openInNewTab: boolean | null;
|
|
3351
|
+
href: string | "/" | "#" | null;
|
|
3352
|
+
}> | null;
|
|
3353
|
+
}
|
|
3354
|
+
| {
|
|
3355
|
+
_key: string;
|
|
3356
|
+
_type: "richTextBlock";
|
|
3357
|
+
eyebrow?: string;
|
|
3358
|
+
title?: string;
|
|
3359
|
+
richText?: RichText;
|
|
3360
|
+
buttons: null;
|
|
3361
|
+
}
|
|
3362
|
+
| {
|
|
3363
|
+
_key: string;
|
|
3364
|
+
_type: "subscribeNewsletter";
|
|
3365
|
+
title?: string;
|
|
3366
|
+
subTitle?: RichText;
|
|
3367
|
+
helperText?: RichText;
|
|
3368
|
+
buttons: null;
|
|
3369
|
+
}
|
|
3370
|
+
> | null;
|
|
3371
|
+
} | null;
|
|
1511
3372
|
|
|
1512
|
-
|
|
1513
|
-
|
|
3373
|
+
// Source: ../../packages/sanity/src/queries.ts
|
|
3374
|
+
// Variable: PAGE_QUERY
|
|
3375
|
+
// Query: *[_type == "page" && slug.current == $slug][0]{ _id, _type, title, description, "slug": slug.current, pageBuilder[]{ ..., buttons[]{ _key, _type, text, variant, "openInNewTab": url.openInNewTab, "href": select( url.type == "external" => url.external, url.internal->_type == "blog" => "/blog/" + url.internal->slug.current, url.internal->slug.current == "home" => "/", defined(url.internal->slug.current) => "/" + url.internal->slug.current, "#" ) }, _type == "faqAccordion" => { faqs[]->{ _id, title, richText } }, _type == "imageLinkCards" => { cards[]{ ..., "openInNewTab": url.openInNewTab, "href": select( url.type == "external" => url.external, url.internal->_type == "blog" => "/blog/" + url.internal->slug.current, url.internal->slug.current == "home" => "/", defined(url.internal->slug.current) => "/" + url.internal->slug.current, "#" ) } } }}
|
|
3376
|
+
export type PAGE_QUERY_RESULT = {
|
|
3377
|
+
_id: string;
|
|
3378
|
+
_type: "page";
|
|
3379
|
+
title: string;
|
|
3380
|
+
description: string | null;
|
|
3381
|
+
slug: string;
|
|
3382
|
+
pageBuilder: Array<
|
|
3383
|
+
| {
|
|
3384
|
+
_key: string;
|
|
3385
|
+
_type: "cta";
|
|
3386
|
+
eyebrow?: string;
|
|
3387
|
+
title?: string;
|
|
3388
|
+
richText?: RichText;
|
|
3389
|
+
buttons: Array<{
|
|
3390
|
+
_key: string;
|
|
3391
|
+
_type: "button";
|
|
3392
|
+
text: string | null;
|
|
3393
|
+
variant: "default" | "link" | "outline" | "secondary" | null;
|
|
3394
|
+
openInNewTab: boolean | null;
|
|
3395
|
+
href: string | "/" | "#" | null;
|
|
3396
|
+
}> | null;
|
|
3397
|
+
}
|
|
3398
|
+
| {
|
|
3399
|
+
_key: string;
|
|
3400
|
+
_type: "faqAccordion";
|
|
3401
|
+
eyebrow?: string;
|
|
3402
|
+
title: string;
|
|
3403
|
+
subtitle?: string;
|
|
3404
|
+
faqs: Array<{
|
|
3405
|
+
_id: string;
|
|
3406
|
+
title: string;
|
|
3407
|
+
richText: RichText | null;
|
|
3408
|
+
}> | null;
|
|
3409
|
+
buttons: null;
|
|
3410
|
+
}
|
|
3411
|
+
| {
|
|
3412
|
+
_key: string;
|
|
3413
|
+
_type: "featureCardsIcon";
|
|
3414
|
+
eyebrow?: string;
|
|
3415
|
+
title?: string;
|
|
3416
|
+
richText?: RichText;
|
|
3417
|
+
cards?: Array<{
|
|
3418
|
+
icon?: string;
|
|
3419
|
+
title?: string;
|
|
3420
|
+
richText?: RichText;
|
|
3421
|
+
_type: "featureCardIcon";
|
|
3422
|
+
_key: string;
|
|
3423
|
+
}>;
|
|
3424
|
+
buttons: null;
|
|
3425
|
+
}
|
|
3426
|
+
| {
|
|
3427
|
+
_key: string;
|
|
3428
|
+
_type: "hero";
|
|
3429
|
+
badge?: string;
|
|
3430
|
+
title?: string;
|
|
3431
|
+
richText?: RichText;
|
|
3432
|
+
image?: Image1;
|
|
3433
|
+
buttons: Array<{
|
|
3434
|
+
_key: string;
|
|
3435
|
+
_type: "button";
|
|
3436
|
+
text: string | null;
|
|
3437
|
+
variant: "default" | "link" | "outline" | "secondary" | null;
|
|
3438
|
+
openInNewTab: boolean | null;
|
|
3439
|
+
href: string | "/" | "#" | null;
|
|
3440
|
+
}> | null;
|
|
3441
|
+
}
|
|
3442
|
+
| {
|
|
3443
|
+
_key: string;
|
|
3444
|
+
_type: "imageLinkCards";
|
|
3445
|
+
eyebrow?: string;
|
|
3446
|
+
title: string;
|
|
3447
|
+
richText?: RichText;
|
|
3448
|
+
buttons: Array<{
|
|
3449
|
+
_key: string;
|
|
3450
|
+
_type: "button";
|
|
3451
|
+
text: string | null;
|
|
3452
|
+
variant: "default" | "link" | "outline" | "secondary" | null;
|
|
3453
|
+
openInNewTab: boolean | null;
|
|
3454
|
+
href: string | "/" | "#" | null;
|
|
3455
|
+
}> | null;
|
|
3456
|
+
cards: Array<{
|
|
3457
|
+
title: string;
|
|
3458
|
+
description?: string;
|
|
3459
|
+
image?: Image1;
|
|
3460
|
+
url?: CustomUrl;
|
|
3461
|
+
_type: "imageLinkCard";
|
|
3462
|
+
_key: string;
|
|
3463
|
+
openInNewTab: boolean | null;
|
|
3464
|
+
href: string | "/" | "#" | null;
|
|
3465
|
+
}> | null;
|
|
3466
|
+
}
|
|
3467
|
+
| {
|
|
3468
|
+
_key: string;
|
|
3469
|
+
_type: "richTextBlock";
|
|
3470
|
+
eyebrow?: string;
|
|
3471
|
+
title?: string;
|
|
3472
|
+
richText?: RichText;
|
|
3473
|
+
buttons: null;
|
|
3474
|
+
}
|
|
3475
|
+
| {
|
|
3476
|
+
_key: string;
|
|
3477
|
+
_type: "subscribeNewsletter";
|
|
3478
|
+
title?: string;
|
|
3479
|
+
subTitle?: RichText;
|
|
3480
|
+
helperText?: RichText;
|
|
3481
|
+
buttons: null;
|
|
3482
|
+
}
|
|
3483
|
+
> | null;
|
|
3484
|
+
} | null;
|
|
1514
3485
|
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
<title>{page?.title ?? "web"}</title>
|
|
1520
|
-
</head>
|
|
1521
|
-
<body style="font-family: system-ui, sans-serif; padding: 3rem;">
|
|
1522
|
-
<h1>{page?.title}</h1>
|
|
1523
|
-
<PageBuilder blocks={page?.pageBuilder ?? []} />
|
|
1524
|
-
<VisualEditing enabled={visualEditingEnabled} />
|
|
1525
|
-
</body>
|
|
1526
|
-
</html>
|
|
1527
|
-
`, ASTRO_SANITY_CONFIG, ASTRO_SANITY_CLI = `import { defineCliConfig } from "sanity/cli";
|
|
3486
|
+
// Source: ../../packages/sanity/src/queries.ts
|
|
3487
|
+
// Variable: PAGE_SLUGS_QUERY
|
|
3488
|
+
// Query: *[_type == "page" && defined(slug.current) && slug.current != "home"].slug.current
|
|
3489
|
+
export type PAGE_SLUGS_QUERY_RESULT = Array<string>;
|
|
1528
3490
|
|
|
1529
|
-
//
|
|
1530
|
-
//
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
3491
|
+
// Source: ../../packages/sanity/src/queries.ts
|
|
3492
|
+
// Variable: BLOG_INDEX_QUERY
|
|
3493
|
+
// Query: *[_type == "blog" && defined(slug.current)]|order(publishedAt desc){ _id, title, description, publishedAt, image, "slug": slug.current, "authors": authors[]->{ _id, name, position, image }}
|
|
3494
|
+
export type BLOG_INDEX_QUERY_RESULT = Array<{
|
|
3495
|
+
_id: string;
|
|
3496
|
+
title: string;
|
|
3497
|
+
description: string | null;
|
|
3498
|
+
publishedAt: string | null;
|
|
3499
|
+
image: {
|
|
3500
|
+
asset?: SanityImageAssetReference;
|
|
3501
|
+
media?: unknown;
|
|
3502
|
+
hotspot?: SanityImageHotspot;
|
|
3503
|
+
crop?: SanityImageCrop;
|
|
3504
|
+
alt?: string;
|
|
3505
|
+
_type: "image";
|
|
3506
|
+
} | null;
|
|
3507
|
+
slug: string;
|
|
3508
|
+
authors: Array<{
|
|
3509
|
+
_id: string;
|
|
3510
|
+
name: string;
|
|
3511
|
+
position: string | null;
|
|
3512
|
+
image: {
|
|
3513
|
+
asset?: SanityImageAssetReference;
|
|
3514
|
+
media?: unknown;
|
|
3515
|
+
hotspot?: SanityImageHotspot;
|
|
3516
|
+
crop?: SanityImageCrop;
|
|
3517
|
+
_type: "image";
|
|
3518
|
+
} | null;
|
|
3519
|
+
}> | null;
|
|
3520
|
+
}>;
|
|
3521
|
+
|
|
3522
|
+
// Source: ../../packages/sanity/src/queries.ts
|
|
3523
|
+
// Variable: BLOG_QUERY
|
|
3524
|
+
// Query: *[_type == "blog" && slug.current == $slug][0]{ _id, title, description, publishedAt, image, "slug": slug.current, "authors": authors[]->{ _id, name, position, image }, richText}
|
|
3525
|
+
export type BLOG_QUERY_RESULT = {
|
|
3526
|
+
_id: string;
|
|
3527
|
+
title: string;
|
|
3528
|
+
description: string | null;
|
|
3529
|
+
publishedAt: string | null;
|
|
3530
|
+
image: {
|
|
3531
|
+
asset?: SanityImageAssetReference;
|
|
3532
|
+
media?: unknown;
|
|
3533
|
+
hotspot?: SanityImageHotspot;
|
|
3534
|
+
crop?: SanityImageCrop;
|
|
3535
|
+
alt?: string;
|
|
3536
|
+
_type: "image";
|
|
3537
|
+
} | null;
|
|
3538
|
+
slug: string;
|
|
3539
|
+
authors: Array<{
|
|
3540
|
+
_id: string;
|
|
3541
|
+
name: string;
|
|
3542
|
+
position: string | null;
|
|
3543
|
+
image: {
|
|
3544
|
+
asset?: SanityImageAssetReference;
|
|
3545
|
+
media?: unknown;
|
|
3546
|
+
hotspot?: SanityImageHotspot;
|
|
3547
|
+
crop?: SanityImageCrop;
|
|
3548
|
+
_type: "image";
|
|
3549
|
+
} | null;
|
|
3550
|
+
}> | null;
|
|
3551
|
+
richText: RichText | null;
|
|
3552
|
+
} | null;
|
|
3553
|
+
|
|
3554
|
+
// Source: ../../packages/sanity/src/queries.ts
|
|
3555
|
+
// Variable: BLOG_SLUGS_QUERY
|
|
3556
|
+
// Query: *[_type == "blog" && defined(slug.current)].slug.current
|
|
3557
|
+
export type BLOG_SLUGS_QUERY_RESULT = Array<string>;
|
|
3558
|
+
|
|
3559
|
+
// Source: ../../packages/sanity/src/queries.ts
|
|
3560
|
+
// Variable: SETTINGS_QUERY
|
|
3561
|
+
// Query: *[_type == "settings"][0]{ siteTitle, siteDescription, socialImage }
|
|
3562
|
+
export type SETTINGS_QUERY_RESULT = {
|
|
3563
|
+
siteTitle: string;
|
|
3564
|
+
siteDescription: string | null;
|
|
3565
|
+
socialImage: {
|
|
3566
|
+
asset?: SanityImageAssetReference;
|
|
3567
|
+
media?: unknown;
|
|
3568
|
+
hotspot?: SanityImageHotspot;
|
|
3569
|
+
crop?: SanityImageCrop;
|
|
3570
|
+
_type: "image";
|
|
3571
|
+
} | null;
|
|
3572
|
+
} | null;
|
|
3573
|
+
|
|
3574
|
+
// Query TypeMap
|
|
3575
|
+
import "@sanity/client";
|
|
3576
|
+
declare module "@sanity/client" {
|
|
3577
|
+
interface SanityQueries {
|
|
3578
|
+
'*[_type == "page" && slug.current == "home"][0]{\\n _id,\\n _type,\\n title,\\n description,\\n "slug": slug.current,\\n \\n pageBuilder[]{\\n ...,\\n buttons[]{ _key, _type, text, variant, \\n "openInNewTab": url.openInNewTab,\\n "href": select(\\n url.type == "external" => url.external,\\n url.internal->_type == "blog" => "/blog/" + url.internal->slug.current,\\n url.internal->slug.current == "home" => "/",\\n defined(url.internal->slug.current) => "/" + url.internal->slug.current,\\n "#"\\n )\\n },\\n _type == "faqAccordion" => { faqs[]->{ _id, title, richText } },\\n _type == "imageLinkCards" => { cards[]{ ..., \\n "openInNewTab": url.openInNewTab,\\n "href": select(\\n url.type == "external" => url.external,\\n url.internal->_type == "blog" => "/blog/" + url.internal->slug.current,\\n url.internal->slug.current == "home" => "/",\\n defined(url.internal->slug.current) => "/" + url.internal->slug.current,\\n "#"\\n )\\n } }\\n }\\n\\n}': HOME_PAGE_QUERY_RESULT;
|
|
3579
|
+
'*[_type == "page" && slug.current == $slug][0]{\\n _id,\\n _type,\\n title,\\n description,\\n "slug": slug.current,\\n \\n pageBuilder[]{\\n ...,\\n buttons[]{ _key, _type, text, variant, \\n "openInNewTab": url.openInNewTab,\\n "href": select(\\n url.type == "external" => url.external,\\n url.internal->_type == "blog" => "/blog/" + url.internal->slug.current,\\n url.internal->slug.current == "home" => "/",\\n defined(url.internal->slug.current) => "/" + url.internal->slug.current,\\n "#"\\n )\\n },\\n _type == "faqAccordion" => { faqs[]->{ _id, title, richText } },\\n _type == "imageLinkCards" => { cards[]{ ..., \\n "openInNewTab": url.openInNewTab,\\n "href": select(\\n url.type == "external" => url.external,\\n url.internal->_type == "blog" => "/blog/" + url.internal->slug.current,\\n url.internal->slug.current == "home" => "/",\\n defined(url.internal->slug.current) => "/" + url.internal->slug.current,\\n "#"\\n )\\n } }\\n }\\n\\n}': PAGE_QUERY_RESULT;
|
|
3580
|
+
'*[_type == "page" && defined(slug.current) && slug.current != "home"].slug.current': PAGE_SLUGS_QUERY_RESULT;
|
|
3581
|
+
'*[_type == "blog" && defined(slug.current)]|order(publishedAt desc){\\n _id,\\n title,\\n description,\\n publishedAt,\\n image,\\n "slug": slug.current,\\n "authors": authors[]->{ _id, name, position, image }\\n}': BLOG_INDEX_QUERY_RESULT;
|
|
3582
|
+
'*[_type == "blog" && slug.current == $slug][0]{\\n _id,\\n title,\\n description,\\n publishedAt,\\n image,\\n "slug": slug.current,\\n "authors": authors[]->{ _id, name, position, image }\\n, richText}': BLOG_QUERY_RESULT;
|
|
3583
|
+
'*[_type == "blog" && defined(slug.current)].slug.current': BLOG_SLUGS_QUERY_RESULT;
|
|
3584
|
+
'*[_type == "settings"][0]{ siteTitle, siteDescription, socialImage }': SETTINGS_QUERY_RESULT;
|
|
3585
|
+
}
|
|
1547
3586
|
}
|
|
1548
|
-
|
|
3587
|
+
`;
|
|
3588
|
+
|
|
3589
|
+
// ../../packages/registry/src/content/sanity/index.ts
|
|
3590
|
+
var j6 = (value) => `${JSON.stringify(value, null, 2)}
|
|
3591
|
+
`, SANITY_PACKAGE_JSON, SANITY_BLOCKS_PACKAGE_JSON, prependAstroConfigStatement = (content, block, marker) => content.includes(marker) ? content : content.replace("export default defineConfig({", `${block}
|
|
1549
3592
|
export default defineConfig({`), addAstroOptimizeDeps = (content) => {
|
|
1550
3593
|
if (content.includes("react/compiler-runtime"))
|
|
1551
3594
|
return content;
|
|
@@ -1562,23 +3605,12 @@ export default defineConfig({`), addAstroOptimizeDeps = (content) => {
|
|
|
1562
3605
|
return content.replace(/vite:\s*\{/, (m) => `${m} ${field}`);
|
|
1563
3606
|
return content.replace(/defineConfig\(\{/, (m) => `${m}
|
|
1564
3607
|
vite: { ${field} },`);
|
|
1565
|
-
},
|
|
1566
|
-
const pkg = JSON.parse(pkgJson);
|
|
1567
|
-
const merged = { ...pkg.scripts, ...scripts };
|
|
1568
|
-
const sorted = Object.fromEntries(Object.entries(merged).sort(([a], [b]) => a.localeCompare(b)));
|
|
1569
|
-
return `${JSON.stringify({ ...pkg, scripts: sorted }, null, 2)}
|
|
1570
|
-
`;
|
|
1571
|
-
}, TYPEGEN = "sanity schema extract --enforce-required-fields --path sanity/schema.json && sanity typegen generate", patchWebPackageJson = (content) => addScripts(content, {
|
|
1572
|
-
"sanity:typegen": TYPEGEN,
|
|
1573
|
-
prebuild: TYPEGEN,
|
|
1574
|
-
...content.includes('"next build"') ? { "precheck-types": TYPEGEN } : {},
|
|
1575
|
-
"sanity:seed": "sanity dataset import sanity/seed.ndjson"
|
|
1576
|
-
}), NEXT_LAYOUT_SIGNATURE = "export default function RootLayout({ children }: { children: ReactNode }) {", patchNextLayout = (content) => {
|
|
3608
|
+
}, NEXT_LAYOUT_SIGNATURE = "export default function RootLayout({ children }: { children: ReactNode }) {", patchNextLayout = (content) => {
|
|
1577
3609
|
const withImports = [
|
|
1578
3610
|
'import { draftMode } from "next/headers";',
|
|
1579
3611
|
'import { VisualEditing } from "next-sanity/visual-editing";',
|
|
1580
3612
|
'import { DisableDraftMode } from "../sanity/disable-draft-mode";',
|
|
1581
|
-
'import { SanityLive } from "
|
|
3613
|
+
'import { SanityLive } from "@workspace/sanity/live";'
|
|
1582
3614
|
].reduce(ensureImport, content);
|
|
1583
3615
|
const asAsync = replaceOnce(withImports, NEXT_LAYOUT_SIGNATURE, `export default async function RootLayout({ children }: { children: ReactNode }) {
|
|
1584
3616
|
const { isEnabled: isDraft } = await draftMode();`);
|
|
@@ -1600,15 +3632,15 @@ export default defineConfig({`), addAstroOptimizeDeps = (content) => {
|
|
|
1600
3632
|
return content;
|
|
1601
3633
|
const withImports = [
|
|
1602
3634
|
'import { VisualEditing } from "@sanity/astro/visual-editing";',
|
|
1603
|
-
'import
|
|
1604
|
-
'import {
|
|
1605
|
-
'import {
|
|
1606
|
-
'import {
|
|
1607
|
-
'import
|
|
3635
|
+
'import { visualEditingEnabled } from "@workspace/sanity/env";',
|
|
3636
|
+
'import { loadQuery } from "@workspace/sanity/fetch";',
|
|
3637
|
+
'import { HOME_PAGE_QUERY } from "@workspace/sanity/queries";',
|
|
3638
|
+
'import type { HOME_PAGE_QUERY_RESULT } from "@workspace/sanity/types";',
|
|
3639
|
+
'import PageBuilder from "@workspace/sanity-blocks/PageBuilder.astro";'
|
|
1608
3640
|
].reduce(ensureImport, content);
|
|
1609
3641
|
const withFetch = replaceOnce(withImports, 'log.info("rendering home");', `log.info("rendering home");
|
|
1610
3642
|
|
|
1611
|
-
const page = await loadQuery<
|
|
3643
|
+
const page = await loadQuery<HOME_PAGE_QUERY_RESULT>(HOME_PAGE_QUERY);`);
|
|
1612
3644
|
const withHeading = replaceOnce(withFetch, " <h1>create-turbo-wizard</h1>", ' <h1>{page?.title ?? "create-turbo-wizard"}</h1>');
|
|
1613
3645
|
const withBlocks = replaceOnce(withHeading, " <p>Astro + Turborepo, scaffolded.</p>", [
|
|
1614
3646
|
" <PageBuilder blocks={page?.pageBuilder ?? []} />",
|
|
@@ -1622,136 +3654,142 @@ const page = await loadQuery<SanityPage>(HOME_PAGE_QUERY);`);
|
|
|
1622
3654
|
}, README_SECTION = `
|
|
1623
3655
|
## Sanity
|
|
1624
3656
|
|
|
1625
|
-
The Studio is
|
|
1626
|
-
|
|
3657
|
+
The Studio is its own app, \`apps/studio\` (\`pnpm dev:studio\`, port 3333) — a Vite app, so
|
|
3658
|
+
both frontends share the same one. It owns the document schemas, Studio structure and
|
|
3659
|
+
sample content. The read path (client, queries, generated types) is \`packages/sanity\` and
|
|
3660
|
+
the page-builder blocks — schemas and renderers together — are \`packages/sanity-blocks\`.
|
|
1627
3661
|
|
|
1628
3662
|
\`\`\`sh
|
|
1629
|
-
|
|
1630
|
-
cp
|
|
1631
|
-
pnpm
|
|
1632
|
-
pnpm sanity:
|
|
3663
|
+
cp .env.example apps/web/.env.local # or apps/web/.env on Astro — the project id
|
|
3664
|
+
cp apps/studio/.env.example apps/studio/.env # and SANITY_STUDIO_PROJECT_ID
|
|
3665
|
+
pnpm dev:studio # the Studio on http://localhost:3333
|
|
3666
|
+
cd apps/studio && pnpm sanity:seed # import seed.ndjson into the dataset
|
|
3667
|
+
cd apps/studio && pnpm extract && pnpm type # regenerate packages/sanity/src/sanity.types.ts
|
|
1633
3668
|
\`\`\`
|
|
1634
3669
|
|
|
3670
|
+
\`packages/sanity/src/sanity.types.ts\` ships pre-generated, so a fresh clone type-checks
|
|
3671
|
+
before anything is run; re-run \`extract\` + \`type\` after every schema or query edit.
|
|
3672
|
+
|
|
1635
3673
|
Without a project id the site builds and serves an empty state — no credentials required.
|
|
1636
3674
|
|
|
3675
|
+
### What's in the schema
|
|
3676
|
+
|
|
3677
|
+
\`page\` documents compose a page builder out of seven blocks — hero, rich text, feature
|
|
3678
|
+
cards, image link cards, FAQ accordion, newsletter and CTA — and \`blog\` posts carry an
|
|
3679
|
+
\`author\` reference, a publish date and a Portable Text body. Pages route by slug
|
|
3680
|
+
(\`/about\`), posts live under \`/blog\`. The shape follows Roboto Studio's
|
|
3681
|
+
\`turbo-start-sanity\`, so components and GROQ from that repo drop in unchanged.
|
|
3682
|
+
|
|
3683
|
+
Links are stored as \`customUrl\` (internal document or external URL) and flattened to a
|
|
3684
|
+
plain \`href\` in \`packages/sanity/src/queries.ts\`, so components never branch on the
|
|
3685
|
+
link type.
|
|
3686
|
+
|
|
1637
3687
|
### Visual Editing
|
|
1638
3688
|
|
|
1639
3689
|
Next: open the Studio's **Presentation** tab — draft mode enables via
|
|
1640
3690
|
\`/api/draft-mode/enable\`; leave it with the on-page "Disable draft mode" button.
|
|
1641
3691
|
Astro: set \`PUBLIC_SANITY_VISUAL_EDITING_ENABLED=true\` and \`SANITY_API_READ_TOKEN\`,
|
|
1642
3692
|
then use the dev server — overlays need server rendering (add an SSR adapter for a
|
|
1643
|
-
production preview deployment).
|
|
3693
|
+
production preview deployment). \`PUBLIC_SANITY_STUDIO_URL\` is where stega sends a
|
|
3694
|
+
click-through; it defaults to the Studio's own dev origin.
|
|
1644
3695
|
`, sanity;
|
|
1645
3696
|
var init_sanity = __esm(() => {
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
structureTool({ structure }),
|
|
1675
|
-
],
|
|
1676
|
-
schema: { types: schemaTypes },
|
|
1677
|
-
${DOCUMENT_GUARD}
|
|
1678
|
-
});
|
|
1679
|
-
`;
|
|
1680
|
-
ASTRO_SANITY_CONFIG = `import { defineConfig } from "sanity";
|
|
1681
|
-
import { presentationTool } from "sanity/presentation";
|
|
1682
|
-
import { structureTool } from "sanity/structure";
|
|
1683
|
-
import { locations, mainDocuments } from "./sanity/presentation";
|
|
1684
|
-
import { schemaTypes } from "./sanity/schema/index";
|
|
1685
|
-
import { singletonActions, singletonTypes, structure } from "./sanity/structure";
|
|
1686
|
-
|
|
1687
|
-
export default defineConfig({
|
|
1688
|
-
name: "default",
|
|
1689
|
-
title: "Studio",
|
|
1690
|
-
projectId: import.meta.env.PUBLIC_SANITY_PROJECT_ID || "placeholder",
|
|
1691
|
-
dataset: import.meta.env.PUBLIC_SANITY_DATASET || "production",
|
|
1692
|
-
plugins: [
|
|
1693
|
-
presentationTool({
|
|
1694
|
-
resolve: { mainDocuments, locations },
|
|
1695
|
-
// No previewMode: Astro has no draft-mode routes. Draft content comes from
|
|
1696
|
-
// loadQuery's perspective switch (PUBLIC_SANITY_VISUAL_EDITING_ENABLED + token),
|
|
1697
|
-
// which needs a rendering server — \`astro dev\`, or an SSR adapter in production.
|
|
1698
|
-
previewUrl: { initial: "/" },
|
|
1699
|
-
}),
|
|
1700
|
-
structureTool({ structure }),
|
|
1701
|
-
],
|
|
1702
|
-
schema: { types: schemaTypes },
|
|
1703
|
-
${DOCUMENT_GUARD}
|
|
1704
|
-
});
|
|
1705
|
-
`;
|
|
3697
|
+
init_seed();
|
|
3698
|
+
init_studio();
|
|
3699
|
+
SANITY_PACKAGE_JSON = j6({
|
|
3700
|
+
name: "@workspace/sanity",
|
|
3701
|
+
version: "0.0.0",
|
|
3702
|
+
private: true,
|
|
3703
|
+
type: "module",
|
|
3704
|
+
exports: {
|
|
3705
|
+
"./env": "./src/env.ts",
|
|
3706
|
+
"./client": "./src/client.ts",
|
|
3707
|
+
"./live": "./src/live.ts",
|
|
3708
|
+
"./fetch": "./src/fetch.ts",
|
|
3709
|
+
"./image": "./src/image.ts",
|
|
3710
|
+
"./queries": "./src/queries.ts",
|
|
3711
|
+
"./types": "./src/sanity.types.ts"
|
|
3712
|
+
}
|
|
3713
|
+
});
|
|
3714
|
+
SANITY_BLOCKS_PACKAGE_JSON = j6({
|
|
3715
|
+
name: "@workspace/sanity-blocks",
|
|
3716
|
+
version: "0.0.0",
|
|
3717
|
+
private: true,
|
|
3718
|
+
type: "module",
|
|
3719
|
+
exports: {
|
|
3720
|
+
".": "./src/schema.ts",
|
|
3721
|
+
"./blocks": "./src/blocks.tsx",
|
|
3722
|
+
"./PageBuilder.astro": "./src/PageBuilder.astro"
|
|
3723
|
+
}
|
|
3724
|
+
});
|
|
1706
3725
|
sanity = {
|
|
1707
3726
|
id: "sanity",
|
|
1708
3727
|
kind: "content",
|
|
1709
3728
|
prompt: {
|
|
1710
3729
|
label: "Sanity",
|
|
1711
|
-
hint: "Headless CMS with
|
|
3730
|
+
hint: "Headless CMS with a Studio of its own (apps/studio)"
|
|
1712
3731
|
},
|
|
1713
3732
|
templates: [
|
|
1714
|
-
{ path: "
|
|
1715
|
-
{ path: "
|
|
1716
|
-
{ path: "
|
|
1717
|
-
{ path: "
|
|
1718
|
-
{ path: "
|
|
1719
|
-
{ path: "apps/
|
|
1720
|
-
{ path: "apps/
|
|
1721
|
-
{ path: "apps/
|
|
3733
|
+
{ path: "packages/sanity/package.json", content: SANITY_PACKAGE_JSON },
|
|
3734
|
+
{ path: "packages/sanity/src/queries.ts", content: QUERIES },
|
|
3735
|
+
{ path: "packages/sanity/src/sanity.types.ts", content: SANITY_TYPES },
|
|
3736
|
+
{ path: "packages/sanity-blocks/package.json", content: SANITY_BLOCKS_PACKAGE_JSON },
|
|
3737
|
+
{ path: "packages/sanity-blocks/src/schema.ts", content: SCHEMA_BLOCKS },
|
|
3738
|
+
{ path: "apps/studio/package.json", content: STUDIO_PACKAGE_JSON },
|
|
3739
|
+
{ path: "apps/studio/tsconfig.json", content: STUDIO_TSCONFIG },
|
|
3740
|
+
{ path: "apps/studio/sanity.cli.ts", content: STUDIO_CLI },
|
|
3741
|
+
{ path: "apps/studio/schemaTypes/index.ts", content: SCHEMA_INDEX },
|
|
3742
|
+
{ path: "apps/studio/schemaTypes/definitions.ts", content: SCHEMA_DEFINITIONS },
|
|
3743
|
+
{ path: "apps/studio/schemaTypes/page.ts", content: SCHEMA_PAGE },
|
|
3744
|
+
{ path: "apps/studio/schemaTypes/blog.ts", content: SCHEMA_BLOG },
|
|
3745
|
+
{ path: "apps/studio/schemaTypes/settings.ts", content: SCHEMA_SETTINGS },
|
|
3746
|
+
{ path: "apps/studio/structure.ts", content: STRUCTURE },
|
|
3747
|
+
{ path: "apps/studio/presentation.ts", content: PRESENTATION },
|
|
3748
|
+
{ path: "apps/studio/seed.ndjson", content: SEED_NDJSON }
|
|
1722
3749
|
],
|
|
1723
3750
|
fragments: {
|
|
1724
3751
|
next: [
|
|
1725
|
-
{ path: "
|
|
1726
|
-
{ path: "
|
|
1727
|
-
{ path: "
|
|
1728
|
-
{ path: "
|
|
1729
|
-
{ path: "
|
|
1730
|
-
{ path: "
|
|
3752
|
+
{ path: "packages/sanity/src/env.ts", content: NEXT_ENV },
|
|
3753
|
+
{ path: "packages/sanity/src/client.ts", content: NEXT_CLIENT },
|
|
3754
|
+
{ path: "packages/sanity/src/live.ts", content: NEXT_LIVE },
|
|
3755
|
+
{ path: "packages/sanity/src/fetch.ts", content: NEXT_FETCH },
|
|
3756
|
+
{ path: "packages/sanity/src/image.ts", content: NEXT_IMAGE },
|
|
3757
|
+
{ path: "packages/sanity-blocks/src/blocks.tsx", content: NEXT_PAGE_BUILDER },
|
|
3758
|
+
{ path: "apps/studio/sanity.config.ts", content: STUDIO_CONFIG_NEXT },
|
|
3759
|
+
{ path: "apps/studio/.env.example", content: STUDIO_ENV_EXAMPLE_NEXT },
|
|
1731
3760
|
{ path: "apps/web/sanity/disable-draft-mode.tsx", content: NEXT_DISABLE_DRAFT_MODE },
|
|
1732
|
-
{ path: "apps/web/sanity.config.ts", content: NEXT_SANITY_CONFIG },
|
|
1733
|
-
{ path: "apps/web/sanity.cli.ts", content: NEXT_SANITY_CLI },
|
|
1734
|
-
{ path: "apps/web/app/studio/[[...tool]]/page.tsx", content: NEXT_STUDIO_PAGE },
|
|
1735
3761
|
{ path: "apps/web/app/api/draft-mode/enable/route.ts", content: NEXT_DRAFT_MODE_ROUTE },
|
|
1736
3762
|
{
|
|
1737
3763
|
path: "apps/web/app/api/draft-mode/disable/route.ts",
|
|
1738
3764
|
content: NEXT_DRAFT_MODE_DISABLE_ROUTE
|
|
1739
3765
|
},
|
|
1740
|
-
{ path: "apps/web/app/[slug]/page.tsx", content: NEXT_SLUG_PAGE }
|
|
3766
|
+
{ path: "apps/web/app/[slug]/page.tsx", content: NEXT_SLUG_PAGE },
|
|
3767
|
+
{ path: "apps/web/app/blog/page.tsx", content: NEXT_BLOG_INDEX_PAGE },
|
|
3768
|
+
{ path: "apps/web/app/blog/[slug]/page.tsx", content: NEXT_BLOG_POST_PAGE }
|
|
1741
3769
|
],
|
|
1742
3770
|
astro: [
|
|
1743
|
-
{ path: "
|
|
1744
|
-
{ path: "
|
|
1745
|
-
{ path: "
|
|
1746
|
-
{ path: "
|
|
1747
|
-
{ path: "apps/
|
|
1748
|
-
{ path: "apps/
|
|
1749
|
-
{ path: "apps/web/src/
|
|
1750
|
-
{ path: "apps/web/src/pages/
|
|
3771
|
+
{ path: "packages/sanity/src/env.ts", content: ASTRO_ENV },
|
|
3772
|
+
{ path: "packages/sanity/src/fetch.ts", content: ASTRO_FETCH },
|
|
3773
|
+
{ path: "packages/sanity/src/image.ts", content: ASTRO_IMAGE },
|
|
3774
|
+
{ path: "packages/sanity-blocks/src/PageBuilder.astro", content: ASTRO_PAGE_BUILDER },
|
|
3775
|
+
{ path: "apps/studio/sanity.config.ts", content: STUDIO_CONFIG_ASTRO },
|
|
3776
|
+
{ path: "apps/studio/.env.example", content: STUDIO_ENV_EXAMPLE_ASTRO },
|
|
3777
|
+
{ path: "apps/web/src/pages/[slug].astro", content: ASTRO_SLUG_PAGE },
|
|
3778
|
+
{ path: "apps/web/src/pages/blog/index.astro", content: ASTRO_BLOG_INDEX_PAGE },
|
|
3779
|
+
{ path: "apps/web/src/pages/blog/[slug].astro", content: ASTRO_BLOG_POST_PAGE }
|
|
1751
3780
|
]
|
|
1752
3781
|
},
|
|
3782
|
+
scripts: {
|
|
3783
|
+
"dev:studio": "turbo run dev --filter=studio",
|
|
3784
|
+
"build:studio": "turbo run build --filter=studio"
|
|
3785
|
+
},
|
|
1753
3786
|
deps: [
|
|
1754
|
-
{ name: "
|
|
3787
|
+
{ name: "@workspace/sanity", version: "workspace:*", target: "app" },
|
|
3788
|
+
{ name: "@workspace/sanity-blocks", version: "workspace:*", target: "app" },
|
|
3789
|
+
{ name: "groq", version: "^6.7.0", target: "sanity" },
|
|
3790
|
+
{ name: "@sanity/client", version: "^7.26.0", target: "sanity" },
|
|
3791
|
+
{ name: "sanity", version: "^5.31.1", target: "sanity-blocks" },
|
|
3792
|
+
{ name: "@workspace/sanity", version: "workspace:*", target: "sanity-blocks" }
|
|
1755
3793
|
],
|
|
1756
3794
|
frontendDeps: {
|
|
1757
3795
|
next: [
|
|
@@ -1759,59 +3797,40 @@ ${DOCUMENT_GUARD}
|
|
|
1759
3797
|
{ name: "@portabletext/react", version: "^7.0.1", target: "app" },
|
|
1760
3798
|
{ name: "@sanity/image-url", version: "^2.1.1", target: "app" },
|
|
1761
3799
|
{ name: "@sanity/client", version: "^7.26.0", target: "app" },
|
|
1762
|
-
{ name: "sanity", version: "^
|
|
1763
|
-
{ name: "
|
|
3800
|
+
{ name: "next-sanity", version: "^13.2.3", target: "sanity" },
|
|
3801
|
+
{ name: "@sanity/image-url", version: "^2.1.1", target: "sanity" },
|
|
3802
|
+
{ name: "next", version: "^16.2.0", target: "sanity" },
|
|
3803
|
+
{ name: "@portabletext/react", version: "^7.0.1", target: "sanity-blocks" },
|
|
3804
|
+
{ name: "next", version: "^16.2.0", target: "sanity-blocks" },
|
|
3805
|
+
{ name: "react", version: "^19.0.0", target: "sanity-blocks" }
|
|
1764
3806
|
],
|
|
1765
3807
|
astro: [
|
|
1766
3808
|
{ name: "@sanity/astro", version: "^3.5.0", target: "app" },
|
|
1767
3809
|
{ name: "astro-portabletext", version: "^0.13.0", target: "app" },
|
|
1768
|
-
{ name: "@portabletext/types", version: "^4.0.2", target: "app" },
|
|
1769
3810
|
{ name: "@sanity/image-url", version: "^2.1.1", target: "app" },
|
|
1770
3811
|
{ name: "@sanity/client", version: "^7.26.0", target: "app" },
|
|
1771
|
-
{ name: "sanity", version: "^5.31.1", target: "app" },
|
|
1772
3812
|
{ name: "@sanity/ui", version: "^3.5.1", target: "app" },
|
|
1773
3813
|
{ name: "react-compiler-runtime", version: "^1.0.0", target: "app" },
|
|
1774
3814
|
{ name: "react-is", version: "^19.0.0", target: "app" },
|
|
1775
3815
|
{ name: "lodash", version: "^4.17.21", target: "app" },
|
|
1776
|
-
{ name: "
|
|
1777
|
-
{ name: "
|
|
3816
|
+
{ name: "@sanity/image-url", version: "^2.1.1", target: "sanity" },
|
|
3817
|
+
{ name: "astro-portabletext", version: "^0.13.0", target: "sanity-blocks" }
|
|
1778
3818
|
]
|
|
1779
3819
|
},
|
|
1780
3820
|
env: [
|
|
1781
3821
|
{ key: "NEXT_PUBLIC_SANITY_PROJECT_ID", scope: "client" },
|
|
1782
3822
|
{ key: "NEXT_PUBLIC_SANITY_DATASET", scope: "client" },
|
|
1783
3823
|
{ key: "NEXT_PUBLIC_SANITY_API_VERSION", scope: "client" },
|
|
3824
|
+
{ key: "NEXT_PUBLIC_SANITY_STUDIO_URL", scope: "client" },
|
|
3825
|
+
{ key: "SANITY_STUDIO_PROJECT_ID", scope: "server" },
|
|
3826
|
+
{ key: "SANITY_STUDIO_DATASET", scope: "server" },
|
|
3827
|
+
{ key: "SANITY_STUDIO_PRESENTATION_URL", scope: "server" },
|
|
1784
3828
|
{ key: "SANITY_API_READ_TOKEN", scope: "server" },
|
|
1785
3829
|
{ key: "PUBLIC_SANITY_PROJECT_ID", scope: "client" },
|
|
1786
3830
|
{ key: "PUBLIC_SANITY_DATASET", scope: "client" },
|
|
3831
|
+
{ key: "PUBLIC_SANITY_STUDIO_URL", scope: "client" },
|
|
1787
3832
|
{ key: "PUBLIC_SANITY_VISUAL_EDITING_ENABLED", scope: "client" }
|
|
1788
3833
|
],
|
|
1789
|
-
owns: [
|
|
1790
|
-
"apps/web/sanity/schema/blocks.ts",
|
|
1791
|
-
"apps/web/sanity/schema/page.ts",
|
|
1792
|
-
"apps/web/sanity/schema/settings.ts",
|
|
1793
|
-
"apps/web/sanity/schema/index.ts",
|
|
1794
|
-
"apps/web/sanity/structure.ts",
|
|
1795
|
-
"apps/web/sanity/presentation.ts",
|
|
1796
|
-
"apps/web/sanity/queries.ts",
|
|
1797
|
-
"apps/web/sanity/seed.ndjson",
|
|
1798
|
-
"apps/web/sanity/env.ts",
|
|
1799
|
-
"apps/web/sanity/client.ts",
|
|
1800
|
-
"apps/web/sanity/live.ts",
|
|
1801
|
-
"apps/web/sanity/fetch.ts",
|
|
1802
|
-
"apps/web/sanity/image.ts",
|
|
1803
|
-
"apps/web/sanity/types.ts",
|
|
1804
|
-
"apps/web/sanity/page-builder.tsx",
|
|
1805
|
-
"apps/web/sanity/disable-draft-mode.tsx",
|
|
1806
|
-
"apps/web/sanity.config.ts",
|
|
1807
|
-
"apps/web/sanity.cli.ts",
|
|
1808
|
-
"apps/web/app/studio/[[...tool]]/page.tsx",
|
|
1809
|
-
"apps/web/app/api/draft-mode/enable/route.ts",
|
|
1810
|
-
"apps/web/app/api/draft-mode/disable/route.ts",
|
|
1811
|
-
"apps/web/app/[slug]/page.tsx",
|
|
1812
|
-
"apps/web/src/components/PageBuilder.astro",
|
|
1813
|
-
"apps/web/src/pages/[slug].astro"
|
|
1814
|
-
],
|
|
1815
3834
|
patches: [
|
|
1816
3835
|
{
|
|
1817
3836
|
path: "apps/web/next.config.ts",
|
|
@@ -1821,14 +3840,13 @@ ${DOCUMENT_GUARD}
|
|
|
1821
3840
|
{ path: "apps/web/app/page.tsx", apply: replaceHomePage(NEXT_HOME_PAGE) },
|
|
1822
3841
|
{
|
|
1823
3842
|
path: "apps/web/astro.config.mjs",
|
|
1824
|
-
apply: (content) => addAstroOptimizeDeps(addAstroIntegration(prependAstroConfigStatement(content, ASTRO_ENV_LOADER, "process.loadEnvFile"), 'import sanity from "@sanity/astro";', `sanity({ projectId: process.env.PUBLIC_SANITY_PROJECT_ID ?? 'placeholder', dataset: process.env.PUBLIC_SANITY_DATASET ?? 'production', apiVersion: '2026-02-01', useCdn: false,
|
|
3843
|
+
apply: (content) => addAstroOptimizeDeps(addAstroIntegration(prependAstroConfigStatement(content, ASTRO_ENV_LOADER, "process.loadEnvFile"), 'import sanity from "@sanity/astro";', `sanity({ projectId: process.env.PUBLIC_SANITY_PROJECT_ID ?? 'placeholder', dataset: process.env.PUBLIC_SANITY_DATASET ?? 'production', apiVersion: '2026-02-01', useCdn: false, stega: { studioUrl: process.env.PUBLIC_SANITY_STUDIO_URL ?? 'http://localhost:3333' } })`))
|
|
1825
3844
|
},
|
|
1826
3845
|
{
|
|
1827
3846
|
path: "apps/web/src/env.d.ts",
|
|
1828
3847
|
apply: (content) => ensureImport(content, ASTRO_MODULE_REF)
|
|
1829
3848
|
},
|
|
1830
3849
|
{ path: "apps/web/src/pages/index.astro", apply: patchAstroIndex },
|
|
1831
|
-
{ path: "apps/web/package.json", apply: patchWebPackageJson },
|
|
1832
3850
|
{
|
|
1833
3851
|
path: "README.md",
|
|
1834
3852
|
apply: (content) => content.includes("## Sanity") ? content : content + README_SECTION
|
|
@@ -1838,7 +3856,7 @@ ${DOCUMENT_GUARD}
|
|
|
1838
3856
|
});
|
|
1839
3857
|
|
|
1840
3858
|
// ../../packages/registry/src/frontends/astro.ts
|
|
1841
|
-
var
|
|
3859
|
+
var j7 = (value) => `${JSON.stringify(value, null, 2)}
|
|
1842
3860
|
`, ASTRO_CONFIG = `import react from "@astrojs/react";
|
|
1843
3861
|
import { defineConfig } from "astro/config";
|
|
1844
3862
|
|
|
@@ -1871,7 +3889,7 @@ var init_astro = __esm(() => {
|
|
|
1871
3889
|
templates: [
|
|
1872
3890
|
{
|
|
1873
3891
|
path: "apps/web/package.json",
|
|
1874
|
-
content:
|
|
3892
|
+
content: j7({
|
|
1875
3893
|
name: "web",
|
|
1876
3894
|
version: "0.0.0",
|
|
1877
3895
|
private: true,
|
|
@@ -1903,7 +3921,7 @@ var init_astro = __esm(() => {
|
|
|
1903
3921
|
{ path: "apps/web/astro.config.mjs", content: ASTRO_CONFIG },
|
|
1904
3922
|
{
|
|
1905
3923
|
path: "apps/web/tsconfig.json",
|
|
1906
|
-
content:
|
|
3924
|
+
content: j7({
|
|
1907
3925
|
extends: "astro/tsconfigs/strict",
|
|
1908
3926
|
compilerOptions: { jsx: "react-jsx", jsxImportSource: "react" },
|
|
1909
3927
|
include: [".astro/types.d.ts", "**/*"],
|
|
@@ -1913,17 +3931,44 @@ var init_astro = __esm(() => {
|
|
|
1913
3931
|
{ path: "apps/web/src/env.d.ts", content: `/// <reference types="astro/client" />
|
|
1914
3932
|
` },
|
|
1915
3933
|
{ path: "apps/web/src/pages/index.astro", content: INDEX_ASTRO }
|
|
1916
|
-
],
|
|
1917
|
-
owns: [
|
|
1918
|
-
"apps/web/package.json",
|
|
1919
|
-
"apps/web/astro.config.mjs",
|
|
1920
|
-
"apps/web/tsconfig.json",
|
|
1921
|
-
"apps/web/src/env.d.ts",
|
|
1922
|
-
"apps/web/src/pages/index.astro"
|
|
1923
3934
|
]
|
|
1924
3935
|
};
|
|
1925
3936
|
});
|
|
1926
3937
|
|
|
3938
|
+
// ../../packages/registry/src/frontends/deploy-target.ts
|
|
3939
|
+
var ASTRO_SSR_MODULES, ASTROJS_VERCEL_VERSION = "^9.0.5", NODE_IMPORT = 'import node from "@astrojs/node";', NODE_ADAPTER = 'node({ mode: "standalone" })', VERCEL_IMPORT = 'import vercel from "@astrojs/vercel";', VERCEL_ADAPTER = "vercel()", deployDescriptor = (config) => {
|
|
3940
|
+
if (config.frontend !== "astro" || config.deploy !== "vercel")
|
|
3941
|
+
return null;
|
|
3942
|
+
if (!config.modules.some((id) => ASTRO_SSR_MODULES.includes(id)))
|
|
3943
|
+
return null;
|
|
3944
|
+
return {
|
|
3945
|
+
id: "deploy-vercel",
|
|
3946
|
+
kind: "frontend",
|
|
3947
|
+
frontendDeps: {
|
|
3948
|
+
astro: [{ name: "@astrojs/vercel", version: ASTROJS_VERCEL_VERSION, target: "app" }]
|
|
3949
|
+
},
|
|
3950
|
+
patches: [
|
|
3951
|
+
{
|
|
3952
|
+
path: "apps/web/astro.config.mjs",
|
|
3953
|
+
apply: (content) => {
|
|
3954
|
+
if (content.includes(VERCEL_IMPORT))
|
|
3955
|
+
return content;
|
|
3956
|
+
if (!content.includes(NODE_IMPORT))
|
|
3957
|
+
return content;
|
|
3958
|
+
return content.replace(NODE_IMPORT, VERCEL_IMPORT).replace(NODE_ADAPTER, VERCEL_ADAPTER);
|
|
3959
|
+
}
|
|
3960
|
+
},
|
|
3961
|
+
{
|
|
3962
|
+
path: "apps/web/package.json",
|
|
3963
|
+
apply: (content) => content.includes('"@astrojs/node"') ? content.replace(/^\s*"@astrojs\/node":\s*"[^"]*",?\n/m, "") : content
|
|
3964
|
+
}
|
|
3965
|
+
]
|
|
3966
|
+
};
|
|
3967
|
+
};
|
|
3968
|
+
var init_deploy_target = __esm(() => {
|
|
3969
|
+
ASTRO_SSR_MODULES = ["forms"];
|
|
3970
|
+
});
|
|
3971
|
+
|
|
1927
3972
|
// ../../packages/registry/src/frontends/experimental.ts
|
|
1928
3973
|
var BABEL_REACT_COMPILER_VERSION = "^1.0.0", experimentalDescriptor = (config) => {
|
|
1929
3974
|
const { cacheComponents, reactCompiler } = config.experimental;
|
|
@@ -1960,7 +4005,7 @@ var BABEL_REACT_COMPILER_VERSION = "^1.0.0", experimentalDescriptor = (config) =
|
|
|
1960
4005
|
var init_experimental = () => {};
|
|
1961
4006
|
|
|
1962
4007
|
// ../../packages/registry/src/frontends/next.ts
|
|
1963
|
-
var
|
|
4008
|
+
var j8 = (value) => `${JSON.stringify(value, null, 2)}
|
|
1964
4009
|
`, NEXT_CONFIG = `import { createRequire } from "node:module";
|
|
1965
4010
|
import type { NextConfig } from "next";
|
|
1966
4011
|
|
|
@@ -2019,7 +4064,7 @@ var init_next = __esm(() => {
|
|
|
2019
4064
|
templates: [
|
|
2020
4065
|
{
|
|
2021
4066
|
path: "apps/web/package.json",
|
|
2022
|
-
content:
|
|
4067
|
+
content: j8({
|
|
2023
4068
|
name: "web",
|
|
2024
4069
|
version: "0.0.0",
|
|
2025
4070
|
private: true,
|
|
@@ -2050,7 +4095,7 @@ var init_next = __esm(() => {
|
|
|
2050
4095
|
{ path: "apps/web/next.config.ts", content: NEXT_CONFIG },
|
|
2051
4096
|
{
|
|
2052
4097
|
path: "apps/web/tsconfig.json",
|
|
2053
|
-
content:
|
|
4098
|
+
content: j8({
|
|
2054
4099
|
extends: "@workspace/typescript-config/nextjs.json",
|
|
2055
4100
|
compilerOptions: { plugins: [{ name: "next" }], paths: { "@/*": ["./*"] } },
|
|
2056
4101
|
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
@@ -2060,14 +4105,6 @@ var init_next = __esm(() => {
|
|
|
2060
4105
|
{ path: "apps/web/app/layout.tsx", content: LAYOUT },
|
|
2061
4106
|
{ path: "apps/web/app/page.tsx", content: PAGE },
|
|
2062
4107
|
{ path: "apps/web/app/globals.css", content: GLOBALS }
|
|
2063
|
-
],
|
|
2064
|
-
owns: [
|
|
2065
|
-
"apps/web/package.json",
|
|
2066
|
-
"apps/web/next.config.ts",
|
|
2067
|
-
"apps/web/tsconfig.json",
|
|
2068
|
-
"apps/web/app/layout.tsx",
|
|
2069
|
-
"apps/web/app/page.tsx",
|
|
2070
|
-
"apps/web/app/globals.css"
|
|
2071
4108
|
]
|
|
2072
4109
|
};
|
|
2073
4110
|
});
|
|
@@ -2236,18 +4273,12 @@ export default function AlgoliaSearch({
|
|
|
2236
4273
|
{ key: "PUBLIC_ALGOLIA_APP_ID", scope: "client" },
|
|
2237
4274
|
{ key: "PUBLIC_ALGOLIA_SEARCH_KEY", scope: "client" },
|
|
2238
4275
|
{ key: "PUBLIC_ALGOLIA_INDEX", scope: "client" }
|
|
2239
|
-
],
|
|
2240
|
-
owns: [
|
|
2241
|
-
"apps/web/app/search/Search.tsx",
|
|
2242
|
-
"apps/web/app/search/page.tsx",
|
|
2243
|
-
"apps/web/src/components/AlgoliaSearch.tsx",
|
|
2244
|
-
"apps/web/src/pages/search.astro"
|
|
2245
4276
|
]
|
|
2246
4277
|
};
|
|
2247
4278
|
});
|
|
2248
4279
|
|
|
2249
4280
|
// ../../packages/registry/src/modules/changesets.ts
|
|
2250
|
-
var
|
|
4281
|
+
var j9 = (v) => `${JSON.stringify(v, null, 2)}
|
|
2251
4282
|
`, CONFIG, README = `# Changesets
|
|
2252
4283
|
|
|
2253
4284
|
This folder is managed by [Changesets](https://github.com/changesets/changesets).
|
|
@@ -2258,7 +4289,7 @@ the \`version-packages\` script consumes them to update versions and changelogs,
|
|
|
2258
4289
|
the \`release\` script builds and publishes.
|
|
2259
4290
|
`, changesets;
|
|
2260
4291
|
var init_changesets = __esm(() => {
|
|
2261
|
-
CONFIG =
|
|
4292
|
+
CONFIG = j9({
|
|
2262
4293
|
$schema: "https://unpkg.com/@changesets/config@3.1.4/schema.json",
|
|
2263
4294
|
changelog: "@changesets/cli/changelog",
|
|
2264
4295
|
commit: false,
|
|
@@ -2286,8 +4317,7 @@ var init_changesets = __esm(() => {
|
|
|
2286
4317
|
changeset: "changeset",
|
|
2287
4318
|
"version-packages": "changeset version",
|
|
2288
4319
|
release: "turbo run build && changeset publish"
|
|
2289
|
-
}
|
|
2290
|
-
owns: [".changeset/config.json", ".changeset/README.md"]
|
|
4320
|
+
}
|
|
2291
4321
|
};
|
|
2292
4322
|
});
|
|
2293
4323
|
|
|
@@ -2336,8 +4366,7 @@ var init_e2e = __esm(() => {
|
|
|
2336
4366
|
]
|
|
2337
4367
|
},
|
|
2338
4368
|
scripts: { e2e: "playwright test" },
|
|
2339
|
-
deps: [{ name: "@playwright/test", version: "^1.60.0", target: "root", dev: true }]
|
|
2340
|
-
owns: ["tests/e2e/home.spec.ts", "playwright.config.ts"]
|
|
4369
|
+
deps: [{ name: "@playwright/test", version: "^1.60.0", target: "root", dev: true }]
|
|
2341
4370
|
};
|
|
2342
4371
|
});
|
|
2343
4372
|
|
|
@@ -2577,13 +4606,6 @@ export const POST: APIRoute = async ({ request }) => {
|
|
|
2577
4606
|
path: "apps/web/astro.config.mjs",
|
|
2578
4607
|
apply: (content) => ensureAstroAdapter(content, 'import node from "@astrojs/node";', 'node({ mode: "standalone" })')
|
|
2579
4608
|
}
|
|
2580
|
-
],
|
|
2581
|
-
owns: [
|
|
2582
|
-
"apps/web/app/contact/page.tsx",
|
|
2583
|
-
"apps/web/app/api/contact/route.ts",
|
|
2584
|
-
"apps/web/src/components/ContactForm.tsx",
|
|
2585
|
-
"apps/web/src/pages/contact.astro",
|
|
2586
|
-
"apps/web/src/pages/api/contact.ts"
|
|
2587
4609
|
]
|
|
2588
4610
|
};
|
|
2589
4611
|
});
|
|
@@ -2625,8 +4647,7 @@ var init_git_hooks = __esm(() => {
|
|
|
2625
4647
|
target: "root",
|
|
2626
4648
|
dev: true
|
|
2627
4649
|
}
|
|
2628
|
-
]
|
|
2629
|
-
owns: ["lefthook.yml", "commitlint.config.mjs"]
|
|
4650
|
+
]
|
|
2630
4651
|
};
|
|
2631
4652
|
});
|
|
2632
4653
|
|
|
@@ -2680,8 +4701,7 @@ var init_github_ci = __esm(() => {
|
|
|
2680
4701
|
label: "GitHub Actions CI",
|
|
2681
4702
|
hint: "Workflow that installs, type-checks, and builds on every push and PR."
|
|
2682
4703
|
},
|
|
2683
|
-
templates: [{ path: ".github/workflows/ci.yml", content: CI_WORKFLOW }]
|
|
2684
|
-
owns: [".github/workflows/ci.yml"]
|
|
4704
|
+
templates: [{ path: ".github/workflows/ci.yml", content: CI_WORKFLOW }]
|
|
2685
4705
|
};
|
|
2686
4706
|
});
|
|
2687
4707
|
|
|
@@ -2844,18 +4864,7 @@ ${LLMS_TXT_REST}\`;
|
|
|
2844
4864
|
{ path: "apps/web/src/content/pages.ts", content: ASTRO_CONTENT },
|
|
2845
4865
|
{ path: "apps/web/src/pages/[slug].md.ts", content: ASTRO_MD_ENDPOINT }
|
|
2846
4866
|
]
|
|
2847
|
-
}
|
|
2848
|
-
owns: [
|
|
2849
|
-
"skills.md",
|
|
2850
|
-
"apps/web/app/llms.txt/route.ts",
|
|
2851
|
-
"apps/web/app/docs/content.ts",
|
|
2852
|
-
"apps/web/app/docs/page.tsx",
|
|
2853
|
-
"apps/web/app/docs.md/route.ts",
|
|
2854
|
-
"apps/web/app/lib/markdown-variant.ts",
|
|
2855
|
-
"apps/web/src/pages/llms.txt.ts",
|
|
2856
|
-
"apps/web/src/content/pages.ts",
|
|
2857
|
-
"apps/web/src/pages/[slug].md.ts"
|
|
2858
|
-
]
|
|
4867
|
+
}
|
|
2859
4868
|
};
|
|
2860
4869
|
});
|
|
2861
4870
|
|
|
@@ -2916,7 +4925,6 @@ var init_posthog = __esm(() => {
|
|
|
2916
4925
|
{ key: "PUBLIC_POSTHOG_KEY", scope: "client" },
|
|
2917
4926
|
{ key: "PUBLIC_POSTHOG_HOST", scope: "client" }
|
|
2918
4927
|
],
|
|
2919
|
-
owns: ["apps/web/instrumentation-client.ts", "apps/web/src/components/PostHog.astro"],
|
|
2920
4928
|
patches: [
|
|
2921
4929
|
{ path: "apps/web/src/pages/index.astro", apply: patchIndexAstro },
|
|
2922
4930
|
{ path: "apps/web/src/layouts/Layout.astro", apply: patchIndexAstro }
|
|
@@ -3444,15 +5452,6 @@ var init_sections = __esm(() => {
|
|
|
3444
5452
|
{ path: "packages/ui/src/components/sections/faq.tsx", content: FAQ_TSX },
|
|
3445
5453
|
{ path: "packages/ui/src/components/sections/cta.tsx", content: CTA_TSX }
|
|
3446
5454
|
],
|
|
3447
|
-
owns: [
|
|
3448
|
-
"packages/ui/src/components/sections/hero.tsx",
|
|
3449
|
-
"packages/ui/src/components/sections/features.tsx",
|
|
3450
|
-
"packages/ui/src/components/sections/logo-cloud.tsx",
|
|
3451
|
-
"packages/ui/src/components/sections/testimonials.tsx",
|
|
3452
|
-
"packages/ui/src/components/sections/pricing.tsx",
|
|
3453
|
-
"packages/ui/src/components/sections/faq.tsx",
|
|
3454
|
-
"packages/ui/src/components/sections/cta.tsx"
|
|
3455
|
-
],
|
|
3456
5455
|
patches: [
|
|
3457
5456
|
{ path: "apps/web/app/page.tsx", apply: replaceWith(COMPOSED_NEXT_HOME_PAGE) },
|
|
3458
5457
|
{ path: "apps/web/src/pages/index.astro", apply: replaceWith(COMPOSED_ASTRO_HOME_PAGE) }
|
|
@@ -3720,14 +5719,6 @@ var init_seo = __esm(() => {
|
|
|
3720
5719
|
{ key: "PUBLIC_SITE_NAME", scope: "client" },
|
|
3721
5720
|
{ key: "PUBLIC_SITE_DESCRIPTION", scope: "client" }
|
|
3722
5721
|
],
|
|
3723
|
-
owns: [
|
|
3724
|
-
"apps/web/app/lib/metadata.ts",
|
|
3725
|
-
"apps/web/app/sitemap.ts",
|
|
3726
|
-
"apps/web/app/robots.ts",
|
|
3727
|
-
"apps/web/app/opengraph-image.tsx",
|
|
3728
|
-
"apps/web/src/components/SEO.astro",
|
|
3729
|
-
"apps/web/src/pages/og.png.ts"
|
|
3730
|
-
],
|
|
3731
5722
|
patches: [
|
|
3732
5723
|
{ path: "apps/web/astro.config.mjs", apply: patchAstroConfig },
|
|
3733
5724
|
{ path: "apps/web/app/layout.tsx", apply: patchLayout },
|
|
@@ -4048,7 +6039,7 @@ export function SiteHeader({
|
|
|
4048
6039
|
))}
|
|
4049
6040
|
</nav>
|
|
4050
6041
|
<div className="ml-auto">
|
|
4051
|
-
<Button size="sm" render={<a href={action.href} />}>
|
|
6042
|
+
<Button size="sm" nativeButton={false} render={<a href={action.href} />}>
|
|
4052
6043
|
{action.label}
|
|
4053
6044
|
</Button>
|
|
4054
6045
|
</div>
|
|
@@ -4111,7 +6102,9 @@ export default function NotFound() {
|
|
|
4111
6102
|
<p className="text-muted-foreground">
|
|
4112
6103
|
The link may be out of date, or the page may have moved.
|
|
4113
6104
|
</p>
|
|
4114
|
-
<Button render={<a href="/" />}>
|
|
6105
|
+
<Button nativeButton={false} render={<a href="/" />}>
|
|
6106
|
+
Back home
|
|
6107
|
+
</Button>
|
|
4115
6108
|
</main>
|
|
4116
6109
|
);
|
|
4117
6110
|
}
|
|
@@ -4562,8 +6555,8 @@ ${DARK_TOKENS}
|
|
|
4562
6555
|
});
|
|
4563
6556
|
|
|
4564
6557
|
// ../../packages/registry/src/modules/ui/index.ts
|
|
4565
|
-
var
|
|
4566
|
-
`, UI_PACKAGE_JSON, componentsJson = (rsc) =>
|
|
6558
|
+
var j10 = (value) => `${JSON.stringify(value, null, 2)}
|
|
6559
|
+
`, UI_PACKAGE_JSON, componentsJson = (rsc) => j10({
|
|
4567
6560
|
$schema: "https://ui.shadcn.com/schema.json",
|
|
4568
6561
|
style: "base-nova",
|
|
4569
6562
|
rsc,
|
|
@@ -4586,20 +6579,22 @@ var j7 = (value) => `${JSON.stringify(value, null, 2)}
|
|
|
4586
6579
|
menuColor: "default",
|
|
4587
6580
|
menuAccent: "subtle"
|
|
4588
6581
|
}), patchNextLayout2 = (content) => {
|
|
4589
|
-
if (content.includes("
|
|
6582
|
+
if (content.includes("<SiteHeader"))
|
|
4590
6583
|
return content;
|
|
4591
6584
|
const withImports = [
|
|
4592
6585
|
'import { SiteFooter } from "@workspace/ui/components/site-footer";',
|
|
4593
6586
|
'import { SiteHeader } from "@workspace/ui/components/site-header";',
|
|
4594
|
-
'import rootPkg from "../../../package.json";'
|
|
4595
|
-
|
|
6587
|
+
'import rootPkg from "../../../package.json";'
|
|
6588
|
+
].reduce(ensureImport, content);
|
|
6589
|
+
const expanded = withImports.replace(/^([ \t]*)<body([^>]*)>\{children\}<\/body>/m, (_m, indent, attrs) => `${indent}<body${attrs}>
|
|
6590
|
+
${indent} {children}
|
|
6591
|
+
${indent}</body>`);
|
|
6592
|
+
return expanded.replace(/<body([^>]*)>/, '<body$1 className="flex min-h-svh flex-col">').replace(/^([ \t]*)\{children\}$/m, (_m, indent) => [
|
|
6593
|
+
`${indent}<SiteHeader siteName={rootPkg.name} />`,
|
|
6594
|
+
`${indent}<div className="flex-1">{children}</div>`,
|
|
6595
|
+
`${indent}<SiteFooter siteName={rootPkg.name} />`
|
|
4596
6596
|
].join(`
|
|
4597
|
-
`);
|
|
4598
|
-
return withImports.replace(/<body([^>]*)>\{children\}<\/body>/, `<body$1 className="flex min-h-svh flex-col">
|
|
4599
|
-
<SiteHeader siteName={rootPkg.name} />
|
|
4600
|
-
<div className="flex-1">{children}</div>
|
|
4601
|
-
<SiteFooter siteName={rootPkg.name} />
|
|
4602
|
-
</body>`);
|
|
6597
|
+
`));
|
|
4603
6598
|
}, NEXT_POSTCSS_CONFIG = `export default {
|
|
4604
6599
|
plugins: {
|
|
4605
6600
|
"@tailwindcss/postcss": {},
|
|
@@ -4610,7 +6605,7 @@ var j7 = (value) => `${JSON.stringify(value, null, 2)}
|
|
|
4610
6605
|
var init_ui = __esm(() => {
|
|
4611
6606
|
init_pages();
|
|
4612
6607
|
init_theme();
|
|
4613
|
-
UI_PACKAGE_JSON =
|
|
6608
|
+
UI_PACKAGE_JSON = j10({
|
|
4614
6609
|
name: "@workspace/ui",
|
|
4615
6610
|
version: "0.0.0",
|
|
4616
6611
|
private: true,
|
|
@@ -4674,25 +6669,6 @@ var init_ui = __esm(() => {
|
|
|
4674
6669
|
]
|
|
4675
6670
|
},
|
|
4676
6671
|
deps: [{ name: "@workspace/ui", version: "workspace:*", target: "app" }],
|
|
4677
|
-
owns: [
|
|
4678
|
-
"packages/ui/package.json",
|
|
4679
|
-
"packages/ui/components.json",
|
|
4680
|
-
"packages/ui/src/styles/globals.css",
|
|
4681
|
-
"packages/ui/src/lib/utils.ts",
|
|
4682
|
-
"packages/ui/src/components/button.tsx",
|
|
4683
|
-
"packages/ui/src/components/card.tsx",
|
|
4684
|
-
"packages/ui/src/components/input.tsx",
|
|
4685
|
-
"packages/ui/src/components/textarea.tsx",
|
|
4686
|
-
"packages/ui/src/components/label.tsx",
|
|
4687
|
-
"packages/ui/src/components/badge.tsx",
|
|
4688
|
-
"packages/ui/src/components/site-header.tsx",
|
|
4689
|
-
"packages/ui/src/components/site-footer.tsx",
|
|
4690
|
-
"apps/web/postcss.config.mjs",
|
|
4691
|
-
"apps/web/components.json",
|
|
4692
|
-
"apps/web/app/not-found.tsx",
|
|
4693
|
-
"apps/web/src/layouts/Layout.astro",
|
|
4694
|
-
"apps/web/src/pages/404.astro"
|
|
4695
|
-
],
|
|
4696
6672
|
patches: [
|
|
4697
6673
|
{
|
|
4698
6674
|
path: "apps/web/app/globals.css",
|
|
@@ -4710,7 +6686,7 @@ var init_ui = __esm(() => {
|
|
|
4710
6686
|
if (!plugin || pkg.devDependencies?.[plugin])
|
|
4711
6687
|
return content;
|
|
4712
6688
|
const devDependencies = Object.fromEntries(Object.entries({ ...pkg.devDependencies, [plugin]: "^4.3.0" }).sort(([a], [b]) => a.localeCompare(b)));
|
|
4713
|
-
return
|
|
6689
|
+
return j10({ ...pkg, devDependencies });
|
|
4714
6690
|
}
|
|
4715
6691
|
},
|
|
4716
6692
|
{ path: "apps/web/app/layout.tsx", apply: patchNextLayout2 },
|
|
@@ -4751,8 +6727,7 @@ var init_vitest = __esm(() => {
|
|
|
4751
6727
|
{ path: "tests/example.test.ts", content: EXAMPLE_TEST }
|
|
4752
6728
|
],
|
|
4753
6729
|
scripts: { test: "vitest run", "test:watch": "vitest" },
|
|
4754
|
-
deps: [{ name: "vitest", version: "^4.1.0", target: "root", dev: true }]
|
|
4755
|
-
owns: ["vitest.config.ts", "tests/example.test.ts"]
|
|
6730
|
+
deps: [{ name: "vitest", version: "^4.1.0", target: "root", dev: true }]
|
|
4756
6731
|
};
|
|
4757
6732
|
});
|
|
4758
6733
|
|
|
@@ -4760,12 +6735,14 @@ var init_vitest = __esm(() => {
|
|
|
4760
6735
|
function buildRegistry(config2) {
|
|
4761
6736
|
const content = CONTENT_DESCRIPTORS[config2.content];
|
|
4762
6737
|
const experimental = experimentalDescriptor(config2);
|
|
6738
|
+
const deploy = deployDescriptor(config2);
|
|
4763
6739
|
return [
|
|
4764
6740
|
...baselineDescriptors,
|
|
4765
6741
|
FRONTEND_DESCRIPTORS[config2.frontend],
|
|
4766
6742
|
...experimental ? [experimental] : [],
|
|
4767
6743
|
...content ? [content] : [],
|
|
4768
|
-
...config2.modules.map((id) => MODULE_DESCRIPTORS[id])
|
|
6744
|
+
...config2.modules.map((id) => MODULE_DESCRIPTORS[id]),
|
|
6745
|
+
...deploy ? [deploy] : []
|
|
4769
6746
|
];
|
|
4770
6747
|
}
|
|
4771
6748
|
var baselineDescriptors, FRONTEND_DESCRIPTORS, MODULE_DESCRIPTORS, CONTENT_DESCRIPTORS;
|
|
@@ -4777,6 +6754,7 @@ var init_src3 = __esm(() => {
|
|
|
4777
6754
|
init_markdown();
|
|
4778
6755
|
init_sanity();
|
|
4779
6756
|
init_astro();
|
|
6757
|
+
init_deploy_target();
|
|
4780
6758
|
init_experimental();
|
|
4781
6759
|
init_next();
|
|
4782
6760
|
init_algolia();
|
|
@@ -5003,6 +6981,7 @@ var CreateInput = z2.object({
|
|
|
5003
6981
|
frontend: z2.enum(FRONTENDS).optional(),
|
|
5004
6982
|
content: z2.enum(CONTENT_SOURCES).optional(),
|
|
5005
6983
|
packageManager: z2.enum(PACKAGE_MANAGERS).optional(),
|
|
6984
|
+
deploy: z2.enum(DEPLOY_TARGETS).optional(),
|
|
5006
6985
|
modules: z2.array(z2.enum(MODULES)).optional(),
|
|
5007
6986
|
preset: z2.enum(PRESETS).optional(),
|
|
5008
6987
|
cacheComponents: z2.boolean().optional(),
|
|
@@ -5020,6 +6999,7 @@ var router = t.router({
|
|
|
5020
6999
|
frontend: input.frontend,
|
|
5021
7000
|
content: input.content ?? base.content,
|
|
5022
7001
|
packageManager: input.packageManager ?? base.packageManager,
|
|
7002
|
+
deploy: input.deploy ?? base.deploy,
|
|
5023
7003
|
modules: input.modules ?? base.modules,
|
|
5024
7004
|
cacheComponents: input.cacheComponents ?? base.experimental?.cacheComponents,
|
|
5025
7005
|
reactCompiler: input.reactCompiler ?? base.experimental?.reactCompiler
|
|
@@ -5030,6 +7010,7 @@ var router = t.router({
|
|
|
5030
7010
|
frontend: flags.frontend ?? "next",
|
|
5031
7011
|
content: flags.content,
|
|
5032
7012
|
packageManager: flags.packageManager,
|
|
7013
|
+
deploy: flags.deploy,
|
|
5033
7014
|
modules: flags.modules,
|
|
5034
7015
|
experimental: {
|
|
5035
7016
|
cacheComponents: flags.cacheComponents,
|