create-turbo-wizard 0.1.0 → 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 +2761 -779
- 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
|
});
|
|
@@ -67,6 +70,72 @@ var init_src = __esm(() => {
|
|
|
67
70
|
};
|
|
68
71
|
});
|
|
69
72
|
|
|
73
|
+
// package.json
|
|
74
|
+
var package_default;
|
|
75
|
+
var init_package = __esm(() => {
|
|
76
|
+
package_default = {
|
|
77
|
+
name: "create-turbo-wizard",
|
|
78
|
+
version: "0.1.2",
|
|
79
|
+
description: "Scaffold a Turborepo monorepo: Astro or Next.js, Sanity or Markdown, opt-in modules.",
|
|
80
|
+
keywords: [
|
|
81
|
+
"turborepo",
|
|
82
|
+
"monorepo",
|
|
83
|
+
"scaffold",
|
|
84
|
+
"astro",
|
|
85
|
+
"nextjs",
|
|
86
|
+
"sanity",
|
|
87
|
+
"shadcn",
|
|
88
|
+
"tailwind",
|
|
89
|
+
"marketing-site",
|
|
90
|
+
"create"
|
|
91
|
+
],
|
|
92
|
+
homepage: "https://turbo-wizard.vercel.app",
|
|
93
|
+
repository: {
|
|
94
|
+
type: "git",
|
|
95
|
+
url: "git+https://github.com/robotostudio/turbo-wizard.git",
|
|
96
|
+
directory: "apps/cli"
|
|
97
|
+
},
|
|
98
|
+
bugs: {
|
|
99
|
+
url: "https://github.com/robotostudio/turbo-wizard/issues"
|
|
100
|
+
},
|
|
101
|
+
author: "Roboto Studio",
|
|
102
|
+
license: "MIT",
|
|
103
|
+
type: "module",
|
|
104
|
+
bin: {
|
|
105
|
+
"create-turbo-wizard": "dist/cli.js"
|
|
106
|
+
},
|
|
107
|
+
files: [
|
|
108
|
+
"dist",
|
|
109
|
+
"README.md"
|
|
110
|
+
],
|
|
111
|
+
scripts: {
|
|
112
|
+
"check-types": "tsc --noEmit",
|
|
113
|
+
test: "bun test",
|
|
114
|
+
start: "bun run src/cli.ts",
|
|
115
|
+
build: "bun run scripts/bundle.ts",
|
|
116
|
+
prepublishOnly: "bun run scripts/bundle.ts"
|
|
117
|
+
},
|
|
118
|
+
dependencies: {
|
|
119
|
+
"@clack/prompts": "^1.5.0",
|
|
120
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
121
|
+
"@trpc/server": "^11.0.0",
|
|
122
|
+
"trpc-cli": "^0.14.1",
|
|
123
|
+
zod: "^4.4.3"
|
|
124
|
+
},
|
|
125
|
+
devDependencies: {
|
|
126
|
+
"@turbo-wizard/engine": "workspace:*",
|
|
127
|
+
"@turbo-wizard/registry": "workspace:*",
|
|
128
|
+
"@turbo-wizard/types": "workspace:*"
|
|
129
|
+
},
|
|
130
|
+
engines: {
|
|
131
|
+
node: ">=22"
|
|
132
|
+
},
|
|
133
|
+
publishConfig: {
|
|
134
|
+
access: "public"
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
|
|
70
139
|
// ../../packages/engine/src/result.ts
|
|
71
140
|
var ok = (value) => ({ ok: true, value }), err = (error) => ({ ok: false, error }), CompatibilityError, CollisionError;
|
|
72
141
|
var init_result = __esm(() => {
|
|
@@ -223,6 +292,7 @@ ${WORKSPACE_GLOBS.map((g) => ` - "${g}"`).join(`
|
|
|
223
292
|
"dist/",
|
|
224
293
|
".next/",
|
|
225
294
|
".astro/",
|
|
295
|
+
".sanity/",
|
|
226
296
|
".turbo/",
|
|
227
297
|
"*.tsbuildinfo",
|
|
228
298
|
"",
|
|
@@ -314,8 +384,7 @@ var init_biome = __esm(() => {
|
|
|
314
384
|
kind: "baseline",
|
|
315
385
|
templates: [{ path: "biome.jsonc", content: BIOME_CONFIG }],
|
|
316
386
|
scripts: { check: "biome check .", format: "biome format --write ." },
|
|
317
|
-
deps: [{ name: "@biomejs/biome", versionKey: "@biomejs/biome", target: "root", dev: true }]
|
|
318
|
-
owns: ["biome.jsonc"]
|
|
387
|
+
deps: [{ name: "@biomejs/biome", versionKey: "@biomejs/biome", target: "root", dev: true }]
|
|
319
388
|
};
|
|
320
389
|
});
|
|
321
390
|
|
|
@@ -360,8 +429,7 @@ var init_env = __esm(() => {
|
|
|
360
429
|
content: j({ extends: "@workspace/typescript-config/base.json", include: ["src"] })
|
|
361
430
|
},
|
|
362
431
|
{ path: "packages/env/src/index.ts", content: ENV_SRC }
|
|
363
|
-
]
|
|
364
|
-
owns: ["packages/env/package.json", "packages/env/tsconfig.json", "packages/env/src/index.ts"]
|
|
432
|
+
]
|
|
365
433
|
};
|
|
366
434
|
});
|
|
367
435
|
|
|
@@ -409,11 +477,6 @@ var init_logger = __esm(() => {
|
|
|
409
477
|
content: j2({ extends: "@workspace/typescript-config/base.json", include: ["src"] })
|
|
410
478
|
},
|
|
411
479
|
{ path: "packages/logger/src/index.ts", content: LOGGER_SRC }
|
|
412
|
-
],
|
|
413
|
-
owns: [
|
|
414
|
-
"packages/logger/package.json",
|
|
415
|
-
"packages/logger/tsconfig.json",
|
|
416
|
-
"packages/logger/src/index.ts"
|
|
417
480
|
]
|
|
418
481
|
};
|
|
419
482
|
});
|
|
@@ -477,12 +540,6 @@ var init_typescript_config = __esm(() => {
|
|
|
477
540
|
compilerOptions: { lib: ["DOM", "DOM.Iterable", "ES2023"], jsx: "react-jsx" }
|
|
478
541
|
})
|
|
479
542
|
}
|
|
480
|
-
],
|
|
481
|
-
owns: [
|
|
482
|
-
"packages/typescript-config/package.json",
|
|
483
|
-
"packages/typescript-config/base.json",
|
|
484
|
-
"packages/typescript-config/nextjs.json",
|
|
485
|
-
"packages/typescript-config/react-library.json"
|
|
486
543
|
]
|
|
487
544
|
};
|
|
488
545
|
});
|
|
@@ -550,7 +607,8 @@ function replaceNextMetadata(content, expr) {
|
|
|
550
607
|
}
|
|
551
608
|
|
|
552
609
|
// ../../packages/registry/src/content/markdown.ts
|
|
553
|
-
var
|
|
610
|
+
var j4 = (value) => `${JSON.stringify(value, null, 2)}
|
|
611
|
+
`, CONTENT_PACKAGE_JSON, NEXT_MDX_COMPONENTS = `import type { MDXComponents } from "mdx/types";
|
|
554
612
|
|
|
555
613
|
const components: MDXComponents = {
|
|
556
614
|
// Override built-ins here, e.g. h1: ({ children }) => <h1 className="...">{children}</h1>,
|
|
@@ -576,15 +634,26 @@ This post is written in **MDX** — Markdown with JSX.
|
|
|
576
634
|
- Mix Markdown prose with interactive components
|
|
577
635
|
- Full JSX expression support
|
|
578
636
|
- Server Components by default
|
|
637
|
+
`, NEXT_CONTENT_SCHEMA = `export interface PostMetadata {
|
|
638
|
+
title: string;
|
|
639
|
+
description?: string;
|
|
640
|
+
date?: string;
|
|
641
|
+
}
|
|
579
642
|
`, NEXT_BLOG_POSTS_TS = `import { readdir } from "node:fs/promises";
|
|
580
643
|
import path from "node:path";
|
|
644
|
+
import type { PostMetadata } from "@workspace/content/schema";
|
|
581
645
|
import type { ComponentType } from "react";
|
|
582
646
|
|
|
583
|
-
|
|
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");
|
|
584
653
|
|
|
585
654
|
export interface PostModule {
|
|
586
655
|
default: ComponentType;
|
|
587
|
-
metadata:
|
|
656
|
+
metadata: PostMetadata;
|
|
588
657
|
}
|
|
589
658
|
|
|
590
659
|
export async function getPostSlugs(): Promise<string[]> {
|
|
@@ -599,7 +668,7 @@ async function getPosts() {
|
|
|
599
668
|
const slugs = await getPostSlugs();
|
|
600
669
|
return Promise.all(
|
|
601
670
|
slugs.map(async (slug) => {
|
|
602
|
-
const { metadata } = (await import(
|
|
671
|
+
const { metadata } = (await import(\`@workspace/content/blog/\${slug}.mdx\`)) as PostModule;
|
|
603
672
|
return { slug, ...metadata };
|
|
604
673
|
}),
|
|
605
674
|
);
|
|
@@ -653,7 +722,7 @@ export default async function BlogPostPage({
|
|
|
653
722
|
params: Promise<{ slug: string }>;
|
|
654
723
|
}) {
|
|
655
724
|
const { slug } = await params;
|
|
656
|
-
const post = (await import(
|
|
725
|
+
const post = (await import(\`@workspace/content/blog/\${slug}.mdx\`).catch(() => null)) as PostModule | null;
|
|
657
726
|
if (!post) notFound();
|
|
658
727
|
|
|
659
728
|
const { default: Post, metadata } = post;
|
|
@@ -667,18 +736,23 @@ export default async function BlogPostPage({
|
|
|
667
736
|
</main>
|
|
668
737
|
);
|
|
669
738
|
}
|
|
670
|
-
`,
|
|
671
|
-
|
|
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";
|
|
672
750
|
|
|
673
751
|
const blog = defineCollection({
|
|
674
|
-
// Pattern includes both .md and .mdx so the collection is mixed-format.
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
description: z.string(),
|
|
679
|
-
pubDate: z.coerce.date(),
|
|
680
|
-
updatedDate: z.coerce.date().optional(),
|
|
681
|
-
}),
|
|
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,
|
|
682
756
|
});
|
|
683
757
|
|
|
684
758
|
export const collections = { blog };
|
|
@@ -756,6 +830,16 @@ const { Content } = await render(post);
|
|
|
756
830
|
</html>
|
|
757
831
|
`, markdown;
|
|
758
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
|
+
});
|
|
759
843
|
markdown = {
|
|
760
844
|
id: "markdown",
|
|
761
845
|
kind: "content",
|
|
@@ -763,21 +847,25 @@ var init_markdown = __esm(() => {
|
|
|
763
847
|
label: "MDX content",
|
|
764
848
|
hint: "Markdown + JSX wired into the frontend (official recipe)"
|
|
765
849
|
},
|
|
850
|
+
templates: [{ path: "packages/content/package.json", content: CONTENT_PACKAGE_JSON }],
|
|
766
851
|
fragments: {
|
|
767
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 },
|
|
768
855
|
{ path: "apps/web/mdx-components.tsx", content: NEXT_MDX_COMPONENTS },
|
|
769
|
-
{ path: "apps/web/content/blog/hello.mdx", content: NEXT_BLOG_POST_MDX },
|
|
770
856
|
{ path: "apps/web/app/blog/posts.ts", content: NEXT_BLOG_POSTS_TS },
|
|
771
857
|
{ path: "apps/web/app/blog/page.tsx", content: NEXT_BLOG_INDEX_TSX },
|
|
772
858
|
{ path: "apps/web/app/blog/[slug]/page.tsx", content: NEXT_BLOG_SLUG_TSX }
|
|
773
859
|
],
|
|
774
860
|
astro: [
|
|
861
|
+
{ path: "packages/content/src/schema.ts", content: ASTRO_CONTENT_SCHEMA },
|
|
862
|
+
{ path: "packages/content/blog/hello.mdx", content: ASTRO_BLOG_POST },
|
|
775
863
|
{ path: "apps/web/src/content.config.ts", content: ASTRO_CONTENT_CONFIG },
|
|
776
|
-
{ path: "apps/web/src/content/blog/hello.mdx", content: ASTRO_BLOG_POST },
|
|
777
864
|
{ path: "apps/web/src/pages/blog/index.astro", content: ASTRO_BLOG_INDEX },
|
|
778
865
|
{ path: "apps/web/src/pages/blog/[...slug].astro", content: ASTRO_BLOG_SLUG_PAGE }
|
|
779
866
|
]
|
|
780
867
|
},
|
|
868
|
+
deps: [{ name: "@workspace/content", version: "workspace:*", target: "app" }],
|
|
781
869
|
frontendDeps: {
|
|
782
870
|
next: [
|
|
783
871
|
{ name: "@next/mdx", version: "^16.2.0", target: "app", dev: true },
|
|
@@ -787,17 +875,6 @@ var init_markdown = __esm(() => {
|
|
|
787
875
|
],
|
|
788
876
|
astro: [{ name: "@astrojs/mdx", version: "^4.3.14", target: "app" }]
|
|
789
877
|
},
|
|
790
|
-
owns: [
|
|
791
|
-
"apps/web/mdx-components.tsx",
|
|
792
|
-
"apps/web/content/blog/hello.mdx",
|
|
793
|
-
"apps/web/app/blog/posts.ts",
|
|
794
|
-
"apps/web/app/blog/page.tsx",
|
|
795
|
-
"apps/web/app/blog/[slug]/page.tsx",
|
|
796
|
-
"apps/web/src/content.config.ts",
|
|
797
|
-
"apps/web/src/content/blog/hello.mdx",
|
|
798
|
-
"apps/web/src/pages/blog/index.astro",
|
|
799
|
-
"apps/web/src/pages/blog/[...slug].astro"
|
|
800
|
-
],
|
|
801
878
|
patches: [
|
|
802
879
|
{
|
|
803
880
|
path: "apps/web/next.config.ts",
|
|
@@ -813,204 +890,374 @@ var init_markdown = __esm(() => {
|
|
|
813
890
|
};
|
|
814
891
|
});
|
|
815
892
|
|
|
816
|
-
// ../../packages/registry/src/content/sanity.ts
|
|
817
|
-
var
|
|
818
|
-
|
|
819
|
-
// Page-builder blocks are objects, not documents: they only exist inside a page.
|
|
820
|
-
|
|
821
|
-
export const hero = defineType({
|
|
822
|
-
name: "hero",
|
|
823
|
-
title: "Hero",
|
|
824
|
-
type: "object",
|
|
825
|
-
fields: [
|
|
826
|
-
defineField({ name: "heading", type: "string", validation: (rule) => rule.required() }),
|
|
827
|
-
defineField({ name: "subheading", type: "text", rows: 2 }),
|
|
828
|
-
defineField({
|
|
829
|
-
name: "image",
|
|
830
|
-
type: "image",
|
|
831
|
-
options: { hotspot: true },
|
|
832
|
-
fields: [defineField({ name: "alt", type: "string", title: "Alternative text" })],
|
|
833
|
-
}),
|
|
834
|
-
],
|
|
835
|
-
preview: {
|
|
836
|
-
select: { title: "heading", media: "image" },
|
|
837
|
-
prepare: ({ title, media }) => ({ title: title ?? "Hero", subtitle: "Hero", media }),
|
|
838
|
-
},
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
export const richText = defineType({
|
|
842
|
-
name: "richText",
|
|
843
|
-
title: "Rich text",
|
|
844
|
-
type: "object",
|
|
845
|
-
fields: [
|
|
846
|
-
defineField({ name: "heading", type: "string" }),
|
|
847
|
-
defineField({ name: "content", type: "array", of: [defineArrayMember({ type: "block" })] }),
|
|
848
|
-
],
|
|
849
|
-
preview: {
|
|
850
|
-
select: { title: "heading" },
|
|
851
|
-
prepare: ({ title }) => ({ title: title ?? "Rich text", subtitle: "Rich text" }),
|
|
852
|
-
},
|
|
853
|
-
});
|
|
893
|
+
// ../../packages/registry/src/content/sanity/astro.ts
|
|
894
|
+
var ASTRO_ENV = `export const projectId = import.meta.env.PUBLIC_SANITY_PROJECT_ID || "placeholder";
|
|
854
895
|
|
|
855
|
-
export const
|
|
856
|
-
name: "cta",
|
|
857
|
-
title: "Call to action",
|
|
858
|
-
type: "object",
|
|
859
|
-
fields: [
|
|
860
|
-
defineField({ name: "heading", type: "string", validation: (rule) => rule.required() }),
|
|
861
|
-
defineField({ name: "text", type: "text", rows: 2 }),
|
|
862
|
-
defineField({ name: "buttonLabel", type: "string" }),
|
|
863
|
-
defineField({ name: "buttonHref", type: "string" }),
|
|
864
|
-
],
|
|
865
|
-
preview: {
|
|
866
|
-
select: { title: "heading" },
|
|
867
|
-
prepare: ({ title }) => ({ title: title ?? "Call to action", subtitle: "Call to action" }),
|
|
868
|
-
},
|
|
869
|
-
});
|
|
870
|
-
`, SCHEMA_PAGE = `import { defineArrayMember, defineField, defineType } from "sanity";
|
|
896
|
+
export const dataset = import.meta.env.PUBLIC_SANITY_DATASET || "production";
|
|
871
897
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
title: "Page",
|
|
875
|
-
type: "document",
|
|
876
|
-
fields: [
|
|
877
|
-
defineField({ name: "title", type: "string", validation: (rule) => rule.required() }),
|
|
878
|
-
defineField({
|
|
879
|
-
name: "slug",
|
|
880
|
-
type: "slug",
|
|
881
|
-
options: { source: "title" },
|
|
882
|
-
validation: (rule) => rule.required(),
|
|
883
|
-
}),
|
|
884
|
-
defineField({ name: "description", type: "text", rows: 3 }),
|
|
885
|
-
defineField({
|
|
886
|
-
name: "pageBuilder",
|
|
887
|
-
title: "Page builder",
|
|
888
|
-
type: "array",
|
|
889
|
-
of: [
|
|
890
|
-
defineArrayMember({ type: "hero" }),
|
|
891
|
-
defineArrayMember({ type: "richText" }),
|
|
892
|
-
defineArrayMember({ type: "cta" }),
|
|
893
|
-
],
|
|
894
|
-
}),
|
|
895
|
-
],
|
|
896
|
-
// The home page is the \`page\` whose slug is "home" — see sanity/queries.ts.
|
|
897
|
-
preview: { select: { title: "title", subtitle: "slug.current" } },
|
|
898
|
-
});
|
|
899
|
-
`, 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 ?? "";
|
|
900
900
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
defineField({ name: "siteDescription", type: "text", rows: 3 }),
|
|
908
|
-
defineField({
|
|
909
|
-
name: "socialImage",
|
|
910
|
-
title: "Social share image",
|
|
911
|
-
type: "image",
|
|
912
|
-
options: { hotspot: true },
|
|
913
|
-
}),
|
|
914
|
-
],
|
|
915
|
-
preview: { select: { title: "siteTitle", media: "socialImage" } },
|
|
916
|
-
});
|
|
917
|
-
`, SCHEMA_INDEX = `import type { SchemaTypeDefinition } from "sanity";
|
|
918
|
-
import { cta, hero, richText } from "./blocks";
|
|
919
|
-
import { page } from "./page";
|
|
920
|
-
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";
|
|
921
907
|
|
|
922
|
-
|
|
923
|
-
|
|
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";
|
|
924
915
|
|
|
925
916
|
/**
|
|
926
|
-
* \`
|
|
927
|
-
*
|
|
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.
|
|
928
924
|
*
|
|
929
|
-
* The
|
|
930
|
-
*
|
|
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.
|
|
931
932
|
*/
|
|
932
|
-
export
|
|
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";
|
|
933
960
|
|
|
934
|
-
|
|
935
|
-
export const singletonActions = new Set(["publish", "discardChanges", "restore"]);
|
|
961
|
+
const builder = createImageUrlBuilder({ projectId, dataset });
|
|
936
962
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
]);
|
|
948
|
-
`, PRESENTATION = `import { defineDocuments, defineLocations } from "sanity/presentation";
|
|
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";
|
|
949
973
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
]);
|
|
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];
|
|
955
978
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
locations: [{ title: "Home", href: "/" }],
|
|
960
|
-
message: "This document is used on all pages",
|
|
961
|
-
tone: "positive",
|
|
962
|
-
}),
|
|
963
|
-
page: defineLocations({
|
|
964
|
-
select: { title: "title", slug: "slug.current" },
|
|
965
|
-
resolve: (doc) => ({
|
|
966
|
-
locations: [
|
|
967
|
-
{
|
|
968
|
-
title: doc?.title || "Untitled",
|
|
969
|
-
href: doc?.slug === "home" ? "/" : \`/\${doc?.slug}\`,
|
|
970
|
-
},
|
|
971
|
-
],
|
|
972
|
-
}),
|
|
973
|
-
}),
|
|
974
|
-
};
|
|
975
|
-
`, DOCUMENT_GUARD = ` document: {
|
|
976
|
-
newDocumentOptions: (prev, { creationContext }) =>
|
|
977
|
-
creationContext.type === "global"
|
|
978
|
-
? prev.filter((template) => !singletonTypes.has(template.templateId))
|
|
979
|
-
: prev,
|
|
980
|
-
actions: (prev, { schemaType }) =>
|
|
981
|
-
singletonTypes.has(schemaType)
|
|
982
|
-
? prev.filter(({ action }) => action && singletonActions.has(action))
|
|
983
|
-
: prev,
|
|
984
|
-
},`, QUERIES = `import { defineQuery } from "groq";
|
|
979
|
+
interface Props {
|
|
980
|
+
blocks: PageBlock[];
|
|
981
|
+
}
|
|
985
982
|
|
|
986
|
-
const
|
|
987
|
-
_id,
|
|
988
|
-
_type,
|
|
989
|
-
title,
|
|
990
|
-
description,
|
|
991
|
-
"slug": slug.current,
|
|
992
|
-
pageBuilder[]{ ... }
|
|
993
|
-
\`;
|
|
983
|
+
const { blocks } = Astro.props;
|
|
994
984
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
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;";
|
|
998
988
|
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
989
|
+
const buttonProps = (button: ButtonLink) =>
|
|
990
|
+
button.openInNewTab ? { target: "_blank", rel: "noreferrer" } : {};
|
|
991
|
+
---
|
|
1002
992
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
)
|
|
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";
|
|
1006
1139
|
|
|
1007
|
-
export
|
|
1008
|
-
|
|
1009
|
-
);
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
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";
|
|
1014
1261
|
|
|
1015
1262
|
export const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET || "production";
|
|
1016
1263
|
|
|
@@ -1021,8 +1268,12 @@ export const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || "2026-02
|
|
|
1021
1268
|
/** Server-only read token; empty when unset (draft mode and live previews need it). */
|
|
1022
1269
|
export const readToken = process.env.SANITY_API_READ_TOKEN ?? "";
|
|
1023
1270
|
|
|
1024
|
-
/**
|
|
1025
|
-
|
|
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";
|
|
1026
1277
|
|
|
1027
1278
|
/**
|
|
1028
1279
|
* False on a freshly scaffolded project. Guards every fetch so the build never dials
|
|
@@ -1043,17 +1294,33 @@ export const client = createClient({
|
|
|
1043
1294
|
stega: { studioUrl },
|
|
1044
1295
|
});
|
|
1045
1296
|
`, NEXT_LIVE = `import { defineLive } from "next-sanity/live";
|
|
1046
|
-
import { readToken } from "./env";
|
|
1297
|
+
import { hasSanityCredentials, readToken } from "./env";
|
|
1047
1298
|
import { client } from "./client";
|
|
1048
1299
|
|
|
1049
1300
|
// defineLive gives us live content plus draft-mode reads. The token is passed as
|
|
1050
1301
|
// undefined rather than "" when unset — an empty string reads as "provided but invalid".
|
|
1051
|
-
export const { sanityFetch, SanityLive } = defineLive({
|
|
1302
|
+
export const { sanityFetch, SanityLive: LiveComponent } = defineLive({
|
|
1052
1303
|
client,
|
|
1053
1304
|
serverToken: readToken || undefined,
|
|
1054
1305
|
browserToken: readToken || undefined,
|
|
1055
1306
|
});
|
|
1056
|
-
|
|
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";
|
|
1057
1324
|
import { client } from "./client";
|
|
1058
1325
|
import { hasSanityCredentials } from "./env";
|
|
1059
1326
|
import { sanityFetch } from "./live";
|
|
@@ -1107,52 +1374,234 @@ export function urlFor(source: unknown, width = 1200): string | null {
|
|
|
1107
1374
|
return builder.image(ref).width(width).auto("format").url();
|
|
1108
1375
|
}
|
|
1109
1376
|
`, NEXT_PAGE_BUILDER = `import { PortableText, type PortableTextProps } from "@portabletext/react";
|
|
1377
|
+
import { urlFor } from "@workspace/sanity/image";
|
|
1110
1378
|
import Image from "next/image";
|
|
1111
|
-
import { urlFor } from "./image";
|
|
1112
1379
|
|
|
1113
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
|
+
};
|
|
1114
1391
|
|
|
1115
1392
|
export type PageBlock =
|
|
1116
|
-
| {
|
|
1117
|
-
|
|
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
|
+
}
|
|
1118
1452
|
| {
|
|
1119
1453
|
_key: string;
|
|
1120
1454
|
_type: "cta";
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1455
|
+
eyebrow?: string | null;
|
|
1456
|
+
title?: string | null;
|
|
1457
|
+
richText?: Rich;
|
|
1458
|
+
buttons?: ButtonLink[] | null;
|
|
1125
1459
|
};
|
|
1126
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
|
+
|
|
1127
1492
|
function Block({ block }: { block: PageBlock }) {
|
|
1128
1493
|
switch (block._type) {
|
|
1129
1494
|
case "hero": {
|
|
1130
1495
|
const src = urlFor(block.image, 1600);
|
|
1131
1496
|
return (
|
|
1132
|
-
<section>
|
|
1133
|
-
<
|
|
1134
|
-
|
|
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} />
|
|
1135
1502
|
{src ? (
|
|
1136
1503
|
<Image src={src} alt={block.image?.alt ?? ""} width={1600} height={900} />
|
|
1137
1504
|
) : null}
|
|
1138
1505
|
</section>
|
|
1139
1506
|
);
|
|
1140
1507
|
}
|
|
1141
|
-
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.
|
|
1142
1587
|
return (
|
|
1143
|
-
<section>
|
|
1144
|
-
|
|
1145
|
-
{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}
|
|
1146
1596
|
</section>
|
|
1147
1597
|
);
|
|
1148
1598
|
case "cta":
|
|
1149
1599
|
return (
|
|
1150
|
-
<section>
|
|
1151
|
-
<
|
|
1152
|
-
|
|
1153
|
-
{block.
|
|
1154
|
-
|
|
1155
|
-
) : 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} />
|
|
1156
1605
|
</section>
|
|
1157
1606
|
);
|
|
1158
1607
|
default:
|
|
@@ -1170,34 +1619,9 @@ export function PageBuilder({ blocks }: { blocks: PageBlock[] }) {
|
|
|
1170
1619
|
</>
|
|
1171
1620
|
);
|
|
1172
1621
|
}
|
|
1173
|
-
`,
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
// build-time \`prebuild\` hook works on a fresh clone.
|
|
1177
|
-
export default defineCliConfig({
|
|
1178
|
-
api: {
|
|
1179
|
-
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || "placeholder",
|
|
1180
|
-
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || "production",
|
|
1181
|
-
},
|
|
1182
|
-
typegen: {
|
|
1183
|
-
path: "./sanity/**/*.{ts,tsx}",
|
|
1184
|
-
schema: "sanity/schema.json",
|
|
1185
|
-
generates: "sanity/sanity.types.ts",
|
|
1186
|
-
},
|
|
1187
|
-
});
|
|
1188
|
-
`, NEXT_STUDIO_PAGE = `import { NextStudio } from "next-sanity/studio";
|
|
1189
|
-
import config from "../../../sanity.config";
|
|
1190
|
-
|
|
1191
|
-
export const dynamic = "force-static";
|
|
1192
|
-
|
|
1193
|
-
export { metadata, viewport } from "next-sanity/studio";
|
|
1194
|
-
|
|
1195
|
-
export default function StudioPage() {
|
|
1196
|
-
return <NextStudio config={config} />;
|
|
1197
|
-
}
|
|
1198
|
-
`, NEXT_DRAFT_MODE_ROUTE = `import { defineEnableDraftMode } from "next-sanity/draft-mode";
|
|
1199
|
-
import { readToken } from "../../../../sanity/env";
|
|
1200
|
-
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";
|
|
1201
1625
|
|
|
1202
1626
|
export const { GET } = defineEnableDraftMode({
|
|
1203
1627
|
client: client.withConfig({ token: readToken }),
|
|
@@ -1235,10 +1659,10 @@ export function DisableDraftMode() {
|
|
|
1235
1659
|
</a>
|
|
1236
1660
|
);
|
|
1237
1661
|
}
|
|
1238
|
-
`, NEXT_HOME_PAGE = `import
|
|
1239
|
-
import {
|
|
1240
|
-
import { PageBuilder, type PageBlock } from "
|
|
1241
|
-
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";
|
|
1242
1666
|
|
|
1243
1667
|
// stega: false — encoded metadata would leak invisible characters into <title>.
|
|
1244
1668
|
export async function generateMetadata(): Promise<Metadata> {
|
|
@@ -1258,7 +1682,8 @@ export default async function HomePage() {
|
|
|
1258
1682
|
<h1>No content yet</h1>
|
|
1259
1683
|
<p>
|
|
1260
1684
|
Set NEXT_PUBLIC_SANITY_PROJECT_ID in apps/web/.env.local, then seed the dataset:
|
|
1261
|
-
<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>.
|
|
1262
1687
|
</p>
|
|
1263
1688
|
</main>
|
|
1264
1689
|
);
|
|
@@ -1271,10 +1696,10 @@ export default async function HomePage() {
|
|
|
1271
1696
|
</main>
|
|
1272
1697
|
);
|
|
1273
1698
|
}
|
|
1274
|
-
`, NEXT_SLUG_PAGE = `import {
|
|
1275
|
-
import {
|
|
1276
|
-
import { PageBuilder, type PageBlock } from "
|
|
1277
|
-
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";
|
|
1278
1703
|
|
|
1279
1704
|
export async function generateStaticParams() {
|
|
1280
1705
|
const slugs = await loadStaticQuery(PAGE_SLUGS_QUERY);
|
|
@@ -1293,193 +1718,1877 @@ export default async function Page({ params }: { params: Promise<{ slug: string
|
|
|
1293
1718
|
</main>
|
|
1294
1719
|
);
|
|
1295
1720
|
}
|
|
1296
|
-
`,
|
|
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";
|
|
1297
1726
|
|
|
1298
|
-
export const
|
|
1727
|
+
export const metadata: Metadata = { title: "Blog" };
|
|
1299
1728
|
|
|
1300
|
-
|
|
1301
|
-
|
|
1729
|
+
export default async function BlogIndexPage() {
|
|
1730
|
+
const posts = (await loadQuery(BLOG_INDEX_QUERY)) ?? [];
|
|
1302
1731
|
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
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";
|
|
1309
1769
|
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
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";
|
|
1814
|
+
|
|
1815
|
+
/**
|
|
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.
|
|
2308
|
+
*
|
|
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.
|
|
2383
|
+
*/
|
|
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} } }
|
|
2405
|
+
}
|
|
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
|
+
});
|
|
1317
2704
|
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
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\`
|
|
1322
2808
|
*
|
|
1323
|
-
*
|
|
1324
|
-
*
|
|
1325
|
-
*
|
|
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
|
+
* ---------------------------------------------------------------------------------
|
|
1326
2816
|
*/
|
|
1327
|
-
export async function loadQuery<T>(
|
|
1328
|
-
query: string,
|
|
1329
|
-
params: Record<string, unknown> = {},
|
|
1330
|
-
): Promise<T | null> {
|
|
1331
|
-
if (!hasSanityCredentials) return null;
|
|
1332
|
-
if (visualEditingEnabled && !readToken) {
|
|
1333
|
-
throw new Error(
|
|
1334
|
-
"SANITY_API_READ_TOKEN is required when PUBLIC_SANITY_VISUAL_EDITING_ENABLED is true.",
|
|
1335
|
-
);
|
|
1336
|
-
}
|
|
1337
|
-
try {
|
|
1338
|
-
const { result } = await sanityClient.fetch<T>(query, params, {
|
|
1339
|
-
filterResponse: false,
|
|
1340
|
-
perspective: visualEditingEnabled ? "drafts" : "published",
|
|
1341
|
-
resultSourceMap: visualEditingEnabled ? "withKeyArraySelector" : false,
|
|
1342
|
-
stega: visualEditingEnabled,
|
|
1343
|
-
...(visualEditingEnabled ? { token: readToken } : {}),
|
|
1344
|
-
useCdn: !visualEditingEnabled,
|
|
1345
|
-
});
|
|
1346
|
-
return result;
|
|
1347
|
-
} catch (error) {
|
|
1348
|
-
console.warn("[sanity] query failed:", error);
|
|
1349
|
-
return null;
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
`, ASTRO_IMAGE = `import { createImageUrlBuilder } from "@sanity/image-url";
|
|
1353
|
-
import { dataset, projectId } from "./env";
|
|
1354
2817
|
|
|
1355
|
-
|
|
2818
|
+
export declare const internalGroqTypeReferenceTo: unique symbol;
|
|
1356
2819
|
|
|
1357
|
-
|
|
1358
|
-
export
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
2820
|
+
// Source: schema.json
|
|
2821
|
+
export type SanityImageAssetReference = {
|
|
2822
|
+
_ref: string;
|
|
2823
|
+
_type: "reference";
|
|
2824
|
+
_weak?: boolean;
|
|
2825
|
+
[internalGroqTypeReferenceTo]?: "sanity.imageAsset";
|
|
2826
|
+
};
|
|
1364
2827
|
|
|
1365
|
-
type
|
|
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
|
+
};
|
|
1366
2836
|
|
|
1367
|
-
export type
|
|
1368
|
-
|
|
1369
|
-
|
|
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<
|
|
1370
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";
|
|
1371
2946
|
_key: string;
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
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
|
+
};
|
|
2987
|
+
|
|
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
|
+
};
|
|
3004
|
+
|
|
3005
|
+
export type SanityImageCrop = {
|
|
3006
|
+
_type: "sanity.imageCrop";
|
|
3007
|
+
top: number;
|
|
3008
|
+
bottom: number;
|
|
3009
|
+
left: number;
|
|
3010
|
+
right: number;
|
|
3011
|
+
};
|
|
3012
|
+
|
|
3013
|
+
export type SanityImageHotspot = {
|
|
3014
|
+
_type: "sanity.imageHotspot";
|
|
3015
|
+
x: number;
|
|
3016
|
+
y: number;
|
|
3017
|
+
height: number;
|
|
3018
|
+
width: number;
|
|
3019
|
+
};
|
|
3020
|
+
|
|
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
|
+
{
|
|
3067
|
+
_key: string;
|
|
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
|
+
};
|
|
3166
|
+
|
|
3167
|
+
export type SanityFileAsset = {
|
|
3168
|
+
_id: string;
|
|
3169
|
+
_type: "sanity.fileAsset";
|
|
3170
|
+
_createdAt: string;
|
|
3171
|
+
_updatedAt: string;
|
|
3172
|
+
_rev: string;
|
|
3173
|
+
originalFilename?: string;
|
|
3174
|
+
label?: string;
|
|
3175
|
+
title?: string;
|
|
3176
|
+
description?: string;
|
|
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
|
+
};
|
|
3188
|
+
|
|
3189
|
+
export type SanityAssetSourceData = {
|
|
3190
|
+
_type: "sanity.assetSourceData";
|
|
3191
|
+
name?: string;
|
|
3192
|
+
id?: string;
|
|
3193
|
+
url?: string;
|
|
3194
|
+
};
|
|
3195
|
+
|
|
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
|
+
};
|
|
3218
|
+
|
|
3219
|
+
export type Geopoint = {
|
|
3220
|
+
_type: "geopoint";
|
|
3221
|
+
lat?: number;
|
|
3222
|
+
lng?: number;
|
|
3223
|
+
alt?: number;
|
|
3224
|
+
};
|
|
3225
|
+
|
|
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;
|
|
1378
3372
|
|
|
1379
|
-
|
|
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 = {
|
|
1380
3377
|
_id: string;
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
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;
|
|
1385
3484
|
} | null;
|
|
1386
|
-
`, ASTRO_PAGE_BUILDER = `---
|
|
1387
|
-
import { PortableText } from "astro-portabletext";
|
|
1388
|
-
import type { PageBlock } from "../../sanity/types";
|
|
1389
|
-
import { urlFor } from "../../sanity/image";
|
|
1390
3485
|
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
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>;
|
|
1394
3490
|
|
|
1395
|
-
|
|
1396
|
-
|
|
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;
|
|
1397
3553
|
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
}
|
|
1418
|
-
if (block._type === "cta") {
|
|
1419
|
-
return (
|
|
1420
|
-
<section>
|
|
1421
|
-
<h2>{block.heading}</h2>
|
|
1422
|
-
{block.text && <p>{block.text}</p>}
|
|
1423
|
-
{block.buttonHref && block.buttonLabel && (
|
|
1424
|
-
<a href={block.buttonHref}>{block.buttonLabel}</a>
|
|
1425
|
-
)}
|
|
1426
|
-
</section>
|
|
1427
|
-
);
|
|
1428
|
-
}
|
|
1429
|
-
return null;
|
|
1430
|
-
})
|
|
1431
|
-
}
|
|
1432
|
-
`, ASTRO_SLUG_PAGE = `---
|
|
1433
|
-
import { VisualEditing } from "@sanity/astro/visual-editing";
|
|
1434
|
-
import PageBuilder from "../components/PageBuilder.astro";
|
|
1435
|
-
import { visualEditingEnabled } from "../../sanity/env";
|
|
1436
|
-
import { loadQuery } from "../../sanity/fetch";
|
|
1437
|
-
import { PAGE_QUERY, PAGE_SLUGS_QUERY } from "../../sanity/queries";
|
|
1438
|
-
import type { SanityPage } from "../../sanity/types";
|
|
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;
|
|
1439
3573
|
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
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
|
+
}
|
|
1444
3586
|
}
|
|
3587
|
+
`;
|
|
1445
3588
|
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
<html lang="en">
|
|
1450
|
-
<head>
|
|
1451
|
-
<meta charset="utf-8" />
|
|
1452
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1453
|
-
<title>{page?.title ?? "web"}</title>
|
|
1454
|
-
</head>
|
|
1455
|
-
<body style="font-family: system-ui, sans-serif; padding: 3rem;">
|
|
1456
|
-
<h1>{page?.title}</h1>
|
|
1457
|
-
<PageBuilder blocks={page?.pageBuilder ?? []} />
|
|
1458
|
-
<VisualEditing enabled={visualEditingEnabled} />
|
|
1459
|
-
</body>
|
|
1460
|
-
</html>
|
|
1461
|
-
`, ASTRO_SANITY_CONFIG, ASTRO_SANITY_CLI = `import { defineCliConfig } from "sanity/cli";
|
|
1462
|
-
|
|
1463
|
-
// Run from apps/web. The Sanity CLI is a Node process, so it reads process.env rather
|
|
1464
|
-
// than Astro's import.meta.env; astro.config.mjs loads apps/web/.env into it.
|
|
1465
|
-
export default defineCliConfig({
|
|
1466
|
-
api: {
|
|
1467
|
-
projectId: process.env.PUBLIC_SANITY_PROJECT_ID || "placeholder",
|
|
1468
|
-
dataset: process.env.PUBLIC_SANITY_DATASET || "production",
|
|
1469
|
-
},
|
|
1470
|
-
typegen: {
|
|
1471
|
-
path: "./sanity/**/*.{ts,tsx}",
|
|
1472
|
-
schema: "sanity/schema.json",
|
|
1473
|
-
generates: "sanity/sanity.types.ts",
|
|
1474
|
-
},
|
|
1475
|
-
});
|
|
1476
|
-
`, 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.
|
|
1477
|
-
try {
|
|
1478
|
-
process.loadEnvFile(".env");
|
|
1479
|
-
} catch {
|
|
1480
|
-
// No .env — the placeholder ids below keep the build working.
|
|
1481
|
-
}
|
|
1482
|
-
`, prependAstroConfigStatement = (content, block, marker) => content.includes(marker) ? content : content.replace("export default defineConfig({", `${block}
|
|
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}
|
|
1483
3592
|
export default defineConfig({`), addAstroOptimizeDeps = (content) => {
|
|
1484
3593
|
if (content.includes("react/compiler-runtime"))
|
|
1485
3594
|
return content;
|
|
@@ -1496,23 +3605,12 @@ export default defineConfig({`), addAstroOptimizeDeps = (content) => {
|
|
|
1496
3605
|
return content.replace(/vite:\s*\{/, (m) => `${m} ${field}`);
|
|
1497
3606
|
return content.replace(/defineConfig\(\{/, (m) => `${m}
|
|
1498
3607
|
vite: { ${field} },`);
|
|
1499
|
-
},
|
|
1500
|
-
const pkg = JSON.parse(pkgJson);
|
|
1501
|
-
const merged = { ...pkg.scripts, ...scripts };
|
|
1502
|
-
const sorted = Object.fromEntries(Object.entries(merged).sort(([a], [b]) => a.localeCompare(b)));
|
|
1503
|
-
return `${JSON.stringify({ ...pkg, scripts: sorted }, null, 2)}
|
|
1504
|
-
`;
|
|
1505
|
-
}, TYPEGEN = "sanity schema extract --enforce-required-fields --path sanity/schema.json && sanity typegen generate", patchWebPackageJson = (content) => addScripts(content, {
|
|
1506
|
-
"sanity:typegen": TYPEGEN,
|
|
1507
|
-
prebuild: TYPEGEN,
|
|
1508
|
-
...content.includes('"next build"') ? { "precheck-types": TYPEGEN } : {},
|
|
1509
|
-
"sanity:seed": "sanity dataset import sanity/seed.ndjson"
|
|
1510
|
-
}), 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) => {
|
|
1511
3609
|
const withImports = [
|
|
1512
3610
|
'import { draftMode } from "next/headers";',
|
|
1513
3611
|
'import { VisualEditing } from "next-sanity/visual-editing";',
|
|
1514
3612
|
'import { DisableDraftMode } from "../sanity/disable-draft-mode";',
|
|
1515
|
-
'import { SanityLive } from "
|
|
3613
|
+
'import { SanityLive } from "@workspace/sanity/live";'
|
|
1516
3614
|
].reduce(ensureImport, content);
|
|
1517
3615
|
const asAsync = replaceOnce(withImports, NEXT_LAYOUT_SIGNATURE, `export default async function RootLayout({ children }: { children: ReactNode }) {
|
|
1518
3616
|
const { isEnabled: isDraft } = await draftMode();`);
|
|
@@ -1534,15 +3632,15 @@ export default defineConfig({`), addAstroOptimizeDeps = (content) => {
|
|
|
1534
3632
|
return content;
|
|
1535
3633
|
const withImports = [
|
|
1536
3634
|
'import { VisualEditing } from "@sanity/astro/visual-editing";',
|
|
1537
|
-
'import
|
|
1538
|
-
'import {
|
|
1539
|
-
'import {
|
|
1540
|
-
'import {
|
|
1541
|
-
'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";'
|
|
1542
3640
|
].reduce(ensureImport, content);
|
|
1543
3641
|
const withFetch = replaceOnce(withImports, 'log.info("rendering home");', `log.info("rendering home");
|
|
1544
3642
|
|
|
1545
|
-
const page = await loadQuery<
|
|
3643
|
+
const page = await loadQuery<HOME_PAGE_QUERY_RESULT>(HOME_PAGE_QUERY);`);
|
|
1546
3644
|
const withHeading = replaceOnce(withFetch, " <h1>create-turbo-wizard</h1>", ' <h1>{page?.title ?? "create-turbo-wizard"}</h1>');
|
|
1547
3645
|
const withBlocks = replaceOnce(withHeading, " <p>Astro + Turborepo, scaffolded.</p>", [
|
|
1548
3646
|
" <PageBuilder blocks={page?.pageBuilder ?? []} />",
|
|
@@ -1556,136 +3654,142 @@ const page = await loadQuery<SanityPage>(HOME_PAGE_QUERY);`);
|
|
|
1556
3654
|
}, README_SECTION = `
|
|
1557
3655
|
## Sanity
|
|
1558
3656
|
|
|
1559
|
-
The Studio is
|
|
1560
|
-
|
|
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\`.
|
|
1561
3661
|
|
|
1562
3662
|
\`\`\`sh
|
|
1563
|
-
|
|
1564
|
-
cp
|
|
1565
|
-
pnpm
|
|
1566
|
-
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
|
|
1567
3668
|
\`\`\`
|
|
1568
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
|
+
|
|
1569
3673
|
Without a project id the site builds and serves an empty state — no credentials required.
|
|
1570
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
|
+
|
|
1571
3687
|
### Visual Editing
|
|
1572
3688
|
|
|
1573
3689
|
Next: open the Studio's **Presentation** tab — draft mode enables via
|
|
1574
3690
|
\`/api/draft-mode/enable\`; leave it with the on-page "Disable draft mode" button.
|
|
1575
3691
|
Astro: set \`PUBLIC_SANITY_VISUAL_EDITING_ENABLED=true\` and \`SANITY_API_READ_TOKEN\`,
|
|
1576
3692
|
then use the dev server — overlays need server rendering (add an SSR adapter for a
|
|
1577
|
-
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.
|
|
1578
3695
|
`, sanity;
|
|
1579
3696
|
var init_sanity = __esm(() => {
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
structureTool({ structure }),
|
|
1609
|
-
],
|
|
1610
|
-
schema: { types: schemaTypes },
|
|
1611
|
-
${DOCUMENT_GUARD}
|
|
1612
|
-
});
|
|
1613
|
-
`;
|
|
1614
|
-
ASTRO_SANITY_CONFIG = `import { defineConfig } from "sanity";
|
|
1615
|
-
import { presentationTool } from "sanity/presentation";
|
|
1616
|
-
import { structureTool } from "sanity/structure";
|
|
1617
|
-
import { locations, mainDocuments } from "./sanity/presentation";
|
|
1618
|
-
import { schemaTypes } from "./sanity/schema/index";
|
|
1619
|
-
import { singletonActions, singletonTypes, structure } from "./sanity/structure";
|
|
1620
|
-
|
|
1621
|
-
export default defineConfig({
|
|
1622
|
-
name: "default",
|
|
1623
|
-
title: "Studio",
|
|
1624
|
-
projectId: import.meta.env.PUBLIC_SANITY_PROJECT_ID || "placeholder",
|
|
1625
|
-
dataset: import.meta.env.PUBLIC_SANITY_DATASET || "production",
|
|
1626
|
-
plugins: [
|
|
1627
|
-
presentationTool({
|
|
1628
|
-
resolve: { mainDocuments, locations },
|
|
1629
|
-
// No previewMode: Astro has no draft-mode routes. Draft content comes from
|
|
1630
|
-
// loadQuery's perspective switch (PUBLIC_SANITY_VISUAL_EDITING_ENABLED + token),
|
|
1631
|
-
// which needs a rendering server — \`astro dev\`, or an SSR adapter in production.
|
|
1632
|
-
previewUrl: { initial: "/" },
|
|
1633
|
-
}),
|
|
1634
|
-
structureTool({ structure }),
|
|
1635
|
-
],
|
|
1636
|
-
schema: { types: schemaTypes },
|
|
1637
|
-
${DOCUMENT_GUARD}
|
|
1638
|
-
});
|
|
1639
|
-
`;
|
|
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
|
+
});
|
|
1640
3725
|
sanity = {
|
|
1641
3726
|
id: "sanity",
|
|
1642
3727
|
kind: "content",
|
|
1643
3728
|
prompt: {
|
|
1644
3729
|
label: "Sanity",
|
|
1645
|
-
hint: "Headless CMS with
|
|
3730
|
+
hint: "Headless CMS with a Studio of its own (apps/studio)"
|
|
1646
3731
|
},
|
|
1647
3732
|
templates: [
|
|
1648
|
-
{ path: "
|
|
1649
|
-
{ path: "
|
|
1650
|
-
{ path: "
|
|
1651
|
-
{ path: "
|
|
1652
|
-
{ path: "
|
|
1653
|
-
{ path: "apps/
|
|
1654
|
-
{ path: "apps/
|
|
1655
|
-
{ 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 }
|
|
1656
3749
|
],
|
|
1657
3750
|
fragments: {
|
|
1658
3751
|
next: [
|
|
1659
|
-
{ path: "
|
|
1660
|
-
{ path: "
|
|
1661
|
-
{ path: "
|
|
1662
|
-
{ path: "
|
|
1663
|
-
{ path: "
|
|
1664
|
-
{ 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 },
|
|
1665
3760
|
{ path: "apps/web/sanity/disable-draft-mode.tsx", content: NEXT_DISABLE_DRAFT_MODE },
|
|
1666
|
-
{ path: "apps/web/sanity.config.ts", content: NEXT_SANITY_CONFIG },
|
|
1667
|
-
{ path: "apps/web/sanity.cli.ts", content: NEXT_SANITY_CLI },
|
|
1668
|
-
{ path: "apps/web/app/studio/[[...tool]]/page.tsx", content: NEXT_STUDIO_PAGE },
|
|
1669
3761
|
{ path: "apps/web/app/api/draft-mode/enable/route.ts", content: NEXT_DRAFT_MODE_ROUTE },
|
|
1670
3762
|
{
|
|
1671
3763
|
path: "apps/web/app/api/draft-mode/disable/route.ts",
|
|
1672
3764
|
content: NEXT_DRAFT_MODE_DISABLE_ROUTE
|
|
1673
3765
|
},
|
|
1674
|
-
{ 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 }
|
|
1675
3769
|
],
|
|
1676
3770
|
astro: [
|
|
1677
|
-
{ path: "
|
|
1678
|
-
{ path: "
|
|
1679
|
-
{ path: "
|
|
1680
|
-
{ path: "
|
|
1681
|
-
{ path: "apps/
|
|
1682
|
-
{ path: "apps/
|
|
1683
|
-
{ path: "apps/web/src/
|
|
1684
|
-
{ 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 }
|
|
1685
3780
|
]
|
|
1686
3781
|
},
|
|
3782
|
+
scripts: {
|
|
3783
|
+
"dev:studio": "turbo run dev --filter=studio",
|
|
3784
|
+
"build:studio": "turbo run build --filter=studio"
|
|
3785
|
+
},
|
|
1687
3786
|
deps: [
|
|
1688
|
-
{ 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" }
|
|
1689
3793
|
],
|
|
1690
3794
|
frontendDeps: {
|
|
1691
3795
|
next: [
|
|
@@ -1693,59 +3797,40 @@ ${DOCUMENT_GUARD}
|
|
|
1693
3797
|
{ name: "@portabletext/react", version: "^7.0.1", target: "app" },
|
|
1694
3798
|
{ name: "@sanity/image-url", version: "^2.1.1", target: "app" },
|
|
1695
3799
|
{ name: "@sanity/client", version: "^7.26.0", target: "app" },
|
|
1696
|
-
{ name: "sanity", version: "^
|
|
1697
|
-
{ 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" }
|
|
1698
3806
|
],
|
|
1699
3807
|
astro: [
|
|
1700
3808
|
{ name: "@sanity/astro", version: "^3.5.0", target: "app" },
|
|
1701
3809
|
{ name: "astro-portabletext", version: "^0.13.0", target: "app" },
|
|
1702
|
-
{ name: "@portabletext/types", version: "^4.0.2", target: "app" },
|
|
1703
3810
|
{ name: "@sanity/image-url", version: "^2.1.1", target: "app" },
|
|
1704
3811
|
{ name: "@sanity/client", version: "^7.26.0", target: "app" },
|
|
1705
|
-
{ name: "sanity", version: "^5.31.1", target: "app" },
|
|
1706
3812
|
{ name: "@sanity/ui", version: "^3.5.1", target: "app" },
|
|
1707
3813
|
{ name: "react-compiler-runtime", version: "^1.0.0", target: "app" },
|
|
1708
3814
|
{ name: "react-is", version: "^19.0.0", target: "app" },
|
|
1709
3815
|
{ name: "lodash", version: "^4.17.21", target: "app" },
|
|
1710
|
-
{ name: "
|
|
1711
|
-
{ name: "
|
|
3816
|
+
{ name: "@sanity/image-url", version: "^2.1.1", target: "sanity" },
|
|
3817
|
+
{ name: "astro-portabletext", version: "^0.13.0", target: "sanity-blocks" }
|
|
1712
3818
|
]
|
|
1713
3819
|
},
|
|
1714
3820
|
env: [
|
|
1715
3821
|
{ key: "NEXT_PUBLIC_SANITY_PROJECT_ID", scope: "client" },
|
|
1716
3822
|
{ key: "NEXT_PUBLIC_SANITY_DATASET", scope: "client" },
|
|
1717
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" },
|
|
1718
3828
|
{ key: "SANITY_API_READ_TOKEN", scope: "server" },
|
|
1719
3829
|
{ key: "PUBLIC_SANITY_PROJECT_ID", scope: "client" },
|
|
1720
3830
|
{ key: "PUBLIC_SANITY_DATASET", scope: "client" },
|
|
3831
|
+
{ key: "PUBLIC_SANITY_STUDIO_URL", scope: "client" },
|
|
1721
3832
|
{ key: "PUBLIC_SANITY_VISUAL_EDITING_ENABLED", scope: "client" }
|
|
1722
3833
|
],
|
|
1723
|
-
owns: [
|
|
1724
|
-
"apps/web/sanity/schema/blocks.ts",
|
|
1725
|
-
"apps/web/sanity/schema/page.ts",
|
|
1726
|
-
"apps/web/sanity/schema/settings.ts",
|
|
1727
|
-
"apps/web/sanity/schema/index.ts",
|
|
1728
|
-
"apps/web/sanity/structure.ts",
|
|
1729
|
-
"apps/web/sanity/presentation.ts",
|
|
1730
|
-
"apps/web/sanity/queries.ts",
|
|
1731
|
-
"apps/web/sanity/seed.ndjson",
|
|
1732
|
-
"apps/web/sanity/env.ts",
|
|
1733
|
-
"apps/web/sanity/client.ts",
|
|
1734
|
-
"apps/web/sanity/live.ts",
|
|
1735
|
-
"apps/web/sanity/fetch.ts",
|
|
1736
|
-
"apps/web/sanity/image.ts",
|
|
1737
|
-
"apps/web/sanity/types.ts",
|
|
1738
|
-
"apps/web/sanity/page-builder.tsx",
|
|
1739
|
-
"apps/web/sanity/disable-draft-mode.tsx",
|
|
1740
|
-
"apps/web/sanity.config.ts",
|
|
1741
|
-
"apps/web/sanity.cli.ts",
|
|
1742
|
-
"apps/web/app/studio/[[...tool]]/page.tsx",
|
|
1743
|
-
"apps/web/app/api/draft-mode/enable/route.ts",
|
|
1744
|
-
"apps/web/app/api/draft-mode/disable/route.ts",
|
|
1745
|
-
"apps/web/app/[slug]/page.tsx",
|
|
1746
|
-
"apps/web/src/components/PageBuilder.astro",
|
|
1747
|
-
"apps/web/src/pages/[slug].astro"
|
|
1748
|
-
],
|
|
1749
3834
|
patches: [
|
|
1750
3835
|
{
|
|
1751
3836
|
path: "apps/web/next.config.ts",
|
|
@@ -1755,14 +3840,13 @@ ${DOCUMENT_GUARD}
|
|
|
1755
3840
|
{ path: "apps/web/app/page.tsx", apply: replaceHomePage(NEXT_HOME_PAGE) },
|
|
1756
3841
|
{
|
|
1757
3842
|
path: "apps/web/astro.config.mjs",
|
|
1758
|
-
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' } })`))
|
|
1759
3844
|
},
|
|
1760
3845
|
{
|
|
1761
3846
|
path: "apps/web/src/env.d.ts",
|
|
1762
3847
|
apply: (content) => ensureImport(content, ASTRO_MODULE_REF)
|
|
1763
3848
|
},
|
|
1764
3849
|
{ path: "apps/web/src/pages/index.astro", apply: patchAstroIndex },
|
|
1765
|
-
{ path: "apps/web/package.json", apply: patchWebPackageJson },
|
|
1766
3850
|
{
|
|
1767
3851
|
path: "README.md",
|
|
1768
3852
|
apply: (content) => content.includes("## Sanity") ? content : content + README_SECTION
|
|
@@ -1772,7 +3856,7 @@ ${DOCUMENT_GUARD}
|
|
|
1772
3856
|
});
|
|
1773
3857
|
|
|
1774
3858
|
// ../../packages/registry/src/frontends/astro.ts
|
|
1775
|
-
var
|
|
3859
|
+
var j7 = (value) => `${JSON.stringify(value, null, 2)}
|
|
1776
3860
|
`, ASTRO_CONFIG = `import react from "@astrojs/react";
|
|
1777
3861
|
import { defineConfig } from "astro/config";
|
|
1778
3862
|
|
|
@@ -1805,7 +3889,7 @@ var init_astro = __esm(() => {
|
|
|
1805
3889
|
templates: [
|
|
1806
3890
|
{
|
|
1807
3891
|
path: "apps/web/package.json",
|
|
1808
|
-
content:
|
|
3892
|
+
content: j7({
|
|
1809
3893
|
name: "web",
|
|
1810
3894
|
version: "0.0.0",
|
|
1811
3895
|
private: true,
|
|
@@ -1837,7 +3921,7 @@ var init_astro = __esm(() => {
|
|
|
1837
3921
|
{ path: "apps/web/astro.config.mjs", content: ASTRO_CONFIG },
|
|
1838
3922
|
{
|
|
1839
3923
|
path: "apps/web/tsconfig.json",
|
|
1840
|
-
content:
|
|
3924
|
+
content: j7({
|
|
1841
3925
|
extends: "astro/tsconfigs/strict",
|
|
1842
3926
|
compilerOptions: { jsx: "react-jsx", jsxImportSource: "react" },
|
|
1843
3927
|
include: [".astro/types.d.ts", "**/*"],
|
|
@@ -1847,17 +3931,44 @@ var init_astro = __esm(() => {
|
|
|
1847
3931
|
{ path: "apps/web/src/env.d.ts", content: `/// <reference types="astro/client" />
|
|
1848
3932
|
` },
|
|
1849
3933
|
{ path: "apps/web/src/pages/index.astro", content: INDEX_ASTRO }
|
|
1850
|
-
],
|
|
1851
|
-
owns: [
|
|
1852
|
-
"apps/web/package.json",
|
|
1853
|
-
"apps/web/astro.config.mjs",
|
|
1854
|
-
"apps/web/tsconfig.json",
|
|
1855
|
-
"apps/web/src/env.d.ts",
|
|
1856
|
-
"apps/web/src/pages/index.astro"
|
|
1857
3934
|
]
|
|
1858
3935
|
};
|
|
1859
3936
|
});
|
|
1860
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
|
+
|
|
1861
3972
|
// ../../packages/registry/src/frontends/experimental.ts
|
|
1862
3973
|
var BABEL_REACT_COMPILER_VERSION = "^1.0.0", experimentalDescriptor = (config) => {
|
|
1863
3974
|
const { cacheComponents, reactCompiler } = config.experimental;
|
|
@@ -1894,7 +4005,7 @@ var BABEL_REACT_COMPILER_VERSION = "^1.0.0", experimentalDescriptor = (config) =
|
|
|
1894
4005
|
var init_experimental = () => {};
|
|
1895
4006
|
|
|
1896
4007
|
// ../../packages/registry/src/frontends/next.ts
|
|
1897
|
-
var
|
|
4008
|
+
var j8 = (value) => `${JSON.stringify(value, null, 2)}
|
|
1898
4009
|
`, NEXT_CONFIG = `import { createRequire } from "node:module";
|
|
1899
4010
|
import type { NextConfig } from "next";
|
|
1900
4011
|
|
|
@@ -1953,7 +4064,7 @@ var init_next = __esm(() => {
|
|
|
1953
4064
|
templates: [
|
|
1954
4065
|
{
|
|
1955
4066
|
path: "apps/web/package.json",
|
|
1956
|
-
content:
|
|
4067
|
+
content: j8({
|
|
1957
4068
|
name: "web",
|
|
1958
4069
|
version: "0.0.0",
|
|
1959
4070
|
private: true,
|
|
@@ -1984,7 +4095,7 @@ var init_next = __esm(() => {
|
|
|
1984
4095
|
{ path: "apps/web/next.config.ts", content: NEXT_CONFIG },
|
|
1985
4096
|
{
|
|
1986
4097
|
path: "apps/web/tsconfig.json",
|
|
1987
|
-
content:
|
|
4098
|
+
content: j8({
|
|
1988
4099
|
extends: "@workspace/typescript-config/nextjs.json",
|
|
1989
4100
|
compilerOptions: { plugins: [{ name: "next" }], paths: { "@/*": ["./*"] } },
|
|
1990
4101
|
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
@@ -1994,14 +4105,6 @@ var init_next = __esm(() => {
|
|
|
1994
4105
|
{ path: "apps/web/app/layout.tsx", content: LAYOUT },
|
|
1995
4106
|
{ path: "apps/web/app/page.tsx", content: PAGE },
|
|
1996
4107
|
{ path: "apps/web/app/globals.css", content: GLOBALS }
|
|
1997
|
-
],
|
|
1998
|
-
owns: [
|
|
1999
|
-
"apps/web/package.json",
|
|
2000
|
-
"apps/web/next.config.ts",
|
|
2001
|
-
"apps/web/tsconfig.json",
|
|
2002
|
-
"apps/web/app/layout.tsx",
|
|
2003
|
-
"apps/web/app/page.tsx",
|
|
2004
|
-
"apps/web/app/globals.css"
|
|
2005
4108
|
]
|
|
2006
4109
|
};
|
|
2007
4110
|
});
|
|
@@ -2170,18 +4273,12 @@ export default function AlgoliaSearch({
|
|
|
2170
4273
|
{ key: "PUBLIC_ALGOLIA_APP_ID", scope: "client" },
|
|
2171
4274
|
{ key: "PUBLIC_ALGOLIA_SEARCH_KEY", scope: "client" },
|
|
2172
4275
|
{ key: "PUBLIC_ALGOLIA_INDEX", scope: "client" }
|
|
2173
|
-
],
|
|
2174
|
-
owns: [
|
|
2175
|
-
"apps/web/app/search/Search.tsx",
|
|
2176
|
-
"apps/web/app/search/page.tsx",
|
|
2177
|
-
"apps/web/src/components/AlgoliaSearch.tsx",
|
|
2178
|
-
"apps/web/src/pages/search.astro"
|
|
2179
4276
|
]
|
|
2180
4277
|
};
|
|
2181
4278
|
});
|
|
2182
4279
|
|
|
2183
4280
|
// ../../packages/registry/src/modules/changesets.ts
|
|
2184
|
-
var
|
|
4281
|
+
var j9 = (v) => `${JSON.stringify(v, null, 2)}
|
|
2185
4282
|
`, CONFIG, README = `# Changesets
|
|
2186
4283
|
|
|
2187
4284
|
This folder is managed by [Changesets](https://github.com/changesets/changesets).
|
|
@@ -2192,7 +4289,7 @@ the \`version-packages\` script consumes them to update versions and changelogs,
|
|
|
2192
4289
|
the \`release\` script builds and publishes.
|
|
2193
4290
|
`, changesets;
|
|
2194
4291
|
var init_changesets = __esm(() => {
|
|
2195
|
-
CONFIG =
|
|
4292
|
+
CONFIG = j9({
|
|
2196
4293
|
$schema: "https://unpkg.com/@changesets/config@3.1.4/schema.json",
|
|
2197
4294
|
changelog: "@changesets/cli/changelog",
|
|
2198
4295
|
commit: false,
|
|
@@ -2220,8 +4317,7 @@ var init_changesets = __esm(() => {
|
|
|
2220
4317
|
changeset: "changeset",
|
|
2221
4318
|
"version-packages": "changeset version",
|
|
2222
4319
|
release: "turbo run build && changeset publish"
|
|
2223
|
-
}
|
|
2224
|
-
owns: [".changeset/config.json", ".changeset/README.md"]
|
|
4320
|
+
}
|
|
2225
4321
|
};
|
|
2226
4322
|
});
|
|
2227
4323
|
|
|
@@ -2270,8 +4366,7 @@ var init_e2e = __esm(() => {
|
|
|
2270
4366
|
]
|
|
2271
4367
|
},
|
|
2272
4368
|
scripts: { e2e: "playwright test" },
|
|
2273
|
-
deps: [{ name: "@playwright/test", version: "^1.60.0", target: "root", dev: true }]
|
|
2274
|
-
owns: ["tests/e2e/home.spec.ts", "playwright.config.ts"]
|
|
4369
|
+
deps: [{ name: "@playwright/test", version: "^1.60.0", target: "root", dev: true }]
|
|
2275
4370
|
};
|
|
2276
4371
|
});
|
|
2277
4372
|
|
|
@@ -2511,13 +4606,6 @@ export const POST: APIRoute = async ({ request }) => {
|
|
|
2511
4606
|
path: "apps/web/astro.config.mjs",
|
|
2512
4607
|
apply: (content) => ensureAstroAdapter(content, 'import node from "@astrojs/node";', 'node({ mode: "standalone" })')
|
|
2513
4608
|
}
|
|
2514
|
-
],
|
|
2515
|
-
owns: [
|
|
2516
|
-
"apps/web/app/contact/page.tsx",
|
|
2517
|
-
"apps/web/app/api/contact/route.ts",
|
|
2518
|
-
"apps/web/src/components/ContactForm.tsx",
|
|
2519
|
-
"apps/web/src/pages/contact.astro",
|
|
2520
|
-
"apps/web/src/pages/api/contact.ts"
|
|
2521
4609
|
]
|
|
2522
4610
|
};
|
|
2523
4611
|
});
|
|
@@ -2559,8 +4647,7 @@ var init_git_hooks = __esm(() => {
|
|
|
2559
4647
|
target: "root",
|
|
2560
4648
|
dev: true
|
|
2561
4649
|
}
|
|
2562
|
-
]
|
|
2563
|
-
owns: ["lefthook.yml", "commitlint.config.mjs"]
|
|
4650
|
+
]
|
|
2564
4651
|
};
|
|
2565
4652
|
});
|
|
2566
4653
|
|
|
@@ -2614,8 +4701,7 @@ var init_github_ci = __esm(() => {
|
|
|
2614
4701
|
label: "GitHub Actions CI",
|
|
2615
4702
|
hint: "Workflow that installs, type-checks, and builds on every push and PR."
|
|
2616
4703
|
},
|
|
2617
|
-
templates: [{ path: ".github/workflows/ci.yml", content: CI_WORKFLOW }]
|
|
2618
|
-
owns: [".github/workflows/ci.yml"]
|
|
4704
|
+
templates: [{ path: ".github/workflows/ci.yml", content: CI_WORKFLOW }]
|
|
2619
4705
|
};
|
|
2620
4706
|
});
|
|
2621
4707
|
|
|
@@ -2778,18 +4864,7 @@ ${LLMS_TXT_REST}\`;
|
|
|
2778
4864
|
{ path: "apps/web/src/content/pages.ts", content: ASTRO_CONTENT },
|
|
2779
4865
|
{ path: "apps/web/src/pages/[slug].md.ts", content: ASTRO_MD_ENDPOINT }
|
|
2780
4866
|
]
|
|
2781
|
-
}
|
|
2782
|
-
owns: [
|
|
2783
|
-
"skills.md",
|
|
2784
|
-
"apps/web/app/llms.txt/route.ts",
|
|
2785
|
-
"apps/web/app/docs/content.ts",
|
|
2786
|
-
"apps/web/app/docs/page.tsx",
|
|
2787
|
-
"apps/web/app/docs.md/route.ts",
|
|
2788
|
-
"apps/web/app/lib/markdown-variant.ts",
|
|
2789
|
-
"apps/web/src/pages/llms.txt.ts",
|
|
2790
|
-
"apps/web/src/content/pages.ts",
|
|
2791
|
-
"apps/web/src/pages/[slug].md.ts"
|
|
2792
|
-
]
|
|
4867
|
+
}
|
|
2793
4868
|
};
|
|
2794
4869
|
});
|
|
2795
4870
|
|
|
@@ -2850,7 +4925,6 @@ var init_posthog = __esm(() => {
|
|
|
2850
4925
|
{ key: "PUBLIC_POSTHOG_KEY", scope: "client" },
|
|
2851
4926
|
{ key: "PUBLIC_POSTHOG_HOST", scope: "client" }
|
|
2852
4927
|
],
|
|
2853
|
-
owns: ["apps/web/instrumentation-client.ts", "apps/web/src/components/PostHog.astro"],
|
|
2854
4928
|
patches: [
|
|
2855
4929
|
{ path: "apps/web/src/pages/index.astro", apply: patchIndexAstro },
|
|
2856
4930
|
{ path: "apps/web/src/layouts/Layout.astro", apply: patchIndexAstro }
|
|
@@ -3378,15 +5452,6 @@ var init_sections = __esm(() => {
|
|
|
3378
5452
|
{ path: "packages/ui/src/components/sections/faq.tsx", content: FAQ_TSX },
|
|
3379
5453
|
{ path: "packages/ui/src/components/sections/cta.tsx", content: CTA_TSX }
|
|
3380
5454
|
],
|
|
3381
|
-
owns: [
|
|
3382
|
-
"packages/ui/src/components/sections/hero.tsx",
|
|
3383
|
-
"packages/ui/src/components/sections/features.tsx",
|
|
3384
|
-
"packages/ui/src/components/sections/logo-cloud.tsx",
|
|
3385
|
-
"packages/ui/src/components/sections/testimonials.tsx",
|
|
3386
|
-
"packages/ui/src/components/sections/pricing.tsx",
|
|
3387
|
-
"packages/ui/src/components/sections/faq.tsx",
|
|
3388
|
-
"packages/ui/src/components/sections/cta.tsx"
|
|
3389
|
-
],
|
|
3390
5455
|
patches: [
|
|
3391
5456
|
{ path: "apps/web/app/page.tsx", apply: replaceWith(COMPOSED_NEXT_HOME_PAGE) },
|
|
3392
5457
|
{ path: "apps/web/src/pages/index.astro", apply: replaceWith(COMPOSED_ASTRO_HOME_PAGE) }
|
|
@@ -3654,14 +5719,6 @@ var init_seo = __esm(() => {
|
|
|
3654
5719
|
{ key: "PUBLIC_SITE_NAME", scope: "client" },
|
|
3655
5720
|
{ key: "PUBLIC_SITE_DESCRIPTION", scope: "client" }
|
|
3656
5721
|
],
|
|
3657
|
-
owns: [
|
|
3658
|
-
"apps/web/app/lib/metadata.ts",
|
|
3659
|
-
"apps/web/app/sitemap.ts",
|
|
3660
|
-
"apps/web/app/robots.ts",
|
|
3661
|
-
"apps/web/app/opengraph-image.tsx",
|
|
3662
|
-
"apps/web/src/components/SEO.astro",
|
|
3663
|
-
"apps/web/src/pages/og.png.ts"
|
|
3664
|
-
],
|
|
3665
5722
|
patches: [
|
|
3666
5723
|
{ path: "apps/web/astro.config.mjs", apply: patchAstroConfig },
|
|
3667
5724
|
{ path: "apps/web/app/layout.tsx", apply: patchLayout },
|
|
@@ -3982,7 +6039,7 @@ export function SiteHeader({
|
|
|
3982
6039
|
))}
|
|
3983
6040
|
</nav>
|
|
3984
6041
|
<div className="ml-auto">
|
|
3985
|
-
<Button size="sm" render={<a href={action.href} />}>
|
|
6042
|
+
<Button size="sm" nativeButton={false} render={<a href={action.href} />}>
|
|
3986
6043
|
{action.label}
|
|
3987
6044
|
</Button>
|
|
3988
6045
|
</div>
|
|
@@ -4045,7 +6102,9 @@ export default function NotFound() {
|
|
|
4045
6102
|
<p className="text-muted-foreground">
|
|
4046
6103
|
The link may be out of date, or the page may have moved.
|
|
4047
6104
|
</p>
|
|
4048
|
-
<Button render={<a href="/" />}>
|
|
6105
|
+
<Button nativeButton={false} render={<a href="/" />}>
|
|
6106
|
+
Back home
|
|
6107
|
+
</Button>
|
|
4049
6108
|
</main>
|
|
4050
6109
|
);
|
|
4051
6110
|
}
|
|
@@ -4496,8 +6555,8 @@ ${DARK_TOKENS}
|
|
|
4496
6555
|
});
|
|
4497
6556
|
|
|
4498
6557
|
// ../../packages/registry/src/modules/ui/index.ts
|
|
4499
|
-
var
|
|
4500
|
-
`, UI_PACKAGE_JSON, componentsJson = (rsc) =>
|
|
6558
|
+
var j10 = (value) => `${JSON.stringify(value, null, 2)}
|
|
6559
|
+
`, UI_PACKAGE_JSON, componentsJson = (rsc) => j10({
|
|
4501
6560
|
$schema: "https://ui.shadcn.com/schema.json",
|
|
4502
6561
|
style: "base-nova",
|
|
4503
6562
|
rsc,
|
|
@@ -4520,20 +6579,22 @@ var j7 = (value) => `${JSON.stringify(value, null, 2)}
|
|
|
4520
6579
|
menuColor: "default",
|
|
4521
6580
|
menuAccent: "subtle"
|
|
4522
6581
|
}), patchNextLayout2 = (content) => {
|
|
4523
|
-
if (content.includes("
|
|
6582
|
+
if (content.includes("<SiteHeader"))
|
|
4524
6583
|
return content;
|
|
4525
6584
|
const withImports = [
|
|
4526
6585
|
'import { SiteFooter } from "@workspace/ui/components/site-footer";',
|
|
4527
6586
|
'import { SiteHeader } from "@workspace/ui/components/site-header";',
|
|
4528
|
-
'import rootPkg from "../../../package.json";'
|
|
4529
|
-
|
|
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} />`
|
|
4530
6596
|
].join(`
|
|
4531
|
-
`);
|
|
4532
|
-
return withImports.replace(/<body([^>]*)>\{children\}<\/body>/, `<body$1 className="flex min-h-svh flex-col">
|
|
4533
|
-
<SiteHeader siteName={rootPkg.name} />
|
|
4534
|
-
<div className="flex-1">{children}</div>
|
|
4535
|
-
<SiteFooter siteName={rootPkg.name} />
|
|
4536
|
-
</body>`);
|
|
6597
|
+
`));
|
|
4537
6598
|
}, NEXT_POSTCSS_CONFIG = `export default {
|
|
4538
6599
|
plugins: {
|
|
4539
6600
|
"@tailwindcss/postcss": {},
|
|
@@ -4544,7 +6605,7 @@ var j7 = (value) => `${JSON.stringify(value, null, 2)}
|
|
|
4544
6605
|
var init_ui = __esm(() => {
|
|
4545
6606
|
init_pages();
|
|
4546
6607
|
init_theme();
|
|
4547
|
-
UI_PACKAGE_JSON =
|
|
6608
|
+
UI_PACKAGE_JSON = j10({
|
|
4548
6609
|
name: "@workspace/ui",
|
|
4549
6610
|
version: "0.0.0",
|
|
4550
6611
|
private: true,
|
|
@@ -4608,25 +6669,6 @@ var init_ui = __esm(() => {
|
|
|
4608
6669
|
]
|
|
4609
6670
|
},
|
|
4610
6671
|
deps: [{ name: "@workspace/ui", version: "workspace:*", target: "app" }],
|
|
4611
|
-
owns: [
|
|
4612
|
-
"packages/ui/package.json",
|
|
4613
|
-
"packages/ui/components.json",
|
|
4614
|
-
"packages/ui/src/styles/globals.css",
|
|
4615
|
-
"packages/ui/src/lib/utils.ts",
|
|
4616
|
-
"packages/ui/src/components/button.tsx",
|
|
4617
|
-
"packages/ui/src/components/card.tsx",
|
|
4618
|
-
"packages/ui/src/components/input.tsx",
|
|
4619
|
-
"packages/ui/src/components/textarea.tsx",
|
|
4620
|
-
"packages/ui/src/components/label.tsx",
|
|
4621
|
-
"packages/ui/src/components/badge.tsx",
|
|
4622
|
-
"packages/ui/src/components/site-header.tsx",
|
|
4623
|
-
"packages/ui/src/components/site-footer.tsx",
|
|
4624
|
-
"apps/web/postcss.config.mjs",
|
|
4625
|
-
"apps/web/components.json",
|
|
4626
|
-
"apps/web/app/not-found.tsx",
|
|
4627
|
-
"apps/web/src/layouts/Layout.astro",
|
|
4628
|
-
"apps/web/src/pages/404.astro"
|
|
4629
|
-
],
|
|
4630
6672
|
patches: [
|
|
4631
6673
|
{
|
|
4632
6674
|
path: "apps/web/app/globals.css",
|
|
@@ -4644,7 +6686,7 @@ var init_ui = __esm(() => {
|
|
|
4644
6686
|
if (!plugin || pkg.devDependencies?.[plugin])
|
|
4645
6687
|
return content;
|
|
4646
6688
|
const devDependencies = Object.fromEntries(Object.entries({ ...pkg.devDependencies, [plugin]: "^4.3.0" }).sort(([a], [b]) => a.localeCompare(b)));
|
|
4647
|
-
return
|
|
6689
|
+
return j10({ ...pkg, devDependencies });
|
|
4648
6690
|
}
|
|
4649
6691
|
},
|
|
4650
6692
|
{ path: "apps/web/app/layout.tsx", apply: patchNextLayout2 },
|
|
@@ -4685,8 +6727,7 @@ var init_vitest = __esm(() => {
|
|
|
4685
6727
|
{ path: "tests/example.test.ts", content: EXAMPLE_TEST }
|
|
4686
6728
|
],
|
|
4687
6729
|
scripts: { test: "vitest run", "test:watch": "vitest" },
|
|
4688
|
-
deps: [{ name: "vitest", version: "^4.1.0", target: "root", dev: true }]
|
|
4689
|
-
owns: ["vitest.config.ts", "tests/example.test.ts"]
|
|
6730
|
+
deps: [{ name: "vitest", version: "^4.1.0", target: "root", dev: true }]
|
|
4690
6731
|
};
|
|
4691
6732
|
});
|
|
4692
6733
|
|
|
@@ -4694,12 +6735,14 @@ var init_vitest = __esm(() => {
|
|
|
4694
6735
|
function buildRegistry(config2) {
|
|
4695
6736
|
const content = CONTENT_DESCRIPTORS[config2.content];
|
|
4696
6737
|
const experimental = experimentalDescriptor(config2);
|
|
6738
|
+
const deploy = deployDescriptor(config2);
|
|
4697
6739
|
return [
|
|
4698
6740
|
...baselineDescriptors,
|
|
4699
6741
|
FRONTEND_DESCRIPTORS[config2.frontend],
|
|
4700
6742
|
...experimental ? [experimental] : [],
|
|
4701
6743
|
...content ? [content] : [],
|
|
4702
|
-
...config2.modules.map((id) => MODULE_DESCRIPTORS[id])
|
|
6744
|
+
...config2.modules.map((id) => MODULE_DESCRIPTORS[id]),
|
|
6745
|
+
...deploy ? [deploy] : []
|
|
4703
6746
|
];
|
|
4704
6747
|
}
|
|
4705
6748
|
var baselineDescriptors, FRONTEND_DESCRIPTORS, MODULE_DESCRIPTORS, CONTENT_DESCRIPTORS;
|
|
@@ -4711,6 +6754,7 @@ var init_src3 = __esm(() => {
|
|
|
4711
6754
|
init_markdown();
|
|
4712
6755
|
init_sanity();
|
|
4713
6756
|
init_astro();
|
|
6757
|
+
init_deploy_target();
|
|
4714
6758
|
init_experimental();
|
|
4715
6759
|
init_next();
|
|
4716
6760
|
init_algolia();
|
|
@@ -4790,72 +6834,6 @@ var init_create = __esm(() => {
|
|
|
4790
6834
|
init_src3();
|
|
4791
6835
|
});
|
|
4792
6836
|
|
|
4793
|
-
// package.json
|
|
4794
|
-
var package_default;
|
|
4795
|
-
var init_package = __esm(() => {
|
|
4796
|
-
package_default = {
|
|
4797
|
-
name: "create-turbo-wizard",
|
|
4798
|
-
version: "0.1.0",
|
|
4799
|
-
description: "Scaffold a Turborepo monorepo: Astro or Next.js, Sanity or Markdown, opt-in modules.",
|
|
4800
|
-
keywords: [
|
|
4801
|
-
"turborepo",
|
|
4802
|
-
"monorepo",
|
|
4803
|
-
"scaffold",
|
|
4804
|
-
"astro",
|
|
4805
|
-
"nextjs",
|
|
4806
|
-
"sanity",
|
|
4807
|
-
"shadcn",
|
|
4808
|
-
"tailwind",
|
|
4809
|
-
"marketing-site",
|
|
4810
|
-
"create"
|
|
4811
|
-
],
|
|
4812
|
-
homepage: "https://turbo-wizard.vercel.app",
|
|
4813
|
-
repository: {
|
|
4814
|
-
type: "git",
|
|
4815
|
-
url: "git+https://github.com/robotostudio/turbo-wizard.git",
|
|
4816
|
-
directory: "apps/cli"
|
|
4817
|
-
},
|
|
4818
|
-
bugs: {
|
|
4819
|
-
url: "https://github.com/robotostudio/turbo-wizard/issues"
|
|
4820
|
-
},
|
|
4821
|
-
author: "Roboto Studio",
|
|
4822
|
-
license: "MIT",
|
|
4823
|
-
type: "module",
|
|
4824
|
-
bin: {
|
|
4825
|
-
"create-turbo-wizard": "dist/cli.js"
|
|
4826
|
-
},
|
|
4827
|
-
files: [
|
|
4828
|
-
"dist",
|
|
4829
|
-
"README.md"
|
|
4830
|
-
],
|
|
4831
|
-
scripts: {
|
|
4832
|
-
"check-types": "tsc --noEmit",
|
|
4833
|
-
test: "bun test",
|
|
4834
|
-
start: "bun run src/cli.ts",
|
|
4835
|
-
build: "bun run scripts/bundle.ts",
|
|
4836
|
-
prepublishOnly: "bun run scripts/bundle.ts"
|
|
4837
|
-
},
|
|
4838
|
-
dependencies: {
|
|
4839
|
-
"@clack/prompts": "^1.5.0",
|
|
4840
|
-
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
4841
|
-
"@trpc/server": "^11.0.0",
|
|
4842
|
-
"trpc-cli": "^0.14.1",
|
|
4843
|
-
zod: "^4.4.3"
|
|
4844
|
-
},
|
|
4845
|
-
devDependencies: {
|
|
4846
|
-
"@turbo-wizard/engine": "workspace:*",
|
|
4847
|
-
"@turbo-wizard/registry": "workspace:*",
|
|
4848
|
-
"@turbo-wizard/types": "workspace:*"
|
|
4849
|
-
},
|
|
4850
|
-
engines: {
|
|
4851
|
-
node: ">=22"
|
|
4852
|
-
},
|
|
4853
|
-
publishConfig: {
|
|
4854
|
-
access: "public"
|
|
4855
|
-
}
|
|
4856
|
-
};
|
|
4857
|
-
});
|
|
4858
|
-
|
|
4859
6837
|
// src/mcp.ts
|
|
4860
6838
|
var exports_mcp = {};
|
|
4861
6839
|
__export(exports_mcp, {
|
|
@@ -4941,6 +6919,7 @@ var init_mcp = __esm(() => {
|
|
|
4941
6919
|
|
|
4942
6920
|
// src/router.ts
|
|
4943
6921
|
init_src();
|
|
6922
|
+
init_package();
|
|
4944
6923
|
init_create();
|
|
4945
6924
|
import { initTRPC } from "@trpc/server";
|
|
4946
6925
|
import { createCli } from "trpc-cli";
|
|
@@ -5002,6 +6981,7 @@ var CreateInput = z2.object({
|
|
|
5002
6981
|
frontend: z2.enum(FRONTENDS).optional(),
|
|
5003
6982
|
content: z2.enum(CONTENT_SOURCES).optional(),
|
|
5004
6983
|
packageManager: z2.enum(PACKAGE_MANAGERS).optional(),
|
|
6984
|
+
deploy: z2.enum(DEPLOY_TARGETS).optional(),
|
|
5005
6985
|
modules: z2.array(z2.enum(MODULES)).optional(),
|
|
5006
6986
|
preset: z2.enum(PRESETS).optional(),
|
|
5007
6987
|
cacheComponents: z2.boolean().optional(),
|
|
@@ -5012,13 +6992,14 @@ var CreateInput = z2.object({
|
|
|
5012
6992
|
yes: z2.boolean().default(false)
|
|
5013
6993
|
});
|
|
5014
6994
|
var router = t.router({
|
|
5015
|
-
create: t.procedure.input(CreateInput).mutation(async ({ input }) => {
|
|
6995
|
+
create: t.procedure.meta({ default: true }).input(CreateInput).mutation(async ({ input }) => {
|
|
5016
6996
|
const base = input.preset ? applyPreset(input.preset) : {};
|
|
5017
6997
|
const flags = {
|
|
5018
6998
|
name: input.name,
|
|
5019
6999
|
frontend: input.frontend,
|
|
5020
7000
|
content: input.content ?? base.content,
|
|
5021
7001
|
packageManager: input.packageManager ?? base.packageManager,
|
|
7002
|
+
deploy: input.deploy ?? base.deploy,
|
|
5022
7003
|
modules: input.modules ?? base.modules,
|
|
5023
7004
|
cacheComponents: input.cacheComponents ?? base.experimental?.cacheComponents,
|
|
5024
7005
|
reactCompiler: input.reactCompiler ?? base.experimental?.reactCompiler
|
|
@@ -5029,6 +7010,7 @@ var router = t.router({
|
|
|
5029
7010
|
frontend: flags.frontend ?? "next",
|
|
5030
7011
|
content: flags.content,
|
|
5031
7012
|
packageManager: flags.packageManager,
|
|
7013
|
+
deploy: flags.deploy,
|
|
5032
7014
|
modules: flags.modules,
|
|
5033
7015
|
experimental: {
|
|
5034
7016
|
cacheComponents: flags.cacheComponents,
|
|
@@ -5048,7 +7030,7 @@ var router = t.router({
|
|
|
5048
7030
|
console.error(` next: cd ${res.value.dir} && ${config2.packageManager} install`);
|
|
5049
7031
|
})
|
|
5050
7032
|
});
|
|
5051
|
-
var cli = createCli({ router, name: "create-turbo-wizard", version:
|
|
7033
|
+
var cli = createCli({ router, name: "create-turbo-wizard", version: package_default.version });
|
|
5052
7034
|
|
|
5053
7035
|
// src/cli.ts
|
|
5054
7036
|
if (process.argv[2] === "mcp") {
|