create-turbo-wizard 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -15
- package/dist/cli.js +3089 -789
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -15,13 +15,38 @@ var __export = (target, all) => {
|
|
|
15
15
|
};
|
|
16
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
17
|
|
|
18
|
+
// ../../packages/types/src/command.ts
|
|
19
|
+
function buildCommand(config) {
|
|
20
|
+
const parts = [
|
|
21
|
+
...CREATE_PREFIX[config.packageManager],
|
|
22
|
+
`--name ${config.name}`,
|
|
23
|
+
`--frontend ${config.frontend}`,
|
|
24
|
+
`--content ${config.content}`,
|
|
25
|
+
`--package-manager ${config.packageManager}`
|
|
26
|
+
];
|
|
27
|
+
config.deploy !== "node" && parts.push(`--deploy ${config.deploy}`);
|
|
28
|
+
config.modules.length && parts.push(`--modules ${config.modules.join(" ")}`);
|
|
29
|
+
config.experimental.cacheComponents && parts.push("--cache-components");
|
|
30
|
+
config.experimental.reactCompiler && parts.push("--react-compiler");
|
|
31
|
+
return parts.join(" ");
|
|
32
|
+
}
|
|
33
|
+
var CREATE_PREFIX;
|
|
34
|
+
var init_command = __esm(() => {
|
|
35
|
+
CREATE_PREFIX = {
|
|
36
|
+
npm: ["npm create turbo-wizard@latest", "--"],
|
|
37
|
+
pnpm: ["pnpm create turbo-wizard@latest"],
|
|
38
|
+
bun: ["bun create turbo-wizard@latest"]
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
18
42
|
// ../../packages/types/src/index.ts
|
|
19
43
|
import { z } from "zod";
|
|
20
44
|
function applyPreset(preset, overrides = {}) {
|
|
21
45
|
return { ...PRESET_CONFIGS[preset], ...overrides };
|
|
22
46
|
}
|
|
23
|
-
var FRONTENDS, CONTENT_SOURCES, PACKAGE_MANAGERS, MODULES, FrontendSchema, ContentSourceSchema, PackageManagerSchema, ModuleSchema, ExperimentalSchema, ProjectNameSchema, WizardConfigSchema, wizardConfigJsonSchema = () => z.toJSONSchema(WizardConfigSchema), PRESETS, PRESET_CONFIGS;
|
|
47
|
+
var FRONTENDS, CONTENT_SOURCES, PACKAGE_MANAGERS, MODULES, MODULE_CATEGORIES, DEPLOY_TARGETS, FrontendSchema, ContentSourceSchema, PackageManagerSchema, ModuleSchema, DeployTargetSchema, ExperimentalSchema, ProjectNameSchema, WizardConfigSchema, wizardConfigJsonSchema = () => z.toJSONSchema(WizardConfigSchema), PRESETS, PRESET_CONFIGS;
|
|
24
48
|
var init_src = __esm(() => {
|
|
49
|
+
init_command();
|
|
25
50
|
FRONTENDS = ["astro", "next"];
|
|
26
51
|
CONTENT_SOURCES = ["sanity", "markdown", "none"];
|
|
27
52
|
PACKAGE_MANAGERS = ["pnpm", "bun", "npm"];
|
|
@@ -39,10 +64,13 @@ var init_src = __esm(() => {
|
|
|
39
64
|
"forms",
|
|
40
65
|
"llms"
|
|
41
66
|
];
|
|
67
|
+
MODULE_CATEGORIES = ["devtools", "integration", "ui", "cross-cutting"];
|
|
68
|
+
DEPLOY_TARGETS = ["node", "vercel"];
|
|
42
69
|
FrontendSchema = z.enum(FRONTENDS).describe("Web framework");
|
|
43
70
|
ContentSourceSchema = z.enum(CONTENT_SOURCES).describe("Content source");
|
|
44
71
|
PackageManagerSchema = z.enum(PACKAGE_MANAGERS).describe("Package manager");
|
|
45
72
|
ModuleSchema = z.enum(MODULES).describe("Opt-in module");
|
|
73
|
+
DeployTargetSchema = z.enum(DEPLOY_TARGETS).describe("Deploy target (Astro SSR adapter; ignored by Next)");
|
|
46
74
|
ExperimentalSchema = z.object({
|
|
47
75
|
cacheComponents: z.boolean().default(false),
|
|
48
76
|
reactCompiler: z.boolean().default(false)
|
|
@@ -53,6 +81,7 @@ var init_src = __esm(() => {
|
|
|
53
81
|
frontend: FrontendSchema,
|
|
54
82
|
content: ContentSourceSchema.default("none"),
|
|
55
83
|
packageManager: PackageManagerSchema.default("pnpm"),
|
|
84
|
+
deploy: DeployTargetSchema.default("node"),
|
|
56
85
|
modules: z.array(ModuleSchema).default([]),
|
|
57
86
|
experimental: ExperimentalSchema
|
|
58
87
|
});
|
|
@@ -72,7 +101,7 @@ var package_default;
|
|
|
72
101
|
var init_package = __esm(() => {
|
|
73
102
|
package_default = {
|
|
74
103
|
name: "create-turbo-wizard",
|
|
75
|
-
version: "0.
|
|
104
|
+
version: "0.2.0",
|
|
76
105
|
description: "Scaffold a Turborepo monorepo: Astro or Next.js, Sanity or Markdown, opt-in modules.",
|
|
77
106
|
keywords: [
|
|
78
107
|
"turborepo",
|
|
@@ -289,6 +318,7 @@ ${WORKSPACE_GLOBS.map((g) => ` - "${g}"`).join(`
|
|
|
289
318
|
"dist/",
|
|
290
319
|
".next/",
|
|
291
320
|
".astro/",
|
|
321
|
+
".sanity/",
|
|
292
322
|
".turbo/",
|
|
293
323
|
"*.tsbuildinfo",
|
|
294
324
|
"",
|
|
@@ -380,8 +410,7 @@ var init_biome = __esm(() => {
|
|
|
380
410
|
kind: "baseline",
|
|
381
411
|
templates: [{ path: "biome.jsonc", content: BIOME_CONFIG }],
|
|
382
412
|
scripts: { check: "biome check .", format: "biome format --write ." },
|
|
383
|
-
deps: [{ name: "@biomejs/biome", versionKey: "@biomejs/biome", target: "root", dev: true }]
|
|
384
|
-
owns: ["biome.jsonc"]
|
|
413
|
+
deps: [{ name: "@biomejs/biome", versionKey: "@biomejs/biome", target: "root", dev: true }]
|
|
385
414
|
};
|
|
386
415
|
});
|
|
387
416
|
|
|
@@ -426,8 +455,7 @@ var init_env = __esm(() => {
|
|
|
426
455
|
content: j({ extends: "@workspace/typescript-config/base.json", include: ["src"] })
|
|
427
456
|
},
|
|
428
457
|
{ path: "packages/env/src/index.ts", content: ENV_SRC }
|
|
429
|
-
]
|
|
430
|
-
owns: ["packages/env/package.json", "packages/env/tsconfig.json", "packages/env/src/index.ts"]
|
|
458
|
+
]
|
|
431
459
|
};
|
|
432
460
|
});
|
|
433
461
|
|
|
@@ -475,11 +503,6 @@ var init_logger = __esm(() => {
|
|
|
475
503
|
content: j2({ extends: "@workspace/typescript-config/base.json", include: ["src"] })
|
|
476
504
|
},
|
|
477
505
|
{ path: "packages/logger/src/index.ts", content: LOGGER_SRC }
|
|
478
|
-
],
|
|
479
|
-
owns: [
|
|
480
|
-
"packages/logger/package.json",
|
|
481
|
-
"packages/logger/tsconfig.json",
|
|
482
|
-
"packages/logger/src/index.ts"
|
|
483
506
|
]
|
|
484
507
|
};
|
|
485
508
|
});
|
|
@@ -543,12 +566,6 @@ var init_typescript_config = __esm(() => {
|
|
|
543
566
|
compilerOptions: { lib: ["DOM", "DOM.Iterable", "ES2023"], jsx: "react-jsx" }
|
|
544
567
|
})
|
|
545
568
|
}
|
|
546
|
-
],
|
|
547
|
-
owns: [
|
|
548
|
-
"packages/typescript-config/package.json",
|
|
549
|
-
"packages/typescript-config/base.json",
|
|
550
|
-
"packages/typescript-config/nextjs.json",
|
|
551
|
-
"packages/typescript-config/react-library.json"
|
|
552
569
|
]
|
|
553
570
|
};
|
|
554
571
|
});
|
|
@@ -616,7 +633,8 @@ function replaceNextMetadata(content, expr) {
|
|
|
616
633
|
}
|
|
617
634
|
|
|
618
635
|
// ../../packages/registry/src/content/markdown.ts
|
|
619
|
-
var
|
|
636
|
+
var j4 = (value) => `${JSON.stringify(value, null, 2)}
|
|
637
|
+
`, CONTENT_PACKAGE_JSON, NEXT_MDX_COMPONENTS = `import type { MDXComponents } from "mdx/types";
|
|
620
638
|
|
|
621
639
|
const components: MDXComponents = {
|
|
622
640
|
// Override built-ins here, e.g. h1: ({ children }) => <h1 className="...">{children}</h1>,
|
|
@@ -642,15 +660,26 @@ This post is written in **MDX** — Markdown with JSX.
|
|
|
642
660
|
- Mix Markdown prose with interactive components
|
|
643
661
|
- Full JSX expression support
|
|
644
662
|
- Server Components by default
|
|
663
|
+
`, NEXT_CONTENT_SCHEMA = `export interface PostMetadata {
|
|
664
|
+
title: string;
|
|
665
|
+
description?: string;
|
|
666
|
+
date?: string;
|
|
667
|
+
}
|
|
645
668
|
`, NEXT_BLOG_POSTS_TS = `import { readdir } from "node:fs/promises";
|
|
646
669
|
import path from "node:path";
|
|
670
|
+
import type { PostMetadata } from "@workspace/content/schema";
|
|
647
671
|
import type { ComponentType } from "react";
|
|
648
672
|
|
|
649
|
-
|
|
673
|
+
// cwd is apps/web for every next command turbo runs; the posts are two levels up in the
|
|
674
|
+
// workspace. In the default configuration this is read only at build time — the listing
|
|
675
|
+
// prerenders and generateStaticParams enumerates the slugs. With cacheComponents on, the
|
|
676
|
+
// PostList boundary below streams at request time and reads it then, exactly as it did
|
|
677
|
+
// when the posts lived in apps/web/content.
|
|
678
|
+
const POSTS_DIR = path.join(process.cwd(), "../../packages/content/blog");
|
|
650
679
|
|
|
651
680
|
export interface PostModule {
|
|
652
681
|
default: ComponentType;
|
|
653
|
-
metadata:
|
|
682
|
+
metadata: PostMetadata;
|
|
654
683
|
}
|
|
655
684
|
|
|
656
685
|
export async function getPostSlugs(): Promise<string[]> {
|
|
@@ -665,7 +694,7 @@ async function getPosts() {
|
|
|
665
694
|
const slugs = await getPostSlugs();
|
|
666
695
|
return Promise.all(
|
|
667
696
|
slugs.map(async (slug) => {
|
|
668
|
-
const { metadata } = (await import(
|
|
697
|
+
const { metadata } = (await import(\`@workspace/content/blog/\${slug}.mdx\`)) as PostModule;
|
|
669
698
|
return { slug, ...metadata };
|
|
670
699
|
}),
|
|
671
700
|
);
|
|
@@ -719,7 +748,7 @@ export default async function BlogPostPage({
|
|
|
719
748
|
params: Promise<{ slug: string }>;
|
|
720
749
|
}) {
|
|
721
750
|
const { slug } = await params;
|
|
722
|
-
const post = (await import(
|
|
751
|
+
const post = (await import(\`@workspace/content/blog/\${slug}.mdx\`).catch(() => null)) as PostModule | null;
|
|
723
752
|
if (!post) notFound();
|
|
724
753
|
|
|
725
754
|
const { default: Post, metadata } = post;
|
|
@@ -733,18 +762,23 @@ export default async function BlogPostPage({
|
|
|
733
762
|
</main>
|
|
734
763
|
);
|
|
735
764
|
}
|
|
736
|
-
`,
|
|
737
|
-
|
|
765
|
+
`, ASTRO_CONTENT_SCHEMA = `import { z } from "astro:content";
|
|
766
|
+
|
|
767
|
+
export const blogSchema = z.object({
|
|
768
|
+
title: z.string(),
|
|
769
|
+
description: z.string(),
|
|
770
|
+
pubDate: z.coerce.date(),
|
|
771
|
+
updatedDate: z.coerce.date().optional(),
|
|
772
|
+
});
|
|
773
|
+
`, ASTRO_CONTENT_CONFIG = `import { blogSchema } from "@workspace/content/schema";
|
|
774
|
+
import { glob } from "astro/loaders";
|
|
775
|
+
import { defineCollection } from "astro:content";
|
|
738
776
|
|
|
739
777
|
const blog = defineCollection({
|
|
740
|
-
// Pattern includes both .md and .mdx so the collection is mixed-format.
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
description: z.string(),
|
|
745
|
-
pubDate: z.coerce.date(),
|
|
746
|
-
updatedDate: z.coerce.date().optional(),
|
|
747
|
-
}),
|
|
778
|
+
// Pattern includes both .md and .mdx so the collection is mixed-format. \`base\` is
|
|
779
|
+
// resolved from the project root (apps/web), so it reaches out to the package.
|
|
780
|
+
loader: glob({ pattern: "**/*.{md,mdx}", base: "../../packages/content/blog" }),
|
|
781
|
+
schema: blogSchema,
|
|
748
782
|
});
|
|
749
783
|
|
|
750
784
|
export const collections = { blog };
|
|
@@ -822,6 +856,16 @@ const { Content } = await render(post);
|
|
|
822
856
|
</html>
|
|
823
857
|
`, markdown;
|
|
824
858
|
var init_markdown = __esm(() => {
|
|
859
|
+
CONTENT_PACKAGE_JSON = j4({
|
|
860
|
+
name: "@workspace/content",
|
|
861
|
+
version: "0.0.0",
|
|
862
|
+
private: true,
|
|
863
|
+
type: "module",
|
|
864
|
+
exports: {
|
|
865
|
+
"./schema": "./src/schema.ts",
|
|
866
|
+
"./blog/*": "./blog/*"
|
|
867
|
+
}
|
|
868
|
+
});
|
|
825
869
|
markdown = {
|
|
826
870
|
id: "markdown",
|
|
827
871
|
kind: "content",
|
|
@@ -829,21 +873,25 @@ var init_markdown = __esm(() => {
|
|
|
829
873
|
label: "MDX content",
|
|
830
874
|
hint: "Markdown + JSX wired into the frontend (official recipe)"
|
|
831
875
|
},
|
|
876
|
+
templates: [{ path: "packages/content/package.json", content: CONTENT_PACKAGE_JSON }],
|
|
832
877
|
fragments: {
|
|
833
878
|
next: [
|
|
879
|
+
{ path: "packages/content/src/schema.ts", content: NEXT_CONTENT_SCHEMA },
|
|
880
|
+
{ path: "packages/content/blog/hello.mdx", content: NEXT_BLOG_POST_MDX },
|
|
834
881
|
{ path: "apps/web/mdx-components.tsx", content: NEXT_MDX_COMPONENTS },
|
|
835
|
-
{ path: "apps/web/content/blog/hello.mdx", content: NEXT_BLOG_POST_MDX },
|
|
836
882
|
{ path: "apps/web/app/blog/posts.ts", content: NEXT_BLOG_POSTS_TS },
|
|
837
883
|
{ path: "apps/web/app/blog/page.tsx", content: NEXT_BLOG_INDEX_TSX },
|
|
838
884
|
{ path: "apps/web/app/blog/[slug]/page.tsx", content: NEXT_BLOG_SLUG_TSX }
|
|
839
885
|
],
|
|
840
886
|
astro: [
|
|
887
|
+
{ path: "packages/content/src/schema.ts", content: ASTRO_CONTENT_SCHEMA },
|
|
888
|
+
{ path: "packages/content/blog/hello.mdx", content: ASTRO_BLOG_POST },
|
|
841
889
|
{ path: "apps/web/src/content.config.ts", content: ASTRO_CONTENT_CONFIG },
|
|
842
|
-
{ path: "apps/web/src/content/blog/hello.mdx", content: ASTRO_BLOG_POST },
|
|
843
890
|
{ path: "apps/web/src/pages/blog/index.astro", content: ASTRO_BLOG_INDEX },
|
|
844
891
|
{ path: "apps/web/src/pages/blog/[...slug].astro", content: ASTRO_BLOG_SLUG_PAGE }
|
|
845
892
|
]
|
|
846
893
|
},
|
|
894
|
+
deps: [{ name: "@workspace/content", version: "workspace:*", target: "app" }],
|
|
847
895
|
frontendDeps: {
|
|
848
896
|
next: [
|
|
849
897
|
{ name: "@next/mdx", version: "^16.2.0", target: "app", dev: true },
|
|
@@ -853,17 +901,6 @@ var init_markdown = __esm(() => {
|
|
|
853
901
|
],
|
|
854
902
|
astro: [{ name: "@astrojs/mdx", version: "^4.3.14", target: "app" }]
|
|
855
903
|
},
|
|
856
|
-
owns: [
|
|
857
|
-
"apps/web/mdx-components.tsx",
|
|
858
|
-
"apps/web/content/blog/hello.mdx",
|
|
859
|
-
"apps/web/app/blog/posts.ts",
|
|
860
|
-
"apps/web/app/blog/page.tsx",
|
|
861
|
-
"apps/web/app/blog/[slug]/page.tsx",
|
|
862
|
-
"apps/web/src/content.config.ts",
|
|
863
|
-
"apps/web/src/content/blog/hello.mdx",
|
|
864
|
-
"apps/web/src/pages/blog/index.astro",
|
|
865
|
-
"apps/web/src/pages/blog/[...slug].astro"
|
|
866
|
-
],
|
|
867
904
|
patches: [
|
|
868
905
|
{
|
|
869
906
|
path: "apps/web/next.config.ts",
|
|
@@ -879,219 +916,393 @@ var init_markdown = __esm(() => {
|
|
|
879
916
|
};
|
|
880
917
|
});
|
|
881
918
|
|
|
882
|
-
// ../../packages/registry/src/content/sanity.ts
|
|
883
|
-
var
|
|
884
|
-
|
|
885
|
-
// Page-builder blocks are objects, not documents: they only exist inside a page.
|
|
886
|
-
|
|
887
|
-
export const hero = defineType({
|
|
888
|
-
name: "hero",
|
|
889
|
-
title: "Hero",
|
|
890
|
-
type: "object",
|
|
891
|
-
fields: [
|
|
892
|
-
defineField({ name: "heading", type: "string", validation: (rule) => rule.required() }),
|
|
893
|
-
defineField({ name: "subheading", type: "text", rows: 2 }),
|
|
894
|
-
defineField({
|
|
895
|
-
name: "image",
|
|
896
|
-
type: "image",
|
|
897
|
-
options: { hotspot: true },
|
|
898
|
-
fields: [defineField({ name: "alt", type: "string", title: "Alternative text" })],
|
|
899
|
-
}),
|
|
900
|
-
],
|
|
901
|
-
preview: {
|
|
902
|
-
select: { title: "heading", media: "image" },
|
|
903
|
-
prepare: ({ title, media }) => ({ title: title ?? "Hero", subtitle: "Hero", media }),
|
|
904
|
-
},
|
|
905
|
-
});
|
|
906
|
-
|
|
907
|
-
export const richText = defineType({
|
|
908
|
-
name: "richText",
|
|
909
|
-
title: "Rich text",
|
|
910
|
-
type: "object",
|
|
911
|
-
fields: [
|
|
912
|
-
defineField({ name: "heading", type: "string" }),
|
|
913
|
-
defineField({ name: "content", type: "array", of: [defineArrayMember({ type: "block" })] }),
|
|
914
|
-
],
|
|
915
|
-
preview: {
|
|
916
|
-
select: { title: "heading" },
|
|
917
|
-
prepare: ({ title }) => ({ title: title ?? "Rich text", subtitle: "Rich text" }),
|
|
918
|
-
},
|
|
919
|
-
});
|
|
919
|
+
// ../../packages/registry/src/content/sanity/astro.ts
|
|
920
|
+
var ASTRO_ENV = `export const projectId = import.meta.env.PUBLIC_SANITY_PROJECT_ID || "placeholder";
|
|
920
921
|
|
|
921
|
-
export const
|
|
922
|
-
name: "cta",
|
|
923
|
-
title: "Call to action",
|
|
924
|
-
type: "object",
|
|
925
|
-
fields: [
|
|
926
|
-
defineField({ name: "heading", type: "string", validation: (rule) => rule.required() }),
|
|
927
|
-
defineField({ name: "text", type: "text", rows: 2 }),
|
|
928
|
-
defineField({ name: "buttonLabel", type: "string" }),
|
|
929
|
-
defineField({ name: "buttonHref", type: "string" }),
|
|
930
|
-
],
|
|
931
|
-
preview: {
|
|
932
|
-
select: { title: "heading" },
|
|
933
|
-
prepare: ({ title }) => ({ title: title ?? "Call to action", subtitle: "Call to action" }),
|
|
934
|
-
},
|
|
935
|
-
});
|
|
936
|
-
`, SCHEMA_PAGE = `import { defineArrayMember, defineField, defineType } from "sanity";
|
|
922
|
+
export const dataset = import.meta.env.PUBLIC_SANITY_DATASET || "production";
|
|
937
923
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
title: "Page",
|
|
941
|
-
type: "document",
|
|
942
|
-
fields: [
|
|
943
|
-
defineField({ name: "title", type: "string", validation: (rule) => rule.required() }),
|
|
944
|
-
defineField({
|
|
945
|
-
name: "slug",
|
|
946
|
-
type: "slug",
|
|
947
|
-
options: { source: "title" },
|
|
948
|
-
validation: (rule) => rule.required(),
|
|
949
|
-
}),
|
|
950
|
-
defineField({ name: "description", type: "text", rows: 3 }),
|
|
951
|
-
defineField({
|
|
952
|
-
name: "pageBuilder",
|
|
953
|
-
title: "Page builder",
|
|
954
|
-
type: "array",
|
|
955
|
-
of: [
|
|
956
|
-
defineArrayMember({ type: "hero" }),
|
|
957
|
-
defineArrayMember({ type: "richText" }),
|
|
958
|
-
defineArrayMember({ type: "cta" }),
|
|
959
|
-
],
|
|
960
|
-
}),
|
|
961
|
-
],
|
|
962
|
-
// The home page is the \`page\` whose slug is "home" — see sanity/queries.ts.
|
|
963
|
-
preview: { select: { title: "title", subtitle: "slug.current" } },
|
|
964
|
-
});
|
|
965
|
-
`, SCHEMA_SETTINGS = `import { defineField, defineType } from "sanity";
|
|
924
|
+
/** Server-only read token; required (and only read) when Visual Editing is enabled. */
|
|
925
|
+
export const readToken = import.meta.env.SANITY_API_READ_TOKEN ?? "";
|
|
966
926
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
defineField({ name: "siteDescription", type: "text", rows: 3 }),
|
|
974
|
-
defineField({
|
|
975
|
-
name: "socialImage",
|
|
976
|
-
title: "Social share image",
|
|
977
|
-
type: "image",
|
|
978
|
-
options: { hotspot: true },
|
|
979
|
-
}),
|
|
980
|
-
],
|
|
981
|
-
preview: { select: { title: "siteTitle", media: "socialImage" } },
|
|
982
|
-
});
|
|
983
|
-
`, SCHEMA_INDEX = `import type { SchemaTypeDefinition } from "sanity";
|
|
984
|
-
import { cta, hero, richText } from "./blocks";
|
|
985
|
-
import { page } from "./page";
|
|
986
|
-
import { settings } from "./settings";
|
|
927
|
+
/**
|
|
928
|
+
* The @sanity/astro Visual Editing switch. Off by default: overlays need a rendering
|
|
929
|
+
* server (\`astro dev\`, or an SSR adapter in production) plus SANITY_API_READ_TOKEN.
|
|
930
|
+
*/
|
|
931
|
+
export const visualEditingEnabled =
|
|
932
|
+
import.meta.env.PUBLIC_SANITY_VISUAL_EDITING_ENABLED === "true";
|
|
987
933
|
|
|
988
|
-
|
|
989
|
-
|
|
934
|
+
/**
|
|
935
|
+
* False on a freshly scaffolded project. Guards every fetch so \`astro build\` never dials
|
|
936
|
+
* placeholder.api.sanity.io and dies; pages render an empty state instead.
|
|
937
|
+
*/
|
|
938
|
+
export const hasSanityCredentials = projectId !== "placeholder";
|
|
939
|
+
`, ASTRO_FETCH = `import { sanityClient } from "sanity:client";
|
|
940
|
+
import { hasSanityCredentials, readToken, visualEditingEnabled } from "./env";
|
|
990
941
|
|
|
991
942
|
/**
|
|
992
|
-
* \`
|
|
993
|
-
*
|
|
943
|
+
* \`sanity:client\` is a Vite virtual module and this file lives in a linked workspace
|
|
944
|
+
* package, not in apps/web. Both halves of that were measured before the move: the
|
|
945
|
+
* integration's \`resolveId\` fires for any importer in the graph, and Vite still applies
|
|
946
|
+
* its \`import.meta.env\` substitution inside the package (a pnpm workspace link resolves
|
|
947
|
+
* to a real path outside node_modules, so it is treated as source, not as an external
|
|
948
|
+
* dependency). Without the second half every read would silently short-circuit on a
|
|
949
|
+
* "placeholder" project id and every route would still answer 200.
|
|
994
950
|
*
|
|
995
|
-
* The
|
|
996
|
-
*
|
|
951
|
+
* The single read path, following @sanity/astro's Visual Editing recipe: with
|
|
952
|
+
* PUBLIC_SANITY_VISUAL_EDITING_ENABLED=true it fetches the drafts perspective with
|
|
953
|
+
* stega source maps, which is what gives Presentation its click-to-edit overlays.
|
|
954
|
+
*
|
|
955
|
+
* Without credentials it returns null before touching the network, so \`astro build\`
|
|
956
|
+
* succeeds on a fresh scaffold. The catch covers a configured-but-unreachable dataset
|
|
957
|
+
* for the same reason: a starter should render empty, not crash.
|
|
997
958
|
*/
|
|
998
|
-
export
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
locations: [{ title: "Home", href: "/" }],
|
|
1026
|
-
message: "This document is used on all pages",
|
|
1027
|
-
tone: "positive",
|
|
1028
|
-
}),
|
|
1029
|
-
page: defineLocations({
|
|
1030
|
-
select: { title: "title", slug: "slug.current" },
|
|
1031
|
-
resolve: (doc) => ({
|
|
1032
|
-
locations: [
|
|
1033
|
-
{
|
|
1034
|
-
title: doc?.title || "Untitled",
|
|
1035
|
-
href: doc?.slug === "home" ? "/" : \`/\${doc?.slug}\`,
|
|
1036
|
-
},
|
|
1037
|
-
],
|
|
1038
|
-
}),
|
|
1039
|
-
}),
|
|
1040
|
-
};
|
|
1041
|
-
`, DOCUMENT_GUARD = ` document: {
|
|
1042
|
-
newDocumentOptions: (prev, { creationContext }) =>
|
|
1043
|
-
creationContext.type === "global"
|
|
1044
|
-
? prev.filter((template) => !singletonTypes.has(template.templateId))
|
|
1045
|
-
: prev,
|
|
1046
|
-
actions: (prev, { schemaType }) =>
|
|
1047
|
-
singletonTypes.has(schemaType)
|
|
1048
|
-
? prev.filter(({ action }) => action && singletonActions.has(action))
|
|
1049
|
-
: prev,
|
|
1050
|
-
},`, QUERIES = `import { defineQuery } from "groq";
|
|
1051
|
-
|
|
1052
|
-
const PAGE_FIELDS = /* groq */ \`
|
|
1053
|
-
_id,
|
|
1054
|
-
_type,
|
|
1055
|
-
title,
|
|
1056
|
-
description,
|
|
1057
|
-
"slug": slug.current,
|
|
1058
|
-
pageBuilder[]{ ... }
|
|
1059
|
-
\`;
|
|
1060
|
-
|
|
1061
|
-
export const HOME_PAGE_QUERY = defineQuery(
|
|
1062
|
-
\`*[_type == "page" && slug.current == "home"][0]{\${PAGE_FIELDS}}\`,
|
|
1063
|
-
);
|
|
959
|
+
export async function loadQuery<T>(
|
|
960
|
+
query: string,
|
|
961
|
+
params: Record<string, unknown> = {},
|
|
962
|
+
): Promise<T | null> {
|
|
963
|
+
if (!hasSanityCredentials) return null;
|
|
964
|
+
if (visualEditingEnabled && !readToken) {
|
|
965
|
+
throw new Error(
|
|
966
|
+
"SANITY_API_READ_TOKEN is required when PUBLIC_SANITY_VISUAL_EDITING_ENABLED is true.",
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
try {
|
|
970
|
+
const { result } = await sanityClient.fetch<T>(query, params, {
|
|
971
|
+
filterResponse: false,
|
|
972
|
+
perspective: visualEditingEnabled ? "drafts" : "published",
|
|
973
|
+
resultSourceMap: visualEditingEnabled ? "withKeyArraySelector" : false,
|
|
974
|
+
stega: visualEditingEnabled,
|
|
975
|
+
...(visualEditingEnabled ? { token: readToken } : {}),
|
|
976
|
+
useCdn: !visualEditingEnabled,
|
|
977
|
+
});
|
|
978
|
+
return result;
|
|
979
|
+
} catch (error) {
|
|
980
|
+
console.warn("[sanity] query failed:", error);
|
|
981
|
+
return null;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
`, ASTRO_IMAGE = `import { createImageUrlBuilder } from "@sanity/image-url";
|
|
985
|
+
import { dataset, projectId } from "./env";
|
|
1064
986
|
|
|
1065
|
-
|
|
1066
|
-
\`*[_type == "page" && slug.current == $slug][0]{\${PAGE_FIELDS}}\`,
|
|
1067
|
-
);
|
|
987
|
+
const builder = createImageUrlBuilder({ projectId, dataset });
|
|
1068
988
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
);
|
|
989
|
+
/** Build a CDN URL for a Sanity image; null when the block has no asset. */
|
|
990
|
+
export function urlFor(source: unknown, width = 1200): string | null {
|
|
991
|
+
const ref = (source as { asset?: { _ref?: string } } | null)?.asset?._ref;
|
|
992
|
+
if (!ref) return null;
|
|
993
|
+
return builder.image(ref).width(width).auto("format").url();
|
|
994
|
+
}
|
|
995
|
+
`, ASTRO_PAGE_BUILDER = `---
|
|
996
|
+
import { urlFor } from "@workspace/sanity/image";
|
|
997
|
+
import type { PAGE_QUERY_RESULT } from "@workspace/sanity/types";
|
|
998
|
+
import { PortableText } from "astro-portabletext";
|
|
1072
999
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
{"_id":"page-home","_type":"page","title":"Turbo Wizard","description":"The home page, served from Sanity.","slug":{"_type":"slug","current":"home"},"pageBuilder":[{"_key":"hero1","_type":"hero","heading":"Content from Sanity, rendered by your app","subheading":"This page lives in the Studio. Edit it there and watch it change here."},{"_key":"rich1","_type":"richText","heading":"How this fits together","content":[{"_key":"b1","_type":"block","style":"normal","markDefs":[],"children":[{"_key":"s1","_type":"span","marks":[],"text":"The schema lives in apps/web/sanity/schema. Queries live in apps/web/sanity/queries.ts and run through typegen, so results are typed. The page builder renders each block by its _type."}]}]},{"_key":"cta1","_type":"cta","heading":"Open the Studio","text":"Edit this page and reload.","buttonLabel":"Open Studio","buttonHref":"/studio"}]}
|
|
1078
|
-
{"_id":"page-about","_type":"page","title":"About","description":"A second page, to show routing by slug.","slug":{"_type":"slug","current":"about"},"pageBuilder":[{"_key":"hero2","_type":"hero","heading":"About this starter","subheading":"Pages are routed by their slug."},{"_key":"rich2","_type":"richText","content":[{"_key":"b2","_type":"block","style":"normal","markDefs":[],"children":[{"_key":"s2","_type":"span","marks":[],"text":"Add a page in the Studio, give it a slug, and it appears at that path."}]}]}]}
|
|
1079
|
-
`, NEXT_ENV = `export const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || "placeholder";
|
|
1000
|
+
// Derived from the generated query result rather than hand-written: the page builder's
|
|
1001
|
+
// shape IS what the projection returns, so typegen is the only source that cannot drift.
|
|
1002
|
+
type PageBlock = NonNullable<NonNullable<PAGE_QUERY_RESULT>["pageBuilder"]>[number];
|
|
1003
|
+
type ButtonLink = NonNullable<Extract<PageBlock, { _type: "hero" }>["buttons"]>[number];
|
|
1080
1004
|
|
|
1081
|
-
|
|
1005
|
+
interface Props {
|
|
1006
|
+
blocks: PageBlock[];
|
|
1007
|
+
}
|
|
1082
1008
|
|
|
1083
|
-
|
|
1084
|
-
// project was set up; bump deliberately, never to "today").
|
|
1085
|
-
export const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || "2026-02-01";
|
|
1009
|
+
const { blocks } = Astro.props;
|
|
1086
1010
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1011
|
+
const section = "margin: 3rem 0;";
|
|
1012
|
+
const grid = "display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));";
|
|
1013
|
+
const card = "border: 1px solid #e5e5e5; border-radius: 0.75rem; padding: 1.25rem;";
|
|
1089
1014
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1015
|
+
const buttonProps = (button: ButtonLink) =>
|
|
1016
|
+
button.openInNewTab ? { target: "_blank", rel: "noreferrer" } : {};
|
|
1017
|
+
---
|
|
1092
1018
|
|
|
1093
|
-
|
|
1094
|
-
|
|
1019
|
+
{
|
|
1020
|
+
blocks.map((block) => {
|
|
1021
|
+
if (block._type === "hero") {
|
|
1022
|
+
const src = urlFor(block.image, 1600);
|
|
1023
|
+
return (
|
|
1024
|
+
<section style={section}>
|
|
1025
|
+
{block.badge && <p style="opacity: 0.6; margin: 0;">{block.badge}</p>}
|
|
1026
|
+
<h2>{block.title}</h2>
|
|
1027
|
+
{block.richText && <PortableText value={block.richText} />}
|
|
1028
|
+
{!!block.buttons?.length && (
|
|
1029
|
+
<p style="display: flex; gap: 0.75rem; flex-wrap: wrap;">
|
|
1030
|
+
{block.buttons.map(
|
|
1031
|
+
(button) =>
|
|
1032
|
+
button.href && (
|
|
1033
|
+
<a href={button.href} {...buttonProps(button)}>
|
|
1034
|
+
{button.text ?? button.href}
|
|
1035
|
+
</a>
|
|
1036
|
+
),
|
|
1037
|
+
)}
|
|
1038
|
+
</p>
|
|
1039
|
+
)}
|
|
1040
|
+
{src && <img src={src} alt={block.image?.alt ?? ""} />}
|
|
1041
|
+
</section>
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
if (block._type === "richTextBlock") {
|
|
1045
|
+
return (
|
|
1046
|
+
<section style={section}>
|
|
1047
|
+
{block.eyebrow && <p style="opacity: 0.6; margin: 0;">{block.eyebrow}</p>}
|
|
1048
|
+
{block.title && <h2>{block.title}</h2>}
|
|
1049
|
+
{block.richText && <PortableText value={block.richText} />}
|
|
1050
|
+
</section>
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
if (block._type === "featureCardsIcon") {
|
|
1054
|
+
return (
|
|
1055
|
+
<section style={section}>
|
|
1056
|
+
{block.eyebrow && <p style="opacity: 0.6; margin: 0;">{block.eyebrow}</p>}
|
|
1057
|
+
{block.title && <h2>{block.title}</h2>}
|
|
1058
|
+
{block.richText && <PortableText value={block.richText} />}
|
|
1059
|
+
<div style={grid}>
|
|
1060
|
+
{(block.cards ?? []).map((item) => (
|
|
1061
|
+
<div style={card}>
|
|
1062
|
+
{/* \`icon\` holds a Lucide icon name; render it with your icon set of choice. */}
|
|
1063
|
+
{item.icon && <p style="opacity: 0.6; margin: 0;">{item.icon}</p>}
|
|
1064
|
+
<h3>{item.title}</h3>
|
|
1065
|
+
{item.richText && <PortableText value={item.richText} />}
|
|
1066
|
+
</div>
|
|
1067
|
+
))}
|
|
1068
|
+
</div>
|
|
1069
|
+
</section>
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
if (block._type === "imageLinkCards") {
|
|
1073
|
+
return (
|
|
1074
|
+
<section style={section}>
|
|
1075
|
+
{block.eyebrow && <p style="opacity: 0.6; margin: 0;">{block.eyebrow}</p>}
|
|
1076
|
+
{block.title && <h2>{block.title}</h2>}
|
|
1077
|
+
{block.richText && <PortableText value={block.richText} />}
|
|
1078
|
+
<div style={grid}>
|
|
1079
|
+
{(block.cards ?? []).map((item) => {
|
|
1080
|
+
const src = urlFor(item.image, 800);
|
|
1081
|
+
return (
|
|
1082
|
+
<div style={card}>
|
|
1083
|
+
{src && <img src={src} alt={item.image?.alt ?? ""} />}
|
|
1084
|
+
<h3>
|
|
1085
|
+
{item.href ? (
|
|
1086
|
+
<a
|
|
1087
|
+
href={item.href}
|
|
1088
|
+
{...(item.openInNewTab ? { target: "_blank", rel: "noreferrer" } : {})}
|
|
1089
|
+
>
|
|
1090
|
+
{item.title}
|
|
1091
|
+
</a>
|
|
1092
|
+
) : (
|
|
1093
|
+
item.title
|
|
1094
|
+
)}
|
|
1095
|
+
</h3>
|
|
1096
|
+
{item.description && <p>{item.description}</p>}
|
|
1097
|
+
</div>
|
|
1098
|
+
);
|
|
1099
|
+
})}
|
|
1100
|
+
</div>
|
|
1101
|
+
</section>
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
if (block._type === "faqAccordion") {
|
|
1105
|
+
return (
|
|
1106
|
+
<section style={section}>
|
|
1107
|
+
{block.eyebrow && <p style="opacity: 0.6; margin: 0;">{block.eyebrow}</p>}
|
|
1108
|
+
{block.title && <h2>{block.title}</h2>}
|
|
1109
|
+
{block.subtitle && <p>{block.subtitle}</p>}
|
|
1110
|
+
{(block.faqs ?? []).map((faq) => (
|
|
1111
|
+
<details style="border-top: 1px solid #e5e5e5; padding: 0.75rem 0;">
|
|
1112
|
+
<summary>{faq.title}</summary>
|
|
1113
|
+
{faq.richText && <PortableText value={faq.richText} />}
|
|
1114
|
+
</details>
|
|
1115
|
+
))}
|
|
1116
|
+
</section>
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
if (block._type === "subscribeNewsletter") {
|
|
1120
|
+
// Presentational only: no action, no handler. Wire it to the \`forms\` module's
|
|
1121
|
+
// endpoint (or your own) when you want it to do something.
|
|
1122
|
+
return (
|
|
1123
|
+
<section style={section}>
|
|
1124
|
+
<h2>{block.title}</h2>
|
|
1125
|
+
{block.subTitle && <PortableText value={block.subTitle} />}
|
|
1126
|
+
<form style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
|
1127
|
+
<input type="email" name="email" placeholder="you@example.com" required />
|
|
1128
|
+
<button type="submit">Subscribe</button>
|
|
1129
|
+
</form>
|
|
1130
|
+
{block.helperText && <PortableText value={block.helperText} />}
|
|
1131
|
+
</section>
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
if (block._type === "cta") {
|
|
1135
|
+
return (
|
|
1136
|
+
<section style={section}>
|
|
1137
|
+
{block.eyebrow && <p style="opacity: 0.6; margin: 0;">{block.eyebrow}</p>}
|
|
1138
|
+
<h2>{block.title}</h2>
|
|
1139
|
+
{block.richText && <PortableText value={block.richText} />}
|
|
1140
|
+
{!!block.buttons?.length && (
|
|
1141
|
+
<p style="display: flex; gap: 0.75rem; flex-wrap: wrap;">
|
|
1142
|
+
{block.buttons.map(
|
|
1143
|
+
(button) =>
|
|
1144
|
+
button.href && (
|
|
1145
|
+
<a href={button.href} {...buttonProps(button)}>
|
|
1146
|
+
{button.text ?? button.href}
|
|
1147
|
+
</a>
|
|
1148
|
+
),
|
|
1149
|
+
)}
|
|
1150
|
+
</p>
|
|
1151
|
+
)}
|
|
1152
|
+
</section>
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
return null;
|
|
1156
|
+
})
|
|
1157
|
+
}
|
|
1158
|
+
`, ASTRO_SLUG_PAGE = `---
|
|
1159
|
+
import { VisualEditing } from "@sanity/astro/visual-editing";
|
|
1160
|
+
import { visualEditingEnabled } from "@workspace/sanity/env";
|
|
1161
|
+
import { loadQuery } from "@workspace/sanity/fetch";
|
|
1162
|
+
import { PAGE_QUERY, PAGE_SLUGS_QUERY } from "@workspace/sanity/queries";
|
|
1163
|
+
import type { PAGE_QUERY_RESULT } from "@workspace/sanity/types";
|
|
1164
|
+
import PageBuilder from "@workspace/sanity-blocks/PageBuilder.astro";
|
|
1165
|
+
|
|
1166
|
+
export async function getStaticPaths() {
|
|
1167
|
+
// Returns [] without credentials, so a fresh scaffold builds with no CMS pages.
|
|
1168
|
+
const slugs = await loadQuery<(string | null)[]>(PAGE_SLUGS_QUERY);
|
|
1169
|
+
return (slugs ?? []).filter(Boolean).map((slug) => ({ params: { slug } }));
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
const page = await loadQuery<PAGE_QUERY_RESULT>(PAGE_QUERY, { slug: Astro.params.slug });
|
|
1173
|
+
---
|
|
1174
|
+
|
|
1175
|
+
<html lang="en">
|
|
1176
|
+
<head>
|
|
1177
|
+
<meta charset="utf-8" />
|
|
1178
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1179
|
+
<title>{page?.title ?? "web"}</title>
|
|
1180
|
+
</head>
|
|
1181
|
+
<body style="font-family: system-ui, sans-serif; padding: 3rem;">
|
|
1182
|
+
<h1>{page?.title}</h1>
|
|
1183
|
+
<PageBuilder blocks={page?.pageBuilder ?? []} />
|
|
1184
|
+
<VisualEditing enabled={visualEditingEnabled} />
|
|
1185
|
+
</body>
|
|
1186
|
+
</html>
|
|
1187
|
+
`, ASTRO_BLOG_INDEX_PAGE = `---
|
|
1188
|
+
import { VisualEditing } from "@sanity/astro/visual-editing";
|
|
1189
|
+
import { visualEditingEnabled } from "@workspace/sanity/env";
|
|
1190
|
+
import { loadQuery } from "@workspace/sanity/fetch";
|
|
1191
|
+
import { urlFor } from "@workspace/sanity/image";
|
|
1192
|
+
import { BLOG_INDEX_QUERY } from "@workspace/sanity/queries";
|
|
1193
|
+
import type { BLOG_INDEX_QUERY_RESULT } from "@workspace/sanity/types";
|
|
1194
|
+
|
|
1195
|
+
const posts = (await loadQuery<BLOG_INDEX_QUERY_RESULT>(BLOG_INDEX_QUERY)) ?? [];
|
|
1196
|
+
---
|
|
1197
|
+
|
|
1198
|
+
<html lang="en">
|
|
1199
|
+
<head>
|
|
1200
|
+
<meta charset="utf-8" />
|
|
1201
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1202
|
+
<title>Blog</title>
|
|
1203
|
+
</head>
|
|
1204
|
+
<body style="font-family: system-ui, sans-serif; padding: 3rem;">
|
|
1205
|
+
<h1>Blog</h1>
|
|
1206
|
+
{
|
|
1207
|
+
posts.length === 0 && (
|
|
1208
|
+
<p>
|
|
1209
|
+
No posts yet — seed the dataset with <code>pnpm sanity:seed</code> from apps/studio,
|
|
1210
|
+
or write one in the Studio (<code>pnpm dev:studio</code>).
|
|
1211
|
+
</p>
|
|
1212
|
+
)
|
|
1213
|
+
}
|
|
1214
|
+
{
|
|
1215
|
+
posts.map((post) => {
|
|
1216
|
+
const src = urlFor(post.image, 800);
|
|
1217
|
+
const author = post.authors?.[0];
|
|
1218
|
+
return (
|
|
1219
|
+
<article style="border-top: 1px solid #e5e5e5; padding: 1.5rem 0;">
|
|
1220
|
+
{src && <img src={src} alt={post.image?.alt ?? ""} />}
|
|
1221
|
+
<h2>
|
|
1222
|
+
<a href={\`/blog/\${post.slug}\`}>{post.title}</a>
|
|
1223
|
+
</h2>
|
|
1224
|
+
<p style="opacity: 0.6;">
|
|
1225
|
+
{author?.name}
|
|
1226
|
+
{author?.name && post.publishedAt && " · "}
|
|
1227
|
+
{post.publishedAt && <time datetime={post.publishedAt}>{post.publishedAt}</time>}
|
|
1228
|
+
</p>
|
|
1229
|
+
{post.description && <p>{post.description}</p>}
|
|
1230
|
+
</article>
|
|
1231
|
+
);
|
|
1232
|
+
})
|
|
1233
|
+
}
|
|
1234
|
+
<VisualEditing enabled={visualEditingEnabled} />
|
|
1235
|
+
</body>
|
|
1236
|
+
</html>
|
|
1237
|
+
`, ASTRO_BLOG_POST_PAGE = `---
|
|
1238
|
+
import { VisualEditing } from "@sanity/astro/visual-editing";
|
|
1239
|
+
import { visualEditingEnabled } from "@workspace/sanity/env";
|
|
1240
|
+
import { loadQuery } from "@workspace/sanity/fetch";
|
|
1241
|
+
import { urlFor } from "@workspace/sanity/image";
|
|
1242
|
+
import { BLOG_QUERY, BLOG_SLUGS_QUERY } from "@workspace/sanity/queries";
|
|
1243
|
+
import type { BLOG_QUERY_RESULT } from "@workspace/sanity/types";
|
|
1244
|
+
import { PortableText } from "astro-portabletext";
|
|
1245
|
+
|
|
1246
|
+
export async function getStaticPaths() {
|
|
1247
|
+
// Returns [] without credentials, so a fresh scaffold builds with no posts.
|
|
1248
|
+
const slugs = await loadQuery<(string | null)[]>(BLOG_SLUGS_QUERY);
|
|
1249
|
+
return (slugs ?? []).filter(Boolean).map((slug) => ({ params: { slug } }));
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
const post = await loadQuery<BLOG_QUERY_RESULT>(BLOG_QUERY, { slug: Astro.params.slug });
|
|
1253
|
+
const src = urlFor(post?.image, 1600);
|
|
1254
|
+
const author = post?.authors?.[0];
|
|
1255
|
+
---
|
|
1256
|
+
|
|
1257
|
+
<html lang="en">
|
|
1258
|
+
<head>
|
|
1259
|
+
<meta charset="utf-8" />
|
|
1260
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1261
|
+
<title>{post?.title ?? "Blog"}</title>
|
|
1262
|
+
{post?.description && <meta name="description" content={post.description} />}
|
|
1263
|
+
</head>
|
|
1264
|
+
<body style="font-family: system-ui, sans-serif; padding: 3rem;">
|
|
1265
|
+
<p><a href="/blog">← Blog</a></p>
|
|
1266
|
+
<h1>{post?.title}</h1>
|
|
1267
|
+
<p style="opacity: 0.6;">
|
|
1268
|
+
{author?.name && (author.position ? \`\${author.name}, \${author.position}\` : author.name)}
|
|
1269
|
+
{author?.name && post?.publishedAt && " · "}
|
|
1270
|
+
{post?.publishedAt && <time datetime={post.publishedAt}>{post.publishedAt}</time>}
|
|
1271
|
+
</p>
|
|
1272
|
+
{src && <img src={src} alt={post?.image?.alt ?? ""} />}
|
|
1273
|
+
{post?.richText && <PortableText value={post.richText} />}
|
|
1274
|
+
<VisualEditing enabled={visualEditingEnabled} />
|
|
1275
|
+
</body>
|
|
1276
|
+
</html>
|
|
1277
|
+
`, 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.
|
|
1278
|
+
try {
|
|
1279
|
+
process.loadEnvFile(".env");
|
|
1280
|
+
} catch {
|
|
1281
|
+
// No .env — the placeholder ids below keep the build working.
|
|
1282
|
+
}
|
|
1283
|
+
`;
|
|
1284
|
+
|
|
1285
|
+
// ../../packages/registry/src/content/sanity/next.ts
|
|
1286
|
+
var NEXT_ENV = `export const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || "placeholder";
|
|
1287
|
+
|
|
1288
|
+
export const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET || "production";
|
|
1289
|
+
|
|
1290
|
+
// A recent pinned date, per Sanity's API-versioning guidance (the docs pin the date the
|
|
1291
|
+
// project was set up; bump deliberately, never to "today").
|
|
1292
|
+
export const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || "2026-02-01";
|
|
1293
|
+
|
|
1294
|
+
/** Server-only read token; empty when unset (draft mode and live previews need it). */
|
|
1295
|
+
export const readToken = process.env.SANITY_API_READ_TOKEN ?? "";
|
|
1296
|
+
|
|
1297
|
+
/**
|
|
1298
|
+
* Where the Studio lives — stega encodes it into every string it returns, so a
|
|
1299
|
+
* Visual Editing click-through lands on the right field. Absolute since the Studio
|
|
1300
|
+
* became its own app (\`sanity dev\` defaults to :3333).
|
|
1301
|
+
*/
|
|
1302
|
+
export const studioUrl = process.env.NEXT_PUBLIC_SANITY_STUDIO_URL || "http://localhost:3333";
|
|
1303
|
+
|
|
1304
|
+
/**
|
|
1305
|
+
* False on a freshly scaffolded project. Guards every fetch so the build never dials
|
|
1095
1306
|
* placeholder.api.sanity.io and dies; pages render an empty state instead.
|
|
1096
1307
|
*/
|
|
1097
1308
|
export const hasSanityCredentials = projectId !== "placeholder";
|
|
@@ -1109,17 +1320,33 @@ export const client = createClient({
|
|
|
1109
1320
|
stega: { studioUrl },
|
|
1110
1321
|
});
|
|
1111
1322
|
`, NEXT_LIVE = `import { defineLive } from "next-sanity/live";
|
|
1112
|
-
import { readToken } from "./env";
|
|
1323
|
+
import { hasSanityCredentials, readToken } from "./env";
|
|
1113
1324
|
import { client } from "./client";
|
|
1114
1325
|
|
|
1115
1326
|
// defineLive gives us live content plus draft-mode reads. The token is passed as
|
|
1116
1327
|
// undefined rather than "" when unset — an empty string reads as "provided but invalid".
|
|
1117
|
-
export const { sanityFetch, SanityLive } = defineLive({
|
|
1328
|
+
export const { sanityFetch, SanityLive: LiveComponent } = defineLive({
|
|
1118
1329
|
client,
|
|
1119
1330
|
serverToken: readToken || undefined,
|
|
1120
1331
|
browserToken: readToken || undefined,
|
|
1121
1332
|
});
|
|
1122
|
-
|
|
1333
|
+
|
|
1334
|
+
/**
|
|
1335
|
+
* On a fresh scaffold \`projectId\` is "placeholder", so the live socket dials
|
|
1336
|
+
* placeholder.api.sanity.io and next-sanity retries forever — the dev log fills with
|
|
1337
|
+
* "<SanityLive> is attempting to reconnect" and nothing else is readable. Render
|
|
1338
|
+
* nothing until there are credentials, the same guard \`loadQuery\` applies to reads.
|
|
1339
|
+
*/
|
|
1340
|
+
export const SanityLive = hasSanityCredentials ? LiveComponent : () => null;
|
|
1341
|
+
`, NEXT_FETCH = `/// <reference path="./sanity.types.ts" />
|
|
1342
|
+
// ^ \`sanity typegen generate\` writes sanity.types.ts here (see apps/studio/sanity.cli.ts) and
|
|
1343
|
+
// its \`declare module "@sanity/client"\` block is what types every query result. Now that
|
|
1344
|
+
// the file lives outside apps/web it is no longer swept up by the app's tsconfig include,
|
|
1345
|
+
// so this reference is what pulls it into the program — without it every loadQuery result
|
|
1346
|
+
// silently degrades to \`unknown\` and the pages stop type-checking. A path reference cannot
|
|
1347
|
+
// be elided the way an unused import can. The file ships pre-generated; regenerate it with
|
|
1348
|
+
// \`extract\` + \`type\` from apps/studio.
|
|
1349
|
+
import type { QueryParams } from "next-sanity";
|
|
1123
1350
|
import { client } from "./client";
|
|
1124
1351
|
import { hasSanityCredentials } from "./env";
|
|
1125
1352
|
import { sanityFetch } from "./live";
|
|
@@ -1173,52 +1400,234 @@ export function urlFor(source: unknown, width = 1200): string | null {
|
|
|
1173
1400
|
return builder.image(ref).width(width).auto("format").url();
|
|
1174
1401
|
}
|
|
1175
1402
|
`, NEXT_PAGE_BUILDER = `import { PortableText, type PortableTextProps } from "@portabletext/react";
|
|
1403
|
+
import { urlFor } from "@workspace/sanity/image";
|
|
1176
1404
|
import Image from "next/image";
|
|
1177
|
-
import { urlFor } from "./image";
|
|
1178
1405
|
|
|
1179
1406
|
type SanityImage = { asset?: { _ref?: string } | null; alt?: string | null } | null;
|
|
1407
|
+
type Rich = PortableTextProps["value"] | null;
|
|
1408
|
+
|
|
1409
|
+
/** queries.ts flattens every \`customUrl\` to an href, so nothing here branches on link type. */
|
|
1410
|
+
type ButtonLink = {
|
|
1411
|
+
_key: string;
|
|
1412
|
+
text?: string | null;
|
|
1413
|
+
variant?: string | null;
|
|
1414
|
+
href?: string | null;
|
|
1415
|
+
openInNewTab?: boolean | null;
|
|
1416
|
+
};
|
|
1180
1417
|
|
|
1181
1418
|
export type PageBlock =
|
|
1182
|
-
| {
|
|
1183
|
-
|
|
1419
|
+
| {
|
|
1420
|
+
_key: string;
|
|
1421
|
+
_type: "hero";
|
|
1422
|
+
badge?: string | null;
|
|
1423
|
+
title?: string | null;
|
|
1424
|
+
richText?: Rich;
|
|
1425
|
+
image?: SanityImage;
|
|
1426
|
+
buttons?: ButtonLink[] | null;
|
|
1427
|
+
}
|
|
1428
|
+
| {
|
|
1429
|
+
_key: string;
|
|
1430
|
+
_type: "richTextBlock";
|
|
1431
|
+
eyebrow?: string | null;
|
|
1432
|
+
title?: string | null;
|
|
1433
|
+
richText?: Rich;
|
|
1434
|
+
}
|
|
1435
|
+
| {
|
|
1436
|
+
_key: string;
|
|
1437
|
+
_type: "featureCardsIcon";
|
|
1438
|
+
eyebrow?: string | null;
|
|
1439
|
+
title?: string | null;
|
|
1440
|
+
richText?: Rich;
|
|
1441
|
+
cards?:
|
|
1442
|
+
| { _key: string; icon?: string | null; title?: string | null; richText?: Rich }[]
|
|
1443
|
+
| null;
|
|
1444
|
+
}
|
|
1445
|
+
| {
|
|
1446
|
+
_key: string;
|
|
1447
|
+
_type: "imageLinkCards";
|
|
1448
|
+
eyebrow?: string | null;
|
|
1449
|
+
title?: string | null;
|
|
1450
|
+
richText?: Rich;
|
|
1451
|
+
buttons?: ButtonLink[] | null;
|
|
1452
|
+
cards?:
|
|
1453
|
+
| {
|
|
1454
|
+
_key: string;
|
|
1455
|
+
title?: string | null;
|
|
1456
|
+
description?: string | null;
|
|
1457
|
+
image?: SanityImage;
|
|
1458
|
+
href?: string | null;
|
|
1459
|
+
openInNewTab?: boolean | null;
|
|
1460
|
+
}[]
|
|
1461
|
+
| null;
|
|
1462
|
+
}
|
|
1463
|
+
| {
|
|
1464
|
+
_key: string;
|
|
1465
|
+
_type: "faqAccordion";
|
|
1466
|
+
eyebrow?: string | null;
|
|
1467
|
+
title?: string | null;
|
|
1468
|
+
subtitle?: string | null;
|
|
1469
|
+
faqs?: { _id: string; title?: string | null; richText?: Rich }[] | null;
|
|
1470
|
+
}
|
|
1471
|
+
| {
|
|
1472
|
+
_key: string;
|
|
1473
|
+
_type: "subscribeNewsletter";
|
|
1474
|
+
title?: string | null;
|
|
1475
|
+
subTitle?: Rich;
|
|
1476
|
+
helperText?: Rich;
|
|
1477
|
+
}
|
|
1184
1478
|
| {
|
|
1185
1479
|
_key: string;
|
|
1186
1480
|
_type: "cta";
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1481
|
+
eyebrow?: string | null;
|
|
1482
|
+
title?: string | null;
|
|
1483
|
+
richText?: Rich;
|
|
1484
|
+
buttons?: ButtonLink[] | null;
|
|
1191
1485
|
};
|
|
1192
1486
|
|
|
1487
|
+
const section = { margin: "3rem 0" } as const;
|
|
1488
|
+
const grid = {
|
|
1489
|
+
display: "grid",
|
|
1490
|
+
gap: "1.5rem",
|
|
1491
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(16rem, 1fr))",
|
|
1492
|
+
} as const;
|
|
1493
|
+
const card = { border: "1px solid #e5e5e5", borderRadius: "0.75rem", padding: "1.25rem" } as const;
|
|
1494
|
+
|
|
1495
|
+
function Eyebrow({ children }: { children?: string | null }) {
|
|
1496
|
+
return children ? <p style={{ opacity: 0.6, margin: 0 }}>{children}</p> : null;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
function Buttons({ items }: { items?: ButtonLink[] | null }) {
|
|
1500
|
+
if (!items?.length) return null;
|
|
1501
|
+
return (
|
|
1502
|
+
<p style={{ display: "flex", gap: "0.75rem", flexWrap: "wrap" }}>
|
|
1503
|
+
{items.map((button) =>
|
|
1504
|
+
button.href ? (
|
|
1505
|
+
<a
|
|
1506
|
+
key={button._key}
|
|
1507
|
+
href={button.href}
|
|
1508
|
+
{...(button.openInNewTab ? { target: "_blank", rel: "noreferrer" } : {})}
|
|
1509
|
+
>
|
|
1510
|
+
{button.text ?? button.href}
|
|
1511
|
+
</a>
|
|
1512
|
+
) : null,
|
|
1513
|
+
)}
|
|
1514
|
+
</p>
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1193
1518
|
function Block({ block }: { block: PageBlock }) {
|
|
1194
1519
|
switch (block._type) {
|
|
1195
1520
|
case "hero": {
|
|
1196
1521
|
const src = urlFor(block.image, 1600);
|
|
1197
1522
|
return (
|
|
1198
|
-
<section>
|
|
1199
|
-
<
|
|
1200
|
-
|
|
1523
|
+
<section style={section}>
|
|
1524
|
+
<Eyebrow>{block.badge}</Eyebrow>
|
|
1525
|
+
<h2>{block.title}</h2>
|
|
1526
|
+
{block.richText ? <PortableText value={block.richText} /> : null}
|
|
1527
|
+
<Buttons items={block.buttons} />
|
|
1201
1528
|
{src ? (
|
|
1202
1529
|
<Image src={src} alt={block.image?.alt ?? ""} width={1600} height={900} />
|
|
1203
1530
|
) : null}
|
|
1204
1531
|
</section>
|
|
1205
1532
|
);
|
|
1206
1533
|
}
|
|
1207
|
-
case "
|
|
1534
|
+
case "richTextBlock":
|
|
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
|
+
</section>
|
|
1541
|
+
);
|
|
1542
|
+
case "featureCardsIcon":
|
|
1543
|
+
return (
|
|
1544
|
+
<section style={section}>
|
|
1545
|
+
<Eyebrow>{block.eyebrow}</Eyebrow>
|
|
1546
|
+
{block.title ? <h2>{block.title}</h2> : null}
|
|
1547
|
+
{block.richText ? <PortableText value={block.richText} /> : null}
|
|
1548
|
+
<div style={grid}>
|
|
1549
|
+
{(block.cards ?? []).map((item) => (
|
|
1550
|
+
<div key={item._key} style={card}>
|
|
1551
|
+
{/* \`icon\` holds a Lucide icon name; render it with your icon set of choice. */}
|
|
1552
|
+
{item.icon ? <p style={{ opacity: 0.6, margin: 0 }}>{item.icon}</p> : null}
|
|
1553
|
+
<h3>{item.title}</h3>
|
|
1554
|
+
{item.richText ? <PortableText value={item.richText} /> : null}
|
|
1555
|
+
</div>
|
|
1556
|
+
))}
|
|
1557
|
+
</div>
|
|
1558
|
+
</section>
|
|
1559
|
+
);
|
|
1560
|
+
case "imageLinkCards":
|
|
1561
|
+
return (
|
|
1562
|
+
<section style={section}>
|
|
1563
|
+
<Eyebrow>{block.eyebrow}</Eyebrow>
|
|
1564
|
+
{block.title ? <h2>{block.title}</h2> : null}
|
|
1565
|
+
{block.richText ? <PortableText value={block.richText} /> : null}
|
|
1566
|
+
<div style={grid}>
|
|
1567
|
+
{(block.cards ?? []).map((item) => {
|
|
1568
|
+
const src = urlFor(item.image, 800);
|
|
1569
|
+
return (
|
|
1570
|
+
<div key={item._key} style={card}>
|
|
1571
|
+
{src ? (
|
|
1572
|
+
<Image src={src} alt={item.image?.alt ?? ""} width={800} height={450} />
|
|
1573
|
+
) : null}
|
|
1574
|
+
<h3>
|
|
1575
|
+
{item.href ? (
|
|
1576
|
+
<a
|
|
1577
|
+
href={item.href}
|
|
1578
|
+
{...(item.openInNewTab ? { target: "_blank", rel: "noreferrer" } : {})}
|
|
1579
|
+
>
|
|
1580
|
+
{item.title}
|
|
1581
|
+
</a>
|
|
1582
|
+
) : (
|
|
1583
|
+
item.title
|
|
1584
|
+
)}
|
|
1585
|
+
</h3>
|
|
1586
|
+
{item.description ? <p>{item.description}</p> : null}
|
|
1587
|
+
</div>
|
|
1588
|
+
);
|
|
1589
|
+
})}
|
|
1590
|
+
</div>
|
|
1591
|
+
<Buttons items={block.buttons} />
|
|
1592
|
+
</section>
|
|
1593
|
+
);
|
|
1594
|
+
case "faqAccordion":
|
|
1595
|
+
// <details> is the accordion — it needs no client component and behaves the same
|
|
1596
|
+
// in the Astro build, where this same markup is duplicated.
|
|
1597
|
+
return (
|
|
1598
|
+
<section style={section}>
|
|
1599
|
+
<Eyebrow>{block.eyebrow}</Eyebrow>
|
|
1600
|
+
{block.title ? <h2>{block.title}</h2> : null}
|
|
1601
|
+
{block.subtitle ? <p>{block.subtitle}</p> : null}
|
|
1602
|
+
{(block.faqs ?? []).map((faq) => (
|
|
1603
|
+
<details key={faq._id} style={{ borderTop: "1px solid #e5e5e5", padding: "0.75rem 0" }}>
|
|
1604
|
+
<summary>{faq.title}</summary>
|
|
1605
|
+
{faq.richText ? <PortableText value={faq.richText} /> : null}
|
|
1606
|
+
</details>
|
|
1607
|
+
))}
|
|
1608
|
+
</section>
|
|
1609
|
+
);
|
|
1610
|
+
case "subscribeNewsletter":
|
|
1611
|
+
// Presentational only: no action, no handler. Wire it to the \`forms\` module's
|
|
1612
|
+
// endpoint (or your own) when you want it to do something.
|
|
1208
1613
|
return (
|
|
1209
|
-
<section>
|
|
1210
|
-
|
|
1211
|
-
{block.
|
|
1614
|
+
<section style={section}>
|
|
1615
|
+
<h2>{block.title}</h2>
|
|
1616
|
+
{block.subTitle ? <PortableText value={block.subTitle} /> : null}
|
|
1617
|
+
<form style={{ display: "flex", gap: "0.5rem", flexWrap: "wrap" }}>
|
|
1618
|
+
<input type="email" name="email" placeholder="you@example.com" required />
|
|
1619
|
+
<button type="submit">Subscribe</button>
|
|
1620
|
+
</form>
|
|
1621
|
+
{block.helperText ? <PortableText value={block.helperText} /> : null}
|
|
1212
1622
|
</section>
|
|
1213
1623
|
);
|
|
1214
1624
|
case "cta":
|
|
1215
1625
|
return (
|
|
1216
|
-
<section>
|
|
1217
|
-
<
|
|
1218
|
-
|
|
1219
|
-
{block.
|
|
1220
|
-
|
|
1221
|
-
) : null}
|
|
1626
|
+
<section style={section}>
|
|
1627
|
+
<Eyebrow>{block.eyebrow}</Eyebrow>
|
|
1628
|
+
<h2>{block.title}</h2>
|
|
1629
|
+
{block.richText ? <PortableText value={block.richText} /> : null}
|
|
1630
|
+
<Buttons items={block.buttons} />
|
|
1222
1631
|
</section>
|
|
1223
1632
|
);
|
|
1224
1633
|
default:
|
|
@@ -1236,34 +1645,9 @@ export function PageBuilder({ blocks }: { blocks: PageBlock[] }) {
|
|
|
1236
1645
|
</>
|
|
1237
1646
|
);
|
|
1238
1647
|
}
|
|
1239
|
-
`,
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
// build-time \`prebuild\` hook works on a fresh clone.
|
|
1243
|
-
export default defineCliConfig({
|
|
1244
|
-
api: {
|
|
1245
|
-
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || "placeholder",
|
|
1246
|
-
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || "production",
|
|
1247
|
-
},
|
|
1248
|
-
typegen: {
|
|
1249
|
-
path: "./sanity/**/*.{ts,tsx}",
|
|
1250
|
-
schema: "sanity/schema.json",
|
|
1251
|
-
generates: "sanity/sanity.types.ts",
|
|
1252
|
-
},
|
|
1253
|
-
});
|
|
1254
|
-
`, NEXT_STUDIO_PAGE = `import { NextStudio } from "next-sanity/studio";
|
|
1255
|
-
import config from "../../../sanity.config";
|
|
1256
|
-
|
|
1257
|
-
export const dynamic = "force-static";
|
|
1258
|
-
|
|
1259
|
-
export { metadata, viewport } from "next-sanity/studio";
|
|
1260
|
-
|
|
1261
|
-
export default function StudioPage() {
|
|
1262
|
-
return <NextStudio config={config} />;
|
|
1263
|
-
}
|
|
1264
|
-
`, NEXT_DRAFT_MODE_ROUTE = `import { defineEnableDraftMode } from "next-sanity/draft-mode";
|
|
1265
|
-
import { readToken } from "../../../../sanity/env";
|
|
1266
|
-
import { client } from "../../../../sanity/client";
|
|
1648
|
+
`, NEXT_DRAFT_MODE_ROUTE = `import { client } from "@workspace/sanity/client";
|
|
1649
|
+
import { readToken } from "@workspace/sanity/env";
|
|
1650
|
+
import { defineEnableDraftMode } from "next-sanity/draft-mode";
|
|
1267
1651
|
|
|
1268
1652
|
export const { GET } = defineEnableDraftMode({
|
|
1269
1653
|
client: client.withConfig({ token: readToken }),
|
|
@@ -1301,10 +1685,10 @@ export function DisableDraftMode() {
|
|
|
1301
1685
|
</a>
|
|
1302
1686
|
);
|
|
1303
1687
|
}
|
|
1304
|
-
`, NEXT_HOME_PAGE = `import
|
|
1305
|
-
import {
|
|
1306
|
-
import { PageBuilder, type PageBlock } from "
|
|
1307
|
-
import {
|
|
1688
|
+
`, NEXT_HOME_PAGE = `import { loadQuery } from "@workspace/sanity/fetch";
|
|
1689
|
+
import { HOME_PAGE_QUERY, SETTINGS_QUERY } from "@workspace/sanity/queries";
|
|
1690
|
+
import { PageBuilder, type PageBlock } from "@workspace/sanity-blocks/blocks";
|
|
1691
|
+
import type { Metadata } from "next";
|
|
1308
1692
|
|
|
1309
1693
|
// stega: false — encoded metadata would leak invisible characters into <title>.
|
|
1310
1694
|
export async function generateMetadata(): Promise<Metadata> {
|
|
@@ -1324,7 +1708,8 @@ export default async function HomePage() {
|
|
|
1324
1708
|
<h1>No content yet</h1>
|
|
1325
1709
|
<p>
|
|
1326
1710
|
Set NEXT_PUBLIC_SANITY_PROJECT_ID in apps/web/.env.local, then seed the dataset:
|
|
1327
|
-
<code> pnpm sanity:seed</code> from apps/
|
|
1711
|
+
<code> pnpm sanity:seed</code> from apps/studio. Edit it in the Studio:
|
|
1712
|
+
<code> pnpm dev:studio</code>.
|
|
1328
1713
|
</p>
|
|
1329
1714
|
</main>
|
|
1330
1715
|
);
|
|
@@ -1337,10 +1722,10 @@ export default async function HomePage() {
|
|
|
1337
1722
|
</main>
|
|
1338
1723
|
);
|
|
1339
1724
|
}
|
|
1340
|
-
`, NEXT_SLUG_PAGE = `import {
|
|
1341
|
-
import {
|
|
1342
|
-
import { PageBuilder, type PageBlock } from "
|
|
1343
|
-
import {
|
|
1725
|
+
`, NEXT_SLUG_PAGE = `import { loadQuery, loadStaticQuery } from "@workspace/sanity/fetch";
|
|
1726
|
+
import { PAGE_QUERY, PAGE_SLUGS_QUERY } from "@workspace/sanity/queries";
|
|
1727
|
+
import { PageBuilder, type PageBlock } from "@workspace/sanity-blocks/blocks";
|
|
1728
|
+
import { notFound } from "next/navigation";
|
|
1344
1729
|
|
|
1345
1730
|
export async function generateStaticParams() {
|
|
1346
1731
|
const slugs = await loadStaticQuery(PAGE_SLUGS_QUERY);
|
|
@@ -1359,193 +1744,1877 @@ export default async function Page({ params }: { params: Promise<{ slug: string
|
|
|
1359
1744
|
</main>
|
|
1360
1745
|
);
|
|
1361
1746
|
}
|
|
1362
|
-
`,
|
|
1747
|
+
`, NEXT_BLOG_INDEX_PAGE = `import { loadQuery } from "@workspace/sanity/fetch";
|
|
1748
|
+
import { urlFor } from "@workspace/sanity/image";
|
|
1749
|
+
import { BLOG_INDEX_QUERY } from "@workspace/sanity/queries";
|
|
1750
|
+
import type { Metadata } from "next";
|
|
1751
|
+
import Image from "next/image";
|
|
1363
1752
|
|
|
1364
|
-
export const
|
|
1753
|
+
export const metadata: Metadata = { title: "Blog" };
|
|
1365
1754
|
|
|
1366
|
-
|
|
1367
|
-
|
|
1755
|
+
export default async function BlogIndexPage() {
|
|
1756
|
+
const posts = (await loadQuery(BLOG_INDEX_QUERY)) ?? [];
|
|
1368
1757
|
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1758
|
+
return (
|
|
1759
|
+
<main style={{ fontFamily: "system-ui, sans-serif", padding: "3rem" }}>
|
|
1760
|
+
<h1>Blog</h1>
|
|
1761
|
+
{posts.length === 0 ? (
|
|
1762
|
+
<p>
|
|
1763
|
+
No posts yet — seed the dataset with <code>pnpm sanity:seed</code> from apps/studio,
|
|
1764
|
+
or write one in the Studio (<code>pnpm dev:studio</code>).
|
|
1765
|
+
</p>
|
|
1766
|
+
) : null}
|
|
1767
|
+
{posts.map((post) => {
|
|
1768
|
+
const src = urlFor(post.image, 800);
|
|
1769
|
+
const author = post.authors?.[0];
|
|
1770
|
+
return (
|
|
1771
|
+
<article key={post._id} style={{ borderTop: "1px solid #e5e5e5", padding: "1.5rem 0" }}>
|
|
1772
|
+
{src ? <Image src={src} alt={post.image?.alt ?? ""} width={800} height={450} /> : null}
|
|
1773
|
+
<h2>
|
|
1774
|
+
<a href={\`/blog/\${post.slug}\`}>{post.title}</a>
|
|
1775
|
+
</h2>
|
|
1776
|
+
<p style={{ opacity: 0.6 }}>
|
|
1777
|
+
{author?.name ? <span>{author.name}</span> : null}
|
|
1778
|
+
{author?.name && post.publishedAt ? " · " : null}
|
|
1779
|
+
{post.publishedAt ? <time dateTime={post.publishedAt}>{post.publishedAt}</time> : null}
|
|
1780
|
+
</p>
|
|
1781
|
+
{post.description ? <p>{post.description}</p> : null}
|
|
1782
|
+
</article>
|
|
1783
|
+
);
|
|
1784
|
+
})}
|
|
1785
|
+
</main>
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
`, NEXT_BLOG_POST_PAGE = `import { PortableText } from "@portabletext/react";
|
|
1789
|
+
import { loadQuery, loadStaticQuery } from "@workspace/sanity/fetch";
|
|
1790
|
+
import { urlFor } from "@workspace/sanity/image";
|
|
1791
|
+
import { BLOG_QUERY, BLOG_SLUGS_QUERY } from "@workspace/sanity/queries";
|
|
1792
|
+
import type { Metadata } from "next";
|
|
1793
|
+
import Image from "next/image";
|
|
1794
|
+
import { notFound } from "next/navigation";
|
|
1375
1795
|
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1796
|
+
export async function generateStaticParams() {
|
|
1797
|
+
const slugs = await loadStaticQuery(BLOG_SLUGS_QUERY);
|
|
1798
|
+
return (slugs ?? []).filter((slug): slug is string => Boolean(slug)).map((slug) => ({ slug }));
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// stega: false — encoded metadata would leak invisible characters into <title>.
|
|
1802
|
+
export async function generateMetadata({
|
|
1803
|
+
params,
|
|
1804
|
+
}: {
|
|
1805
|
+
params: Promise<{ slug: string }>;
|
|
1806
|
+
}): Promise<Metadata> {
|
|
1807
|
+
const { slug } = await params;
|
|
1808
|
+
const post = await loadQuery(BLOG_QUERY, { slug }, { stega: false });
|
|
1809
|
+
return { title: post?.title ?? "Blog", description: post?.description ?? undefined };
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
export default async function BlogPostPage({ params }: { params: Promise<{ slug: string }> }) {
|
|
1813
|
+
const { slug } = await params;
|
|
1814
|
+
const post = await loadQuery(BLOG_QUERY, { slug });
|
|
1815
|
+
if (!post) notFound();
|
|
1816
|
+
|
|
1817
|
+
const src = urlFor(post.image, 1600);
|
|
1818
|
+
const author = post.authors?.[0];
|
|
1819
|
+
|
|
1820
|
+
return (
|
|
1821
|
+
<main style={{ fontFamily: "system-ui, sans-serif", padding: "3rem" }}>
|
|
1822
|
+
<p>
|
|
1823
|
+
<a href="/blog">← Blog</a>
|
|
1824
|
+
</p>
|
|
1825
|
+
<h1>{post.title}</h1>
|
|
1826
|
+
<p style={{ opacity: 0.6 }}>
|
|
1827
|
+
{author?.name ? <span>{author.position ? \`\${author.name}, \${author.position}\` : author.name}</span> : null}
|
|
1828
|
+
{author?.name && post.publishedAt ? " · " : null}
|
|
1829
|
+
{post.publishedAt ? <time dateTime={post.publishedAt}>{post.publishedAt}</time> : null}
|
|
1830
|
+
</p>
|
|
1831
|
+
{src ? <Image src={src} alt={post.image?.alt ?? ""} width={1600} height={900} /> : null}
|
|
1832
|
+
{post.richText ? <PortableText value={post.richText} /> : null}
|
|
1833
|
+
</main>
|
|
1834
|
+
);
|
|
1835
|
+
}
|
|
1836
|
+
`;
|
|
1837
|
+
|
|
1838
|
+
// ../../packages/registry/src/content/sanity/schema.ts
|
|
1839
|
+
var SCHEMA_DEFINITIONS = `import { defineArrayMember, defineField, defineType } from "sanity";
|
|
1383
1840
|
|
|
1384
1841
|
/**
|
|
1385
|
-
*
|
|
1386
|
-
*
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1842
|
+
* A link that resolves to either an internal document or an external URL. Blocks store the
|
|
1843
|
+
* choice; queries.ts turns it into a plain \`href\` string so components never branch on it.
|
|
1844
|
+
*/
|
|
1845
|
+
export const customUrl = defineType({
|
|
1846
|
+
name: "customUrl",
|
|
1847
|
+
title: "URL",
|
|
1848
|
+
type: "object",
|
|
1849
|
+
fields: [
|
|
1850
|
+
defineField({
|
|
1851
|
+
name: "type",
|
|
1852
|
+
type: "string",
|
|
1853
|
+
initialValue: () => "external",
|
|
1854
|
+
options: { list: ["internal", "external"], layout: "radio", direction: "horizontal" },
|
|
1855
|
+
validation: (rule) => rule.required(),
|
|
1856
|
+
}),
|
|
1857
|
+
defineField({
|
|
1858
|
+
name: "openInNewTab",
|
|
1859
|
+
title: "Open in new tab",
|
|
1860
|
+
type: "boolean",
|
|
1861
|
+
initialValue: () => false,
|
|
1862
|
+
}),
|
|
1863
|
+
defineField({
|
|
1864
|
+
name: "external",
|
|
1865
|
+
title: "URL",
|
|
1866
|
+
type: "string",
|
|
1867
|
+
description: "A full address (https://…) or a path (/about).",
|
|
1868
|
+
hidden: ({ parent }) => parent?.type !== "external",
|
|
1869
|
+
validation: (rule) =>
|
|
1870
|
+
rule.custom((value, { parent }) =>
|
|
1871
|
+
(parent as { type?: string })?.type === "external" && !value
|
|
1872
|
+
? "URL can't be empty"
|
|
1873
|
+
: true,
|
|
1874
|
+
),
|
|
1875
|
+
}),
|
|
1876
|
+
defineField({
|
|
1877
|
+
name: "internal",
|
|
1878
|
+
type: "reference",
|
|
1879
|
+
to: [{ type: "page" }, { type: "blog" }],
|
|
1880
|
+
options: { disableNew: true },
|
|
1881
|
+
hidden: ({ parent }) => parent?.type !== "internal",
|
|
1882
|
+
validation: (rule) =>
|
|
1883
|
+
rule.custom((value, { parent }) =>
|
|
1884
|
+
(parent as { type?: string })?.type === "internal" && !value?._ref
|
|
1885
|
+
? "Pick a document to link to"
|
|
1886
|
+
: true,
|
|
1887
|
+
),
|
|
1888
|
+
}),
|
|
1889
|
+
],
|
|
1890
|
+
preview: {
|
|
1891
|
+
select: { type: "type", external: "external", internal: "internal.slug.current" },
|
|
1892
|
+
prepare: ({ type, external, internal }) => ({
|
|
1893
|
+
title: type === "external" ? "External link" : "Internal link",
|
|
1894
|
+
subtitle: type === "external" ? external : internal,
|
|
1895
|
+
}),
|
|
1896
|
+
},
|
|
1897
|
+
});
|
|
1898
|
+
|
|
1899
|
+
export const button = defineType({
|
|
1900
|
+
name: "button",
|
|
1901
|
+
title: "Button",
|
|
1902
|
+
type: "object",
|
|
1903
|
+
fields: [
|
|
1904
|
+
defineField({
|
|
1905
|
+
name: "variant",
|
|
1906
|
+
type: "string",
|
|
1907
|
+
initialValue: () => "default",
|
|
1908
|
+
options: {
|
|
1909
|
+
list: ["default", "secondary", "outline", "link"],
|
|
1910
|
+
layout: "radio",
|
|
1911
|
+
direction: "horizontal",
|
|
1912
|
+
},
|
|
1913
|
+
}),
|
|
1914
|
+
defineField({ name: "text", title: "Button text", type: "string" }),
|
|
1915
|
+
defineField({ name: "url", title: "URL", type: "customUrl" }),
|
|
1916
|
+
],
|
|
1917
|
+
preview: {
|
|
1918
|
+
select: { title: "text", variant: "variant" },
|
|
1919
|
+
prepare: ({ title, variant }) => ({
|
|
1920
|
+
title: title ?? "Untitled button",
|
|
1921
|
+
subtitle: variant ?? "default",
|
|
1922
|
+
}),
|
|
1923
|
+
},
|
|
1924
|
+
});
|
|
1925
|
+
|
|
1926
|
+
/**
|
|
1927
|
+
* Portable Text. Registered as a named type so documents and blocks can say
|
|
1928
|
+
* \`type: "richText"\` — the same indirection the reference uses.
|
|
1929
|
+
*/
|
|
1930
|
+
export const richText = defineType({
|
|
1931
|
+
name: "richText",
|
|
1932
|
+
type: "array",
|
|
1933
|
+
of: [
|
|
1934
|
+
defineArrayMember({
|
|
1935
|
+
name: "block",
|
|
1936
|
+
type: "block",
|
|
1937
|
+
styles: [
|
|
1938
|
+
{ title: "Normal", value: "normal" },
|
|
1939
|
+
{ title: "H2", value: "h2" },
|
|
1940
|
+
{ title: "H3", value: "h3" },
|
|
1941
|
+
{ title: "H4", value: "h4" },
|
|
1942
|
+
],
|
|
1943
|
+
lists: [
|
|
1944
|
+
{ title: "Bullet", value: "bullet" },
|
|
1945
|
+
{ title: "Numbered", value: "number" },
|
|
1946
|
+
],
|
|
1947
|
+
marks: {
|
|
1948
|
+
decorators: [
|
|
1949
|
+
{ title: "Strong", value: "strong" },
|
|
1950
|
+
{ title: "Emphasis", value: "em" },
|
|
1951
|
+
{ title: "Code", value: "code" },
|
|
1952
|
+
],
|
|
1953
|
+
annotations: [
|
|
1954
|
+
{
|
|
1955
|
+
name: "customLink",
|
|
1956
|
+
title: "Link",
|
|
1957
|
+
type: "object",
|
|
1958
|
+
fields: [defineField({ name: "customLink", type: "customUrl" })],
|
|
1959
|
+
},
|
|
1960
|
+
],
|
|
1961
|
+
},
|
|
1962
|
+
}),
|
|
1963
|
+
defineArrayMember({
|
|
1964
|
+
name: "image",
|
|
1965
|
+
title: "Image",
|
|
1966
|
+
type: "image",
|
|
1967
|
+
options: { hotspot: true },
|
|
1968
|
+
fields: [defineField({ name: "caption", title: "Caption", type: "string" })],
|
|
1969
|
+
}),
|
|
1970
|
+
],
|
|
1971
|
+
});
|
|
1972
|
+
`, SCHEMA_BLOCKS = `import { defineArrayMember, defineField, defineType } from "sanity";
|
|
1973
|
+
|
|
1974
|
+
// Page-builder blocks are objects, not documents: they only exist inside a page.
|
|
1975
|
+
// Field names match turbo-start-sanity's, so its components drop in unchanged.
|
|
1976
|
+
|
|
1977
|
+
const imageField = defineField({
|
|
1978
|
+
name: "image",
|
|
1979
|
+
type: "image",
|
|
1980
|
+
options: { hotspot: true },
|
|
1981
|
+
fields: [
|
|
1982
|
+
defineField({
|
|
1983
|
+
name: "alt",
|
|
1984
|
+
title: "Alt text",
|
|
1985
|
+
type: "string",
|
|
1986
|
+
validation: (rule) =>
|
|
1987
|
+
rule.custom((value, context) =>
|
|
1988
|
+
(context.parent as { asset?: unknown })?.asset && !value?.trim()
|
|
1989
|
+
? "Alt text is required when an image is set"
|
|
1990
|
+
: true,
|
|
1991
|
+
),
|
|
1992
|
+
}),
|
|
1993
|
+
],
|
|
1994
|
+
});
|
|
1995
|
+
|
|
1996
|
+
const buttonsField = defineField({
|
|
1997
|
+
name: "buttons",
|
|
1998
|
+
type: "array",
|
|
1999
|
+
of: [defineArrayMember({ type: "button" })],
|
|
2000
|
+
});
|
|
2001
|
+
|
|
2002
|
+
export const hero = defineType({
|
|
2003
|
+
name: "hero",
|
|
2004
|
+
title: "Hero",
|
|
2005
|
+
type: "object",
|
|
2006
|
+
fields: [
|
|
2007
|
+
defineField({ name: "badge", type: "string" }),
|
|
2008
|
+
defineField({ name: "title", type: "string" }),
|
|
2009
|
+
defineField({ name: "richText", type: "richText" }),
|
|
2010
|
+
imageField,
|
|
2011
|
+
buttonsField,
|
|
2012
|
+
],
|
|
2013
|
+
preview: {
|
|
2014
|
+
select: { title: "title", media: "image" },
|
|
2015
|
+
prepare: ({ title, media }) => ({ title: title ?? "Hero", subtitle: "Hero", media }),
|
|
2016
|
+
},
|
|
2017
|
+
});
|
|
2018
|
+
|
|
2019
|
+
export const cta = defineType({
|
|
2020
|
+
name: "cta",
|
|
2021
|
+
title: "Call to action",
|
|
2022
|
+
type: "object",
|
|
2023
|
+
fields: [
|
|
2024
|
+
defineField({ name: "eyebrow", type: "string" }),
|
|
2025
|
+
defineField({ name: "title", type: "string" }),
|
|
2026
|
+
defineField({ name: "richText", type: "richText" }),
|
|
2027
|
+
buttonsField,
|
|
2028
|
+
],
|
|
2029
|
+
preview: {
|
|
2030
|
+
select: { title: "title" },
|
|
2031
|
+
prepare: ({ title }) => ({ title: title ?? "Call to action", subtitle: "CTA" }),
|
|
2032
|
+
},
|
|
2033
|
+
});
|
|
2034
|
+
|
|
2035
|
+
export const richTextBlock = defineType({
|
|
2036
|
+
name: "richTextBlock",
|
|
2037
|
+
title: "Rich text",
|
|
2038
|
+
type: "object",
|
|
2039
|
+
fields: [
|
|
2040
|
+
defineField({ name: "eyebrow", type: "string" }),
|
|
2041
|
+
defineField({ name: "title", type: "string" }),
|
|
2042
|
+
defineField({ name: "richText", type: "richText" }),
|
|
2043
|
+
],
|
|
2044
|
+
preview: {
|
|
2045
|
+
select: { title: "title" },
|
|
2046
|
+
prepare: ({ title }) => ({ title: title ?? "Rich text", subtitle: "Rich text" }),
|
|
2047
|
+
},
|
|
2048
|
+
});
|
|
2049
|
+
|
|
2050
|
+
// The reference stores \`icon\` as a \`lucide-icon\`, a custom input from
|
|
2051
|
+
// sanity-plugin-lucide-icon-picker. A plain string keeps the field (and the GROQ) the same
|
|
2052
|
+
// shape without a Studio plugin — add the plugin later and only this field changes.
|
|
2053
|
+
const featureCardIcon = defineArrayMember({
|
|
2054
|
+
name: "featureCardIcon",
|
|
2055
|
+
type: "object",
|
|
2056
|
+
fields: [
|
|
2057
|
+
defineField({
|
|
2058
|
+
name: "icon",
|
|
2059
|
+
type: "string",
|
|
2060
|
+
description: 'A Lucide icon name, e.g. "zap" or "shield-check".',
|
|
2061
|
+
}),
|
|
2062
|
+
defineField({ name: "title", type: "string" }),
|
|
2063
|
+
defineField({ name: "richText", type: "richText" }),
|
|
2064
|
+
],
|
|
2065
|
+
preview: { select: { title: "title", subtitle: "icon" } },
|
|
2066
|
+
});
|
|
2067
|
+
|
|
2068
|
+
export const featureCardsIcon = defineType({
|
|
2069
|
+
name: "featureCardsIcon",
|
|
2070
|
+
title: "Feature cards",
|
|
2071
|
+
type: "object",
|
|
2072
|
+
fields: [
|
|
2073
|
+
defineField({ name: "eyebrow", type: "string" }),
|
|
2074
|
+
defineField({ name: "title", type: "string" }),
|
|
2075
|
+
defineField({ name: "richText", type: "richText" }),
|
|
2076
|
+
defineField({ name: "cards", type: "array", of: [featureCardIcon] }),
|
|
2077
|
+
],
|
|
2078
|
+
preview: {
|
|
2079
|
+
select: { title: "title", cards: "cards" },
|
|
2080
|
+
prepare: ({ title, cards }) => ({
|
|
2081
|
+
title: title ?? "Feature cards",
|
|
2082
|
+
subtitle: \`\${cards?.length ?? 0} cards\`,
|
|
2083
|
+
}),
|
|
2084
|
+
},
|
|
2085
|
+
});
|
|
2086
|
+
|
|
2087
|
+
const imageLinkCard = defineArrayMember({
|
|
2088
|
+
name: "imageLinkCard",
|
|
2089
|
+
type: "object",
|
|
2090
|
+
fields: [
|
|
2091
|
+
defineField({ name: "title", type: "string", validation: (rule) => rule.required() }),
|
|
2092
|
+
defineField({ name: "description", type: "text", rows: 2 }),
|
|
2093
|
+
imageField,
|
|
2094
|
+
defineField({ name: "url", title: "Link", type: "customUrl" }),
|
|
2095
|
+
],
|
|
2096
|
+
preview: { select: { title: "title", subtitle: "description", media: "image" } },
|
|
2097
|
+
});
|
|
2098
|
+
|
|
2099
|
+
export const imageLinkCards = defineType({
|
|
2100
|
+
name: "imageLinkCards",
|
|
2101
|
+
title: "Image link cards",
|
|
2102
|
+
type: "object",
|
|
2103
|
+
fields: [
|
|
2104
|
+
defineField({ name: "eyebrow", type: "string" }),
|
|
2105
|
+
defineField({ name: "title", type: "string", validation: (rule) => rule.required() }),
|
|
2106
|
+
defineField({ name: "richText", type: "richText" }),
|
|
2107
|
+
buttonsField,
|
|
2108
|
+
defineField({ name: "cards", type: "array", of: [imageLinkCard] }),
|
|
2109
|
+
],
|
|
2110
|
+
preview: {
|
|
2111
|
+
select: { title: "title", cards: "cards" },
|
|
2112
|
+
prepare: ({ title, cards }) => ({
|
|
2113
|
+
title: title ?? "Image link cards",
|
|
2114
|
+
subtitle: \`\${cards?.length ?? 0} cards\`,
|
|
2115
|
+
}),
|
|
2116
|
+
},
|
|
2117
|
+
});
|
|
2118
|
+
|
|
2119
|
+
export const faqAccordion = defineType({
|
|
2120
|
+
name: "faqAccordion",
|
|
2121
|
+
title: "FAQ accordion",
|
|
2122
|
+
type: "object",
|
|
2123
|
+
fields: [
|
|
2124
|
+
defineField({ name: "eyebrow", type: "string" }),
|
|
2125
|
+
defineField({ name: "title", type: "string", validation: (rule) => rule.required() }),
|
|
2126
|
+
defineField({ name: "subtitle", type: "string" }),
|
|
2127
|
+
defineField({
|
|
2128
|
+
name: "faqs",
|
|
2129
|
+
title: "FAQs",
|
|
2130
|
+
type: "array",
|
|
2131
|
+
of: [defineArrayMember({ type: "reference", to: [{ type: "faq" }] })],
|
|
2132
|
+
validation: (rule) => rule.unique(),
|
|
2133
|
+
}),
|
|
2134
|
+
],
|
|
2135
|
+
preview: {
|
|
2136
|
+
select: { title: "title", faqs: "faqs" },
|
|
2137
|
+
prepare: ({ title, faqs }) => ({
|
|
2138
|
+
title: title ?? "FAQ accordion",
|
|
2139
|
+
subtitle: \`\${faqs?.length ?? 0} questions\`,
|
|
2140
|
+
}),
|
|
2141
|
+
},
|
|
2142
|
+
});
|
|
2143
|
+
|
|
2144
|
+
export const subscribeNewsletter = defineType({
|
|
2145
|
+
name: "subscribeNewsletter",
|
|
2146
|
+
title: "Subscribe newsletter",
|
|
2147
|
+
type: "object",
|
|
2148
|
+
fields: [
|
|
2149
|
+
defineField({ name: "title", type: "string" }),
|
|
2150
|
+
defineField({ name: "subTitle", title: "Subtitle", type: "richText" }),
|
|
2151
|
+
defineField({ name: "helperText", title: "Helper text", type: "richText" }),
|
|
2152
|
+
],
|
|
2153
|
+
preview: {
|
|
2154
|
+
select: { title: "title" },
|
|
2155
|
+
prepare: ({ title }) => ({ title: title ?? "Newsletter", subtitle: "Newsletter" }),
|
|
2156
|
+
},
|
|
2157
|
+
});
|
|
2158
|
+
|
|
2159
|
+
/**
|
|
2160
|
+
* Every page-builder block, in the order the \`page\` document offers them. The Studio
|
|
2161
|
+
* imports this and composes it with its own document schemas — one list to keep in step
|
|
2162
|
+
* with the renderers that live beside it, instead of two.
|
|
2163
|
+
*/
|
|
2164
|
+
export const blockSchemas = [
|
|
2165
|
+
hero,
|
|
2166
|
+
richTextBlock,
|
|
2167
|
+
featureCardsIcon,
|
|
2168
|
+
imageLinkCards,
|
|
2169
|
+
faqAccordion,
|
|
2170
|
+
subscribeNewsletter,
|
|
2171
|
+
cta,
|
|
2172
|
+
];
|
|
2173
|
+
`, SCHEMA_PAGE = `import { defineArrayMember, defineField, defineType } from "sanity";
|
|
2174
|
+
|
|
2175
|
+
export const page = defineType({
|
|
2176
|
+
name: "page",
|
|
2177
|
+
title: "Page",
|
|
2178
|
+
type: "document",
|
|
2179
|
+
fields: [
|
|
2180
|
+
defineField({ name: "title", type: "string", validation: (rule) => rule.required() }),
|
|
2181
|
+
defineField({
|
|
2182
|
+
name: "slug",
|
|
2183
|
+
type: "slug",
|
|
2184
|
+
options: { source: "title" },
|
|
2185
|
+
validation: (rule) => rule.required(),
|
|
2186
|
+
}),
|
|
2187
|
+
defineField({ name: "description", type: "text", rows: 3 }),
|
|
2188
|
+
defineField({ name: "image", type: "image", options: { hotspot: true } }),
|
|
2189
|
+
defineField({
|
|
2190
|
+
name: "pageBuilder",
|
|
2191
|
+
title: "Page builder",
|
|
2192
|
+
type: "array",
|
|
2193
|
+
of: [
|
|
2194
|
+
defineArrayMember({ type: "hero" }),
|
|
2195
|
+
defineArrayMember({ type: "richTextBlock" }),
|
|
2196
|
+
defineArrayMember({ type: "featureCardsIcon" }),
|
|
2197
|
+
defineArrayMember({ type: "imageLinkCards" }),
|
|
2198
|
+
defineArrayMember({ type: "faqAccordion" }),
|
|
2199
|
+
defineArrayMember({ type: "subscribeNewsletter" }),
|
|
2200
|
+
defineArrayMember({ type: "cta" }),
|
|
2201
|
+
],
|
|
2202
|
+
}),
|
|
2203
|
+
],
|
|
2204
|
+
// The home page is the \`page\` whose slug is "home" — see sanity/queries.ts.
|
|
2205
|
+
preview: { select: { title: "title", subtitle: "slug.current", media: "image" } },
|
|
2206
|
+
});
|
|
2207
|
+
`, SCHEMA_BLOG = `import { defineArrayMember, defineField, defineType } from "sanity";
|
|
2208
|
+
|
|
2209
|
+
export const author = defineType({
|
|
2210
|
+
name: "author",
|
|
2211
|
+
title: "Author",
|
|
2212
|
+
type: "document",
|
|
2213
|
+
fields: [
|
|
2214
|
+
defineField({ name: "name", type: "string", validation: (rule) => rule.required() }),
|
|
2215
|
+
defineField({ name: "position", type: "string" }),
|
|
2216
|
+
defineField({ name: "image", type: "image", options: { hotspot: true } }),
|
|
2217
|
+
defineField({ name: "bio", type: "text", rows: 3 }),
|
|
2218
|
+
],
|
|
2219
|
+
preview: { select: { title: "name", subtitle: "position", media: "image" } },
|
|
2220
|
+
});
|
|
2221
|
+
|
|
2222
|
+
export const blog = defineType({
|
|
2223
|
+
name: "blog",
|
|
2224
|
+
title: "Blog post",
|
|
2225
|
+
type: "document",
|
|
2226
|
+
fields: [
|
|
2227
|
+
defineField({ name: "title", type: "string", validation: (rule) => rule.required() }),
|
|
2228
|
+
defineField({
|
|
2229
|
+
name: "slug",
|
|
2230
|
+
type: "slug",
|
|
2231
|
+
options: { source: "title" },
|
|
2232
|
+
validation: (rule) => rule.required(),
|
|
2233
|
+
}),
|
|
2234
|
+
defineField({ name: "description", type: "text", rows: 3 }),
|
|
2235
|
+
defineField({
|
|
2236
|
+
name: "authors",
|
|
2237
|
+
type: "array",
|
|
2238
|
+
of: [
|
|
2239
|
+
defineArrayMember({
|
|
2240
|
+
type: "reference",
|
|
2241
|
+
to: [{ type: "author" }],
|
|
2242
|
+
options: { disableNew: true },
|
|
2243
|
+
}),
|
|
2244
|
+
],
|
|
2245
|
+
validation: (rule) => rule.unique().max(1),
|
|
2246
|
+
}),
|
|
2247
|
+
defineField({
|
|
2248
|
+
name: "publishedAt",
|
|
2249
|
+
title: "Published at",
|
|
2250
|
+
type: "date",
|
|
2251
|
+
// slice, not split("T")[0] — under noUncheckedIndexedAccess the index gives
|
|
2252
|
+
// \`string | undefined\` and initialValue will not take it.
|
|
2253
|
+
initialValue: () => new Date().toISOString().slice(0, 10),
|
|
2254
|
+
}),
|
|
2255
|
+
// Optional, unlike the reference's required image: seeded posts ship without image
|
|
2256
|
+
// assets, and the renderers have to handle a post with no image regardless.
|
|
2257
|
+
defineField({
|
|
2258
|
+
name: "image",
|
|
2259
|
+
type: "image",
|
|
2260
|
+
options: { hotspot: true },
|
|
2261
|
+
fields: [defineField({ name: "alt", title: "Alt text", type: "string" })],
|
|
2262
|
+
}),
|
|
2263
|
+
defineField({ name: "richText", title: "Content", type: "richText" }),
|
|
2264
|
+
],
|
|
2265
|
+
preview: {
|
|
2266
|
+
select: { title: "title", author: "authors.0.name", date: "publishedAt", media: "image" },
|
|
2267
|
+
prepare: ({ title, author, date, media }) => ({
|
|
2268
|
+
title: title ?? "Untitled post",
|
|
2269
|
+
subtitle: [author, date].filter(Boolean).join(" · "),
|
|
2270
|
+
media,
|
|
2271
|
+
}),
|
|
2272
|
+
},
|
|
2273
|
+
});
|
|
2274
|
+
|
|
2275
|
+
export const faq = defineType({
|
|
2276
|
+
name: "faq",
|
|
2277
|
+
title: "FAQ",
|
|
2278
|
+
type: "document",
|
|
2279
|
+
fields: [
|
|
2280
|
+
defineField({
|
|
2281
|
+
name: "title",
|
|
2282
|
+
title: "Question",
|
|
2283
|
+
type: "string",
|
|
2284
|
+
validation: (rule) => rule.required(),
|
|
2285
|
+
}),
|
|
2286
|
+
defineField({ name: "richText", title: "Answer", type: "richText" }),
|
|
2287
|
+
],
|
|
2288
|
+
preview: { select: { title: "title" } },
|
|
2289
|
+
});
|
|
2290
|
+
`, SCHEMA_SETTINGS = `import { defineField, defineType } from "sanity";
|
|
2291
|
+
|
|
2292
|
+
export const settings = defineType({
|
|
2293
|
+
name: "settings",
|
|
2294
|
+
title: "Settings",
|
|
2295
|
+
type: "document",
|
|
2296
|
+
fields: [
|
|
2297
|
+
defineField({ name: "siteTitle", type: "string", validation: (rule) => rule.required() }),
|
|
2298
|
+
defineField({ name: "siteDescription", type: "text", rows: 3 }),
|
|
2299
|
+
defineField({
|
|
2300
|
+
name: "socialImage",
|
|
2301
|
+
title: "Social share image",
|
|
2302
|
+
type: "image",
|
|
2303
|
+
options: { hotspot: true },
|
|
2304
|
+
}),
|
|
2305
|
+
],
|
|
2306
|
+
preview: { select: { title: "siteTitle", media: "socialImage" } },
|
|
2307
|
+
});
|
|
2308
|
+
`, SCHEMA_INDEX = `import { blockSchemas } from "@workspace/sanity-blocks";
|
|
2309
|
+
import type { SchemaTypeDefinition } from "sanity";
|
|
2310
|
+
import { author, blog, faq } from "./blog";
|
|
2311
|
+
import { button, customUrl, richText } from "./definitions";
|
|
2312
|
+
import { page } from "./page";
|
|
2313
|
+
import { settings } from "./settings";
|
|
2314
|
+
|
|
2315
|
+
export const schemaTypes: SchemaTypeDefinition[] = [
|
|
2316
|
+
// Documents
|
|
2317
|
+
page,
|
|
2318
|
+
blog,
|
|
2319
|
+
author,
|
|
2320
|
+
faq,
|
|
2321
|
+
settings,
|
|
2322
|
+
// Reusable definitions
|
|
2323
|
+
customUrl,
|
|
2324
|
+
button,
|
|
2325
|
+
richText,
|
|
2326
|
+
// Page-builder blocks — declared in @workspace/sanity-blocks, next to their renderers.
|
|
2327
|
+
...blockSchemas,
|
|
2328
|
+
];
|
|
2329
|
+
`, STRUCTURE = `import type { StructureResolver } from "sanity/structure";
|
|
2330
|
+
|
|
2331
|
+
/**
|
|
2332
|
+
* \`settings\` is a singleton: one document with a fixed id, edited in place instead of
|
|
2333
|
+
* listed. Everything else falls through to the default document-type lists.
|
|
2334
|
+
*
|
|
2335
|
+
* The two sets feed sanity.config.ts's \`document.newDocumentOptions\` / \`document.actions\`
|
|
2336
|
+
* guards (the official singleton recipe), so a second settings document cannot be created.
|
|
2337
|
+
*/
|
|
2338
|
+
export const singletonTypes = new Set(["settings"]);
|
|
2339
|
+
|
|
2340
|
+
/** Actions that keep a singleton editable without allowing create/delete/duplicate. */
|
|
2341
|
+
export const singletonActions = new Set(["publish", "discardChanges", "restore"]);
|
|
2342
|
+
|
|
2343
|
+
export const structure: StructureResolver = (S) =>
|
|
2344
|
+
S.list()
|
|
2345
|
+
.title("Content")
|
|
2346
|
+
.items([
|
|
2347
|
+
S.listItem()
|
|
2348
|
+
.title("Settings")
|
|
2349
|
+
.id("settings")
|
|
2350
|
+
.child(S.document().schemaType("settings").documentId("settings")),
|
|
2351
|
+
S.divider(),
|
|
2352
|
+
S.documentTypeListItem("page").title("Pages"),
|
|
2353
|
+
S.documentTypeListItem("blog").title("Blog posts"),
|
|
2354
|
+
S.documentTypeListItem("author").title("Authors"),
|
|
2355
|
+
S.documentTypeListItem("faq").title("FAQs"),
|
|
2356
|
+
]);
|
|
2357
|
+
`, PRESENTATION = `import { defineDocuments, defineLocations } from "sanity/presentation";
|
|
2358
|
+
|
|
2359
|
+
/** Route → document: lets Presentation open the right document for the URL in the iframe. */
|
|
2360
|
+
export const mainDocuments = defineDocuments([
|
|
2361
|
+
{ route: "/", filter: \`_type == "page" && slug.current == "home"\` },
|
|
2362
|
+
{ route: "/blog/:slug", filter: \`_type == "blog" && slug.current == $slug\` },
|
|
2363
|
+
{ route: "/:slug", filter: \`_type == "page" && slug.current == $slug\` },
|
|
2364
|
+
]);
|
|
2365
|
+
|
|
2366
|
+
/** Document → routes: the "Used on …" links shown when editing a document. */
|
|
2367
|
+
export const locations = {
|
|
2368
|
+
settings: defineLocations({
|
|
2369
|
+
locations: [{ title: "Home", href: "/" }],
|
|
2370
|
+
message: "This document is used on all pages",
|
|
2371
|
+
tone: "positive",
|
|
2372
|
+
}),
|
|
2373
|
+
page: defineLocations({
|
|
2374
|
+
select: { title: "title", slug: "slug.current" },
|
|
2375
|
+
resolve: (doc) => ({
|
|
2376
|
+
locations: [
|
|
2377
|
+
{
|
|
2378
|
+
title: doc?.title || "Untitled",
|
|
2379
|
+
href: doc?.slug === "home" ? "/" : \`/\${doc?.slug}\`,
|
|
2380
|
+
},
|
|
2381
|
+
],
|
|
2382
|
+
}),
|
|
2383
|
+
}),
|
|
2384
|
+
blog: defineLocations({
|
|
2385
|
+
select: { title: "title", slug: "slug.current" },
|
|
2386
|
+
resolve: (doc) => ({
|
|
2387
|
+
locations: [
|
|
2388
|
+
{ title: doc?.title || "Untitled", href: \`/blog/\${doc?.slug}\` },
|
|
2389
|
+
{ title: "Blog", href: "/blog" },
|
|
2390
|
+
],
|
|
2391
|
+
}),
|
|
2392
|
+
}),
|
|
2393
|
+
};
|
|
2394
|
+
`, DOCUMENT_GUARD = ` document: {
|
|
2395
|
+
newDocumentOptions: (prev, { creationContext }) =>
|
|
2396
|
+
creationContext.type === "global"
|
|
2397
|
+
? prev.filter((template) => !singletonTypes.has(template.templateId))
|
|
2398
|
+
: prev,
|
|
2399
|
+
actions: (prev, { schemaType }) =>
|
|
2400
|
+
singletonTypes.has(schemaType)
|
|
2401
|
+
? prev.filter(({ action }) => action && singletonActions.has(action))
|
|
2402
|
+
: prev,
|
|
2403
|
+
},`, QUERIES = `import { defineQuery } from "groq";
|
|
2404
|
+
|
|
2405
|
+
/**
|
|
2406
|
+
* A \`customUrl\` collapsed to a plain href, so components never branch on internal vs
|
|
2407
|
+
* external. Written against a field literally named \`url\` — the shape \`button\` and
|
|
2408
|
+
* \`imageLinkCard\` both use.
|
|
2409
|
+
*/
|
|
2410
|
+
const LINK_FIELDS = /* groq */ \`
|
|
2411
|
+
"openInNewTab": url.openInNewTab,
|
|
2412
|
+
"href": select(
|
|
2413
|
+
url.type == "external" => url.external,
|
|
2414
|
+
url.internal->_type == "blog" => "/blog/" + url.internal->slug.current,
|
|
2415
|
+
url.internal->slug.current == "home" => "/",
|
|
2416
|
+
defined(url.internal->slug.current) => "/" + url.internal->slug.current,
|
|
2417
|
+
"#"
|
|
2418
|
+
)
|
|
2419
|
+
\`;
|
|
2420
|
+
|
|
2421
|
+
const BUTTONS = /* groq */ \`buttons[]{ _key, _type, text, variant, \${LINK_FIELDS} }\`;
|
|
2422
|
+
|
|
2423
|
+
// \`...\` first, then the overrides: every block keeps its plain fields, and the two that
|
|
2424
|
+
// hold references or links get resolved on top.
|
|
2425
|
+
const PAGE_BUILDER = /* groq */ \`
|
|
2426
|
+
pageBuilder[]{
|
|
2427
|
+
...,
|
|
2428
|
+
\${BUTTONS},
|
|
2429
|
+
_type == "faqAccordion" => { faqs[]->{ _id, title, richText } },
|
|
2430
|
+
_type == "imageLinkCards" => { cards[]{ ..., \${LINK_FIELDS} } }
|
|
2431
|
+
}
|
|
2432
|
+
\`;
|
|
2433
|
+
|
|
2434
|
+
const PAGE_FIELDS = /* groq */ \`
|
|
2435
|
+
_id,
|
|
2436
|
+
_type,
|
|
2437
|
+
title,
|
|
2438
|
+
description,
|
|
2439
|
+
"slug": slug.current,
|
|
2440
|
+
\${PAGE_BUILDER}
|
|
2441
|
+
\`;
|
|
2442
|
+
|
|
2443
|
+
const BLOG_CARD_FIELDS = /* groq */ \`
|
|
2444
|
+
_id,
|
|
2445
|
+
title,
|
|
2446
|
+
description,
|
|
2447
|
+
publishedAt,
|
|
2448
|
+
image,
|
|
2449
|
+
"slug": slug.current,
|
|
2450
|
+
"authors": authors[]->{ _id, name, position, image }
|
|
2451
|
+
\`;
|
|
2452
|
+
|
|
2453
|
+
export const HOME_PAGE_QUERY = defineQuery(
|
|
2454
|
+
\`*[_type == "page" && slug.current == "home"][0]{\${PAGE_FIELDS}}\`,
|
|
2455
|
+
);
|
|
2456
|
+
|
|
2457
|
+
export const PAGE_QUERY = defineQuery(
|
|
2458
|
+
\`*[_type == "page" && slug.current == $slug][0]{\${PAGE_FIELDS}}\`,
|
|
2459
|
+
);
|
|
2460
|
+
|
|
2461
|
+
export const PAGE_SLUGS_QUERY = defineQuery(
|
|
2462
|
+
\`*[_type == "page" && defined(slug.current) && slug.current != "home"].slug.current\`,
|
|
2463
|
+
);
|
|
2464
|
+
|
|
2465
|
+
export const BLOG_INDEX_QUERY = defineQuery(
|
|
2466
|
+
\`*[_type == "blog" && defined(slug.current)]|order(publishedAt desc){\${BLOG_CARD_FIELDS}}\`,
|
|
2467
|
+
);
|
|
2468
|
+
|
|
2469
|
+
export const BLOG_QUERY = defineQuery(
|
|
2470
|
+
\`*[_type == "blog" && slug.current == $slug][0]{\${BLOG_CARD_FIELDS}, richText}\`,
|
|
2471
|
+
);
|
|
2472
|
+
|
|
2473
|
+
export const BLOG_SLUGS_QUERY = defineQuery(
|
|
2474
|
+
\`*[_type == "blog" && defined(slug.current)].slug.current\`,
|
|
2475
|
+
);
|
|
2476
|
+
|
|
2477
|
+
export const SETTINGS_QUERY = defineQuery(
|
|
2478
|
+
\`*[_type == "settings"][0]{ siteTitle, siteDescription, socialImage }\`,
|
|
2479
|
+
);
|
|
2480
|
+
`;
|
|
2481
|
+
|
|
2482
|
+
// ../../packages/registry/src/content/sanity/seed.ts
|
|
2483
|
+
var para = (key, text) => ({
|
|
2484
|
+
_key: key,
|
|
2485
|
+
_type: "block",
|
|
2486
|
+
style: "normal",
|
|
2487
|
+
markDefs: [],
|
|
2488
|
+
children: [{ _key: `${key}s`, _type: "span", marks: [], text }]
|
|
2489
|
+
}), internal = (id) => ({
|
|
2490
|
+
_type: "customUrl",
|
|
2491
|
+
type: "internal",
|
|
2492
|
+
openInNewTab: false,
|
|
2493
|
+
internal: { _type: "reference", _ref: id }
|
|
2494
|
+
}), external = (href) => ({
|
|
2495
|
+
_type: "customUrl",
|
|
2496
|
+
type: "external",
|
|
2497
|
+
openInNewTab: true,
|
|
2498
|
+
external: href
|
|
2499
|
+
}), button = (key, text, url, variant = "default") => ({
|
|
2500
|
+
_key: key,
|
|
2501
|
+
_type: "button",
|
|
2502
|
+
text,
|
|
2503
|
+
variant,
|
|
2504
|
+
url
|
|
2505
|
+
}), DOCS, SEED_NDJSON;
|
|
2506
|
+
var init_seed = __esm(() => {
|
|
2507
|
+
DOCS = [
|
|
2508
|
+
{
|
|
2509
|
+
_id: "settings",
|
|
2510
|
+
_type: "settings",
|
|
2511
|
+
siteTitle: "Turbo Wizard",
|
|
2512
|
+
siteDescription: "A Turborepo monorepo scaffolded with create-turbo-wizard, with Sanity wired end to end."
|
|
2513
|
+
},
|
|
2514
|
+
{
|
|
2515
|
+
_id: "author-nova",
|
|
2516
|
+
_type: "author",
|
|
2517
|
+
name: "Nova Lang",
|
|
2518
|
+
position: "Staff engineer",
|
|
2519
|
+
bio: "Writes about build systems and the parts of a monorepo nobody reads until they break."
|
|
2520
|
+
},
|
|
2521
|
+
{
|
|
2522
|
+
_id: "author-atlas",
|
|
2523
|
+
_type: "author",
|
|
2524
|
+
name: "Atlas Reyes",
|
|
2525
|
+
position: "Content lead",
|
|
2526
|
+
bio: "Turns a schema into something an editor can actually use."
|
|
2527
|
+
},
|
|
2528
|
+
{
|
|
2529
|
+
_id: "faq-content",
|
|
2530
|
+
_type: "faq",
|
|
2531
|
+
title: "Where does this content live?",
|
|
2532
|
+
richText: [
|
|
2533
|
+
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.")
|
|
2534
|
+
]
|
|
2535
|
+
},
|
|
2536
|
+
{
|
|
2537
|
+
_id: "faq-blocks",
|
|
2538
|
+
_type: "faq",
|
|
2539
|
+
title: "How do I add a page-builder block?",
|
|
2540
|
+
richText: [
|
|
2541
|
+
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.")
|
|
2542
|
+
]
|
|
2543
|
+
},
|
|
2544
|
+
{
|
|
2545
|
+
_id: "faq-credentials",
|
|
2546
|
+
_type: "faq",
|
|
2547
|
+
title: "What happens without a project id?",
|
|
2548
|
+
richText: [
|
|
2549
|
+
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.")
|
|
2550
|
+
]
|
|
2551
|
+
},
|
|
2552
|
+
{
|
|
2553
|
+
_id: "page-home",
|
|
2554
|
+
_type: "page",
|
|
2555
|
+
title: "Turbo Wizard",
|
|
2556
|
+
description: "The home page, served from Sanity.",
|
|
2557
|
+
slug: { _type: "slug", current: "home" },
|
|
2558
|
+
pageBuilder: [
|
|
2559
|
+
{
|
|
2560
|
+
_key: "hero",
|
|
2561
|
+
_type: "hero",
|
|
2562
|
+
badge: "Scaffolded",
|
|
2563
|
+
title: "Content from Sanity, rendered by your app",
|
|
2564
|
+
richText: [
|
|
2565
|
+
para("h1", "This page lives in the Studio. Edit it there and it changes here — every block below came out of the same dataset.")
|
|
2566
|
+
],
|
|
2567
|
+
buttons: [
|
|
2568
|
+
button("hb1", "Read the blog", internal("blog-page-builder")),
|
|
2569
|
+
button("hb2", "Sanity docs", external("https://www.sanity.io/docs"), "outline")
|
|
2570
|
+
]
|
|
2571
|
+
},
|
|
2572
|
+
{
|
|
2573
|
+
_key: "features",
|
|
2574
|
+
_type: "featureCardsIcon",
|
|
2575
|
+
eyebrow: "What you got",
|
|
2576
|
+
title: "Wired end to end",
|
|
2577
|
+
cards: [
|
|
2578
|
+
{
|
|
2579
|
+
_key: "f1",
|
|
2580
|
+
_type: "featureCardIcon",
|
|
2581
|
+
icon: "layers",
|
|
2582
|
+
title: "Page builder",
|
|
2583
|
+
richText: [para("f1a", "Seven block types, composed per page in the Studio.")]
|
|
2584
|
+
},
|
|
2585
|
+
{
|
|
2586
|
+
_key: "f2",
|
|
2587
|
+
_type: "featureCardIcon",
|
|
2588
|
+
icon: "pen-line",
|
|
2589
|
+
title: "Blog",
|
|
2590
|
+
richText: [para("f2a", "Posts with authors, dates and Portable Text bodies.")]
|
|
2591
|
+
},
|
|
2592
|
+
{
|
|
2593
|
+
_key: "f3",
|
|
2594
|
+
_type: "featureCardIcon",
|
|
2595
|
+
icon: "shield-check",
|
|
2596
|
+
title: "Typed queries",
|
|
2597
|
+
richText: [para("f3a", "defineQuery plus typegen, so results are not any.")]
|
|
2598
|
+
}
|
|
2599
|
+
]
|
|
2600
|
+
},
|
|
2601
|
+
{
|
|
2602
|
+
_key: "cards",
|
|
2603
|
+
_type: "imageLinkCards",
|
|
2604
|
+
eyebrow: "Go deeper",
|
|
2605
|
+
title: "Where to look next",
|
|
2606
|
+
cards: [
|
|
2607
|
+
{
|
|
2608
|
+
_key: "c1",
|
|
2609
|
+
_type: "imageLinkCard",
|
|
2610
|
+
title: "The schema",
|
|
2611
|
+
description: "Documents, blocks and the reusable definitions they share.",
|
|
2612
|
+
url: external("https://www.sanity.io/docs/schema-types")
|
|
2613
|
+
},
|
|
2614
|
+
{
|
|
2615
|
+
_key: "c2",
|
|
2616
|
+
_type: "imageLinkCard",
|
|
2617
|
+
title: "About this starter",
|
|
2618
|
+
description: "A second page, routed by its slug.",
|
|
2619
|
+
url: internal("page-about")
|
|
2620
|
+
}
|
|
2621
|
+
]
|
|
2622
|
+
},
|
|
2623
|
+
{
|
|
2624
|
+
_key: "faqs",
|
|
2625
|
+
_type: "faqAccordion",
|
|
2626
|
+
eyebrow: "Questions",
|
|
2627
|
+
title: "The ones that come up first",
|
|
2628
|
+
faqs: [
|
|
2629
|
+
{ _key: "q1", _type: "reference", _ref: "faq-content" },
|
|
2630
|
+
{ _key: "q2", _type: "reference", _ref: "faq-blocks" },
|
|
2631
|
+
{ _key: "q3", _type: "reference", _ref: "faq-credentials" }
|
|
2632
|
+
]
|
|
2633
|
+
},
|
|
2634
|
+
{
|
|
2635
|
+
_key: "prose",
|
|
2636
|
+
_type: "richTextBlock",
|
|
2637
|
+
eyebrow: "How it fits together",
|
|
2638
|
+
title: "One schema, two frontends",
|
|
2639
|
+
richText: [
|
|
2640
|
+
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."),
|
|
2641
|
+
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.")
|
|
2642
|
+
]
|
|
2643
|
+
},
|
|
2644
|
+
{
|
|
2645
|
+
_key: "newsletter",
|
|
2646
|
+
_type: "subscribeNewsletter",
|
|
2647
|
+
title: "Ship notes",
|
|
2648
|
+
subTitle: [para("n1", "Occasional posts about the stack this project scaffolds.")],
|
|
2649
|
+
helperText: [para("n2", "No spam. The form is a stub until you wire it up.")]
|
|
2650
|
+
},
|
|
2651
|
+
{
|
|
2652
|
+
_key: "cta",
|
|
2653
|
+
_type: "cta",
|
|
2654
|
+
eyebrow: "Your turn",
|
|
2655
|
+
title: "Open the Studio",
|
|
2656
|
+
richText: [
|
|
2657
|
+
para("ct1", "Edit this page in the Studio — pnpm dev:studio — then publish and reload.")
|
|
2658
|
+
],
|
|
2659
|
+
buttons: [button("cb1", "Read the blog", internal("blog-typed-groq"))]
|
|
2660
|
+
}
|
|
2661
|
+
]
|
|
2662
|
+
},
|
|
2663
|
+
{
|
|
2664
|
+
_id: "page-about",
|
|
2665
|
+
_type: "page",
|
|
2666
|
+
title: "About",
|
|
2667
|
+
description: "A second page, to show routing by slug.",
|
|
2668
|
+
slug: { _type: "slug", current: "about" },
|
|
2669
|
+
pageBuilder: [
|
|
2670
|
+
{
|
|
2671
|
+
_key: "hero",
|
|
2672
|
+
_type: "hero",
|
|
2673
|
+
title: "About this starter",
|
|
2674
|
+
richText: [para("ab1", "Pages are routed by their slug. This one is /about.")]
|
|
2675
|
+
},
|
|
2676
|
+
{
|
|
2677
|
+
_key: "prose",
|
|
2678
|
+
_type: "richTextBlock",
|
|
2679
|
+
title: "Add your own",
|
|
2680
|
+
richText: [
|
|
2681
|
+
para("ab2", "Create a page in the Studio, give it a slug, publish it, and it appears at that path on the next build.")
|
|
2682
|
+
]
|
|
2683
|
+
}
|
|
2684
|
+
]
|
|
2685
|
+
},
|
|
2686
|
+
{
|
|
2687
|
+
_id: "blog-page-builder",
|
|
2688
|
+
_type: "blog",
|
|
2689
|
+
title: "A page builder is just an array",
|
|
2690
|
+
description: "The page-builder pattern in one sentence: a document holds an array of objects, and the frontend renders each one by its _type.",
|
|
2691
|
+
slug: { _type: "slug", current: "a-page-builder-is-just-an-array" },
|
|
2692
|
+
publishedAt: "2026-01-14",
|
|
2693
|
+
authors: [{ _key: "a1", _type: "reference", _ref: "author-nova" }],
|
|
2694
|
+
richText: [
|
|
2695
|
+
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."),
|
|
2696
|
+
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.")
|
|
2697
|
+
]
|
|
2698
|
+
},
|
|
2699
|
+
{
|
|
2700
|
+
_id: "blog-typed-groq",
|
|
2701
|
+
_type: "blog",
|
|
2702
|
+
title: "Typed GROQ, without hand-writing types",
|
|
2703
|
+
description: "defineQuery marks a string as a query, sanity typegen reads the schema, and query results stop being any.",
|
|
2704
|
+
slug: { _type: "slug", current: "typed-groq" },
|
|
2705
|
+
publishedAt: "2026-01-28",
|
|
2706
|
+
authors: [{ _key: "a1", _type: "reference", _ref: "author-nova" }],
|
|
2707
|
+
richText: [
|
|
2708
|
+
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."),
|
|
2709
|
+
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.")
|
|
2710
|
+
]
|
|
2711
|
+
},
|
|
2712
|
+
{
|
|
2713
|
+
_id: "blog-editors",
|
|
2714
|
+
_type: "blog",
|
|
2715
|
+
title: "Write the schema for the person editing it",
|
|
2716
|
+
description: "Field descriptions, previews and singletons are not polish. They are the difference between a Studio someone uses and one they avoid.",
|
|
2717
|
+
slug: { _type: "slug", current: "write-the-schema-for-the-editor" },
|
|
2718
|
+
publishedAt: "2026-02-09",
|
|
2719
|
+
authors: [{ _key: "a1", _type: "reference", _ref: "author-atlas" }],
|
|
2720
|
+
richText: [
|
|
2721
|
+
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."),
|
|
2722
|
+
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.")
|
|
2723
|
+
]
|
|
2724
|
+
}
|
|
2725
|
+
];
|
|
2726
|
+
SEED_NDJSON = `${DOCS.map((doc) => JSON.stringify(doc)).join(`
|
|
2727
|
+
`)}
|
|
2728
|
+
`;
|
|
2729
|
+
});
|
|
2730
|
+
|
|
2731
|
+
// ../../packages/registry/src/content/sanity/studio.ts
|
|
2732
|
+
var j5 = (value) => `${JSON.stringify(value, null, 2)}
|
|
2733
|
+
`, 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.
|
|
2734
|
+
# A Studio only reads SANITY_STUDIO_*-prefixed variables.
|
|
2735
|
+
SANITY_STUDIO_PROJECT_ID=
|
|
2736
|
+
SANITY_STUDIO_DATASET=
|
|
2737
|
+
# Where Presentation points its preview iframe (the site, not the Studio).
|
|
2738
|
+
SANITY_STUDIO_PRESENTATION_URL=${siteUrl}
|
|
2739
|
+
`, STUDIO_ENV_EXAMPLE_NEXT, STUDIO_ENV_EXAMPLE_ASTRO, STUDIO_CLI = `import { defineCliConfig } from "sanity/cli";
|
|
2740
|
+
|
|
2741
|
+
export default defineCliConfig({
|
|
2742
|
+
api: {
|
|
2743
|
+
projectId: process.env.SANITY_STUDIO_PROJECT_ID || "placeholder",
|
|
2744
|
+
dataset: process.env.SANITY_STUDIO_DATASET || "production",
|
|
2745
|
+
},
|
|
2746
|
+
// The Studio owns extraction and typegen — the CLI has to run where sanity.config.ts
|
|
2747
|
+
// lives — but the generated types land beside the queries they type, in
|
|
2748
|
+
// @workspace/sanity. Paths are relative to apps/studio; \`schema\` defaults to the
|
|
2749
|
+
// schema.json that \`sanity schema extract\` writes here.
|
|
2750
|
+
typegen: {
|
|
2751
|
+
path: "../../packages/sanity/src/**/*.{ts,tsx}",
|
|
2752
|
+
generates: "../../packages/sanity/src/sanity.types.ts",
|
|
2753
|
+
},
|
|
2754
|
+
});
|
|
2755
|
+
`, studioConfig = (siteUrl, previewMode) => `import { defineConfig } from "sanity";
|
|
2756
|
+
import { presentationTool } from "sanity/presentation";
|
|
2757
|
+
import { structureTool } from "sanity/structure";
|
|
2758
|
+
import { locations, mainDocuments } from "./presentation";
|
|
2759
|
+
import { schemaTypes } from "./schemaTypes/index";
|
|
2760
|
+
import { singletonActions, singletonTypes, structure } from "./structure";
|
|
2761
|
+
|
|
2762
|
+
export default defineConfig({
|
|
2763
|
+
name: "default",
|
|
2764
|
+
title: "Studio",
|
|
2765
|
+
projectId: process.env.SANITY_STUDIO_PROJECT_ID || "placeholder",
|
|
2766
|
+
dataset: process.env.SANITY_STUDIO_DATASET || "production",
|
|
2767
|
+
plugins: [
|
|
2768
|
+
presentationTool({
|
|
2769
|
+
resolve: { mainDocuments, locations },
|
|
2770
|
+
previewUrl: {
|
|
2771
|
+
// Absolute, unlike an embedded Studio's "/": the site is a different origin now.
|
|
2772
|
+
initial: process.env.SANITY_STUDIO_PRESENTATION_URL || "${siteUrl}",${previewMode}
|
|
2773
|
+
},
|
|
2774
|
+
}),
|
|
2775
|
+
structureTool({ structure }),
|
|
2776
|
+
],
|
|
2777
|
+
schema: { types: schemaTypes },
|
|
2778
|
+
${DOCUMENT_GUARD}
|
|
2779
|
+
});
|
|
2780
|
+
`, STUDIO_CONFIG_NEXT, STUDIO_CONFIG_ASTRO;
|
|
2781
|
+
var init_studio = __esm(() => {
|
|
2782
|
+
STUDIO_PACKAGE_JSON = j5({
|
|
2783
|
+
name: "studio",
|
|
2784
|
+
version: "0.0.0",
|
|
2785
|
+
private: true,
|
|
2786
|
+
type: "module",
|
|
2787
|
+
scripts: {
|
|
2788
|
+
build: "sanity build",
|
|
2789
|
+
"check-types": "tsc --noEmit",
|
|
2790
|
+
dev: "sanity dev",
|
|
2791
|
+
extract: "sanity schema extract --enforce-required-fields",
|
|
2792
|
+
"sanity:seed": "sanity dataset import seed.ndjson",
|
|
2793
|
+
type: "sanity typegen generate"
|
|
2794
|
+
},
|
|
2795
|
+
dependencies: {
|
|
2796
|
+
"@workspace/sanity-blocks": "workspace:*",
|
|
2797
|
+
react: "^19.0.0",
|
|
2798
|
+
"react-dom": "^19.0.0",
|
|
2799
|
+
sanity: "^5.31.1",
|
|
2800
|
+
"styled-components": "^6.1.15"
|
|
2801
|
+
},
|
|
2802
|
+
devDependencies: {
|
|
2803
|
+
"@types/node": "^22.10.0",
|
|
2804
|
+
"@types/react": "^19.0.0",
|
|
2805
|
+
"@workspace/typescript-config": "workspace:*",
|
|
2806
|
+
typescript: "^5.9.0"
|
|
2807
|
+
}
|
|
2808
|
+
});
|
|
2809
|
+
STUDIO_TSCONFIG = j5({
|
|
2810
|
+
extends: "@workspace/typescript-config/react-library.json",
|
|
2811
|
+
include: ["**/*.ts", "**/*.tsx"],
|
|
2812
|
+
exclude: ["node_modules", "dist"]
|
|
2813
|
+
});
|
|
2814
|
+
STUDIO_ENV_EXAMPLE_NEXT = studioEnvExample(NEXT_SITE_URL);
|
|
2815
|
+
STUDIO_ENV_EXAMPLE_ASTRO = studioEnvExample(ASTRO_SITE_URL);
|
|
2816
|
+
STUDIO_CONFIG_NEXT = studioConfig(NEXT_SITE_URL, `
|
|
2817
|
+
previewMode: {
|
|
2818
|
+
enable: "/api/draft-mode/enable",
|
|
2819
|
+
// Presentation never calls this itself (documented as not yet implemented);
|
|
2820
|
+
// editors leave draft mode via <DisableDraftMode/>, which links to the route.
|
|
2821
|
+
disable: "/api/draft-mode/disable",
|
|
2822
|
+
},`);
|
|
2823
|
+
STUDIO_CONFIG_ASTRO = studioConfig(ASTRO_SITE_URL, `
|
|
2824
|
+
// No previewMode: Astro has no draft-mode routes. Draft content comes from
|
|
2825
|
+
// loadQuery's perspective switch (PUBLIC_SANITY_VISUAL_EDITING_ENABLED + a read
|
|
2826
|
+
// token), which needs a rendering server — \`astro dev\`, or an SSR adapter.`);
|
|
2827
|
+
});
|
|
2828
|
+
|
|
2829
|
+
// ../../packages/registry/src/content/sanity/types.ts
|
|
2830
|
+
var SANITY_TYPES = `/**
|
|
2831
|
+
* ---------------------------------------------------------------------------------
|
|
2832
|
+
* This file has been generated by Sanity TypeGen.
|
|
2833
|
+
* Command: \`sanity typegen generate\`
|
|
2834
|
+
*
|
|
2835
|
+
* Any modifications made directly to this file will be overwritten the next time
|
|
2836
|
+
* the TypeScript definitions are generated. Please make changes to the Sanity
|
|
2837
|
+
* schema definitions and/or GROQ queries if you need to update these types.
|
|
2838
|
+
*
|
|
2839
|
+
* For more information on how to use Sanity TypeGen, visit the official documentation:
|
|
2840
|
+
* https://www.sanity.io/docs/sanity-typegen
|
|
2841
|
+
* ---------------------------------------------------------------------------------
|
|
1392
2842
|
*/
|
|
1393
|
-
export async function loadQuery<T>(
|
|
1394
|
-
query: string,
|
|
1395
|
-
params: Record<string, unknown> = {},
|
|
1396
|
-
): Promise<T | null> {
|
|
1397
|
-
if (!hasSanityCredentials) return null;
|
|
1398
|
-
if (visualEditingEnabled && !readToken) {
|
|
1399
|
-
throw new Error(
|
|
1400
|
-
"SANITY_API_READ_TOKEN is required when PUBLIC_SANITY_VISUAL_EDITING_ENABLED is true.",
|
|
1401
|
-
);
|
|
1402
|
-
}
|
|
1403
|
-
try {
|
|
1404
|
-
const { result } = await sanityClient.fetch<T>(query, params, {
|
|
1405
|
-
filterResponse: false,
|
|
1406
|
-
perspective: visualEditingEnabled ? "drafts" : "published",
|
|
1407
|
-
resultSourceMap: visualEditingEnabled ? "withKeyArraySelector" : false,
|
|
1408
|
-
stega: visualEditingEnabled,
|
|
1409
|
-
...(visualEditingEnabled ? { token: readToken } : {}),
|
|
1410
|
-
useCdn: !visualEditingEnabled,
|
|
1411
|
-
});
|
|
1412
|
-
return result;
|
|
1413
|
-
} catch (error) {
|
|
1414
|
-
console.warn("[sanity] query failed:", error);
|
|
1415
|
-
return null;
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
`, ASTRO_IMAGE = `import { createImageUrlBuilder } from "@sanity/image-url";
|
|
1419
|
-
import { dataset, projectId } from "./env";
|
|
1420
2843
|
|
|
1421
|
-
|
|
2844
|
+
export declare const internalGroqTypeReferenceTo: unique symbol;
|
|
1422
2845
|
|
|
1423
|
-
|
|
1424
|
-
export
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
2846
|
+
// Source: schema.json
|
|
2847
|
+
export type SanityImageAssetReference = {
|
|
2848
|
+
_ref: string;
|
|
2849
|
+
_type: "reference";
|
|
2850
|
+
_weak?: boolean;
|
|
2851
|
+
[internalGroqTypeReferenceTo]?: "sanity.imageAsset";
|
|
2852
|
+
};
|
|
1430
2853
|
|
|
1431
|
-
type
|
|
2854
|
+
export type Image1 = {
|
|
2855
|
+
asset?: SanityImageAssetReference;
|
|
2856
|
+
media?: unknown; // Unable to locate the referenced type "image.media" in schema
|
|
2857
|
+
hotspot?: SanityImageHotspot;
|
|
2858
|
+
crop?: SanityImageCrop;
|
|
2859
|
+
alt?: string;
|
|
2860
|
+
_type: "image";
|
|
2861
|
+
};
|
|
1432
2862
|
|
|
1433
|
-
export type
|
|
1434
|
-
|
|
1435
|
-
|
|
2863
|
+
export type Cta = {
|
|
2864
|
+
_type: "cta";
|
|
2865
|
+
eyebrow?: string;
|
|
2866
|
+
title?: string;
|
|
2867
|
+
richText?: RichText;
|
|
2868
|
+
buttons?: Array<
|
|
2869
|
+
{
|
|
2870
|
+
_key: string;
|
|
2871
|
+
} & Button
|
|
2872
|
+
>;
|
|
2873
|
+
};
|
|
2874
|
+
|
|
2875
|
+
export type SubscribeNewsletter = {
|
|
2876
|
+
_type: "subscribeNewsletter";
|
|
2877
|
+
title?: string;
|
|
2878
|
+
subTitle?: RichText;
|
|
2879
|
+
helperText?: RichText;
|
|
2880
|
+
};
|
|
2881
|
+
|
|
2882
|
+
export type FaqReference = {
|
|
2883
|
+
_ref: string;
|
|
2884
|
+
_type: "reference";
|
|
2885
|
+
_weak?: boolean;
|
|
2886
|
+
[internalGroqTypeReferenceTo]?: "faq";
|
|
2887
|
+
};
|
|
2888
|
+
|
|
2889
|
+
export type FaqAccordion = {
|
|
2890
|
+
_type: "faqAccordion";
|
|
2891
|
+
eyebrow?: string;
|
|
2892
|
+
title: string;
|
|
2893
|
+
subtitle?: string;
|
|
2894
|
+
faqs?: Array<
|
|
2895
|
+
{
|
|
2896
|
+
_key: string;
|
|
2897
|
+
} & FaqReference
|
|
2898
|
+
>;
|
|
2899
|
+
};
|
|
2900
|
+
|
|
2901
|
+
export type ImageLinkCards = {
|
|
2902
|
+
_type: "imageLinkCards";
|
|
2903
|
+
eyebrow?: string;
|
|
2904
|
+
title: string;
|
|
2905
|
+
richText?: RichText;
|
|
2906
|
+
buttons?: Array<
|
|
2907
|
+
{
|
|
2908
|
+
_key: string;
|
|
2909
|
+
} & Button
|
|
2910
|
+
>;
|
|
2911
|
+
cards?: Array<{
|
|
2912
|
+
title: string;
|
|
2913
|
+
description?: string;
|
|
2914
|
+
image?: Image1;
|
|
2915
|
+
url?: CustomUrl;
|
|
2916
|
+
_type: "imageLinkCard";
|
|
2917
|
+
_key: string;
|
|
2918
|
+
}>;
|
|
2919
|
+
};
|
|
2920
|
+
|
|
2921
|
+
export type FeatureCardsIcon = {
|
|
2922
|
+
_type: "featureCardsIcon";
|
|
2923
|
+
eyebrow?: string;
|
|
2924
|
+
title?: string;
|
|
2925
|
+
richText?: RichText;
|
|
2926
|
+
cards?: Array<{
|
|
2927
|
+
icon?: string;
|
|
2928
|
+
title?: string;
|
|
2929
|
+
richText?: RichText;
|
|
2930
|
+
_type: "featureCardIcon";
|
|
2931
|
+
_key: string;
|
|
2932
|
+
}>;
|
|
2933
|
+
};
|
|
2934
|
+
|
|
2935
|
+
export type RichTextBlock = {
|
|
2936
|
+
_type: "richTextBlock";
|
|
2937
|
+
eyebrow?: string;
|
|
2938
|
+
title?: string;
|
|
2939
|
+
richText?: RichText;
|
|
2940
|
+
};
|
|
2941
|
+
|
|
2942
|
+
export type Hero = {
|
|
2943
|
+
_type: "hero";
|
|
2944
|
+
badge?: string;
|
|
2945
|
+
title?: string;
|
|
2946
|
+
richText?: RichText;
|
|
2947
|
+
image?: Image1;
|
|
2948
|
+
buttons?: Array<
|
|
2949
|
+
{
|
|
2950
|
+
_key: string;
|
|
2951
|
+
} & Button
|
|
2952
|
+
>;
|
|
2953
|
+
};
|
|
2954
|
+
|
|
2955
|
+
export type RichText = Array<
|
|
1436
2956
|
| {
|
|
2957
|
+
children?: Array<{
|
|
2958
|
+
marks?: Array<string>;
|
|
2959
|
+
text?: string;
|
|
2960
|
+
_type: "span";
|
|
2961
|
+
_key: string;
|
|
2962
|
+
}>;
|
|
2963
|
+
style?: "normal" | "h2" | "h3" | "h4";
|
|
2964
|
+
listItem?: "bullet" | "number";
|
|
2965
|
+
markDefs?: Array<{
|
|
2966
|
+
customLink?: CustomUrl;
|
|
2967
|
+
_type: "customLink";
|
|
2968
|
+
_key: string;
|
|
2969
|
+
}>;
|
|
2970
|
+
level?: number;
|
|
2971
|
+
_type: "block";
|
|
1437
2972
|
_key: string;
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
2973
|
+
}
|
|
2974
|
+
| {
|
|
2975
|
+
asset?: SanityImageAssetReference;
|
|
2976
|
+
media?: unknown;
|
|
2977
|
+
hotspot?: SanityImageHotspot;
|
|
2978
|
+
crop?: SanityImageCrop;
|
|
2979
|
+
caption?: string;
|
|
2980
|
+
_type: "image";
|
|
2981
|
+
_key: string;
|
|
2982
|
+
}
|
|
2983
|
+
>;
|
|
2984
|
+
|
|
2985
|
+
export type Button = {
|
|
2986
|
+
_type: "button";
|
|
2987
|
+
variant?: "default" | "secondary" | "outline" | "link";
|
|
2988
|
+
text?: string;
|
|
2989
|
+
url?: CustomUrl;
|
|
2990
|
+
};
|
|
2991
|
+
|
|
2992
|
+
export type PageReference = {
|
|
2993
|
+
_ref: string;
|
|
2994
|
+
_type: "reference";
|
|
2995
|
+
_weak?: boolean;
|
|
2996
|
+
[internalGroqTypeReferenceTo]?: "page";
|
|
2997
|
+
};
|
|
2998
|
+
|
|
2999
|
+
export type BlogReference = {
|
|
3000
|
+
_ref: string;
|
|
3001
|
+
_type: "reference";
|
|
3002
|
+
_weak?: boolean;
|
|
3003
|
+
[internalGroqTypeReferenceTo]?: "blog";
|
|
3004
|
+
};
|
|
3005
|
+
|
|
3006
|
+
export type CustomUrl = {
|
|
3007
|
+
_type: "customUrl";
|
|
3008
|
+
type: "internal" | "external";
|
|
3009
|
+
openInNewTab?: boolean;
|
|
3010
|
+
external?: string;
|
|
3011
|
+
internal?: PageReference | BlogReference;
|
|
3012
|
+
};
|
|
3013
|
+
|
|
3014
|
+
export type Settings = {
|
|
3015
|
+
_id: string;
|
|
3016
|
+
_type: "settings";
|
|
3017
|
+
_createdAt: string;
|
|
3018
|
+
_updatedAt: string;
|
|
3019
|
+
_rev: string;
|
|
3020
|
+
siteTitle: string;
|
|
3021
|
+
siteDescription?: string;
|
|
3022
|
+
socialImage?: {
|
|
3023
|
+
asset?: SanityImageAssetReference;
|
|
3024
|
+
media?: unknown;
|
|
3025
|
+
hotspot?: SanityImageHotspot;
|
|
3026
|
+
crop?: SanityImageCrop;
|
|
3027
|
+
_type: "image";
|
|
3028
|
+
};
|
|
3029
|
+
};
|
|
3030
|
+
|
|
3031
|
+
export type SanityImageCrop = {
|
|
3032
|
+
_type: "sanity.imageCrop";
|
|
3033
|
+
top: number;
|
|
3034
|
+
bottom: number;
|
|
3035
|
+
left: number;
|
|
3036
|
+
right: number;
|
|
3037
|
+
};
|
|
3038
|
+
|
|
3039
|
+
export type SanityImageHotspot = {
|
|
3040
|
+
_type: "sanity.imageHotspot";
|
|
3041
|
+
x: number;
|
|
3042
|
+
y: number;
|
|
3043
|
+
height: number;
|
|
3044
|
+
width: number;
|
|
3045
|
+
};
|
|
1444
3046
|
|
|
1445
|
-
export type
|
|
3047
|
+
export type Faq = {
|
|
1446
3048
|
_id: string;
|
|
3049
|
+
_type: "faq";
|
|
3050
|
+
_createdAt: string;
|
|
3051
|
+
_updatedAt: string;
|
|
3052
|
+
_rev: string;
|
|
3053
|
+
title: string;
|
|
3054
|
+
richText?: RichText;
|
|
3055
|
+
};
|
|
3056
|
+
|
|
3057
|
+
export type Author = {
|
|
3058
|
+
_id: string;
|
|
3059
|
+
_type: "author";
|
|
3060
|
+
_createdAt: string;
|
|
3061
|
+
_updatedAt: string;
|
|
3062
|
+
_rev: string;
|
|
3063
|
+
name: string;
|
|
3064
|
+
position?: string;
|
|
3065
|
+
image?: {
|
|
3066
|
+
asset?: SanityImageAssetReference;
|
|
3067
|
+
media?: unknown;
|
|
3068
|
+
hotspot?: SanityImageHotspot;
|
|
3069
|
+
crop?: SanityImageCrop;
|
|
3070
|
+
_type: "image";
|
|
3071
|
+
};
|
|
3072
|
+
bio?: string;
|
|
3073
|
+
};
|
|
3074
|
+
|
|
3075
|
+
export type AuthorReference = {
|
|
3076
|
+
_ref: string;
|
|
3077
|
+
_type: "reference";
|
|
3078
|
+
_weak?: boolean;
|
|
3079
|
+
[internalGroqTypeReferenceTo]?: "author";
|
|
3080
|
+
};
|
|
3081
|
+
|
|
3082
|
+
export type Blog = {
|
|
3083
|
+
_id: string;
|
|
3084
|
+
_type: "blog";
|
|
3085
|
+
_createdAt: string;
|
|
3086
|
+
_updatedAt: string;
|
|
3087
|
+
_rev: string;
|
|
3088
|
+
title: string;
|
|
3089
|
+
slug: Slug;
|
|
3090
|
+
description?: string;
|
|
3091
|
+
authors?: Array<
|
|
3092
|
+
{
|
|
3093
|
+
_key: string;
|
|
3094
|
+
} & AuthorReference
|
|
3095
|
+
>;
|
|
3096
|
+
publishedAt?: string;
|
|
3097
|
+
image?: {
|
|
3098
|
+
asset?: SanityImageAssetReference;
|
|
3099
|
+
media?: unknown;
|
|
3100
|
+
hotspot?: SanityImageHotspot;
|
|
3101
|
+
crop?: SanityImageCrop;
|
|
3102
|
+
alt?: string;
|
|
3103
|
+
_type: "image";
|
|
3104
|
+
};
|
|
3105
|
+
richText?: RichText;
|
|
3106
|
+
};
|
|
3107
|
+
|
|
3108
|
+
export type Slug = {
|
|
3109
|
+
_type: "slug";
|
|
3110
|
+
current: string;
|
|
3111
|
+
source?: string;
|
|
3112
|
+
};
|
|
3113
|
+
|
|
3114
|
+
export type Page = {
|
|
3115
|
+
_id: string;
|
|
3116
|
+
_type: "page";
|
|
3117
|
+
_createdAt: string;
|
|
3118
|
+
_updatedAt: string;
|
|
3119
|
+
_rev: string;
|
|
3120
|
+
title: string;
|
|
3121
|
+
slug: Slug;
|
|
3122
|
+
description?: string;
|
|
3123
|
+
image?: {
|
|
3124
|
+
asset?: SanityImageAssetReference;
|
|
3125
|
+
media?: unknown;
|
|
3126
|
+
hotspot?: SanityImageHotspot;
|
|
3127
|
+
crop?: SanityImageCrop;
|
|
3128
|
+
_type: "image";
|
|
3129
|
+
};
|
|
3130
|
+
pageBuilder?: Array<
|
|
3131
|
+
| ({
|
|
3132
|
+
_key: string;
|
|
3133
|
+
} & Hero)
|
|
3134
|
+
| ({
|
|
3135
|
+
_key: string;
|
|
3136
|
+
} & RichTextBlock)
|
|
3137
|
+
| ({
|
|
3138
|
+
_key: string;
|
|
3139
|
+
} & FeatureCardsIcon)
|
|
3140
|
+
| ({
|
|
3141
|
+
_key: string;
|
|
3142
|
+
} & ImageLinkCards)
|
|
3143
|
+
| ({
|
|
3144
|
+
_key: string;
|
|
3145
|
+
} & FaqAccordion)
|
|
3146
|
+
| ({
|
|
3147
|
+
_key: string;
|
|
3148
|
+
} & SubscribeNewsletter)
|
|
3149
|
+
| ({
|
|
3150
|
+
_key: string;
|
|
3151
|
+
} & Cta)
|
|
3152
|
+
>;
|
|
3153
|
+
};
|
|
3154
|
+
|
|
3155
|
+
export type SanityImagePaletteSwatch = {
|
|
3156
|
+
_type: "sanity.imagePaletteSwatch";
|
|
3157
|
+
background?: string;
|
|
3158
|
+
foreground?: string;
|
|
3159
|
+
population?: number;
|
|
3160
|
+
title?: string;
|
|
3161
|
+
};
|
|
3162
|
+
|
|
3163
|
+
export type SanityImagePalette = {
|
|
3164
|
+
_type: "sanity.imagePalette";
|
|
3165
|
+
darkMuted?: SanityImagePaletteSwatch;
|
|
3166
|
+
lightVibrant?: SanityImagePaletteSwatch;
|
|
3167
|
+
darkVibrant?: SanityImagePaletteSwatch;
|
|
3168
|
+
vibrant?: SanityImagePaletteSwatch;
|
|
3169
|
+
dominant?: SanityImagePaletteSwatch;
|
|
3170
|
+
lightMuted?: SanityImagePaletteSwatch;
|
|
3171
|
+
muted?: SanityImagePaletteSwatch;
|
|
3172
|
+
};
|
|
3173
|
+
|
|
3174
|
+
export type SanityImageDimensions = {
|
|
3175
|
+
_type: "sanity.imageDimensions";
|
|
3176
|
+
height: number;
|
|
3177
|
+
width: number;
|
|
3178
|
+
aspectRatio: number;
|
|
3179
|
+
};
|
|
3180
|
+
|
|
3181
|
+
export type SanityImageMetadata = {
|
|
3182
|
+
_type: "sanity.imageMetadata";
|
|
3183
|
+
location?: Geopoint;
|
|
3184
|
+
dimensions?: SanityImageDimensions;
|
|
3185
|
+
palette?: SanityImagePalette;
|
|
3186
|
+
lqip?: string;
|
|
3187
|
+
blurHash?: string;
|
|
3188
|
+
thumbHash?: string;
|
|
3189
|
+
hasAlpha?: boolean;
|
|
3190
|
+
isOpaque?: boolean;
|
|
3191
|
+
};
|
|
3192
|
+
|
|
3193
|
+
export type SanityFileAsset = {
|
|
3194
|
+
_id: string;
|
|
3195
|
+
_type: "sanity.fileAsset";
|
|
3196
|
+
_createdAt: string;
|
|
3197
|
+
_updatedAt: string;
|
|
3198
|
+
_rev: string;
|
|
3199
|
+
originalFilename?: string;
|
|
3200
|
+
label?: string;
|
|
3201
|
+
title?: string;
|
|
3202
|
+
description?: string;
|
|
3203
|
+
altText?: string;
|
|
3204
|
+
sha1hash: string;
|
|
3205
|
+
extension: string;
|
|
3206
|
+
mimeType: string;
|
|
3207
|
+
size: number;
|
|
3208
|
+
assetId: string;
|
|
3209
|
+
uploadId?: string;
|
|
3210
|
+
path: string;
|
|
3211
|
+
url: string;
|
|
3212
|
+
source?: SanityAssetSourceData;
|
|
3213
|
+
};
|
|
3214
|
+
|
|
3215
|
+
export type SanityAssetSourceData = {
|
|
3216
|
+
_type: "sanity.assetSourceData";
|
|
3217
|
+
name?: string;
|
|
3218
|
+
id?: string;
|
|
3219
|
+
url?: string;
|
|
3220
|
+
};
|
|
3221
|
+
|
|
3222
|
+
export type SanityImageAsset = {
|
|
3223
|
+
_id: string;
|
|
3224
|
+
_type: "sanity.imageAsset";
|
|
3225
|
+
_createdAt: string;
|
|
3226
|
+
_updatedAt: string;
|
|
3227
|
+
_rev: string;
|
|
3228
|
+
originalFilename?: string;
|
|
3229
|
+
label?: string;
|
|
1447
3230
|
title?: string;
|
|
1448
3231
|
description?: string;
|
|
1449
|
-
|
|
1450
|
-
|
|
3232
|
+
altText?: string;
|
|
3233
|
+
sha1hash: string;
|
|
3234
|
+
extension: string;
|
|
3235
|
+
mimeType: string;
|
|
3236
|
+
size: number;
|
|
3237
|
+
assetId: string;
|
|
3238
|
+
uploadId?: string;
|
|
3239
|
+
path: string;
|
|
3240
|
+
url: string;
|
|
3241
|
+
metadata?: SanityImageMetadata;
|
|
3242
|
+
source?: SanityAssetSourceData;
|
|
3243
|
+
};
|
|
3244
|
+
|
|
3245
|
+
export type Geopoint = {
|
|
3246
|
+
_type: "geopoint";
|
|
3247
|
+
lat?: number;
|
|
3248
|
+
lng?: number;
|
|
3249
|
+
alt?: number;
|
|
3250
|
+
};
|
|
3251
|
+
|
|
3252
|
+
export type AllSanitySchemaTypes =
|
|
3253
|
+
| SanityImageAssetReference
|
|
3254
|
+
| Image1
|
|
3255
|
+
| Cta
|
|
3256
|
+
| SubscribeNewsletter
|
|
3257
|
+
| FaqReference
|
|
3258
|
+
| FaqAccordion
|
|
3259
|
+
| ImageLinkCards
|
|
3260
|
+
| FeatureCardsIcon
|
|
3261
|
+
| RichTextBlock
|
|
3262
|
+
| Hero
|
|
3263
|
+
| RichText
|
|
3264
|
+
| Button
|
|
3265
|
+
| PageReference
|
|
3266
|
+
| BlogReference
|
|
3267
|
+
| CustomUrl
|
|
3268
|
+
| Settings
|
|
3269
|
+
| SanityImageCrop
|
|
3270
|
+
| SanityImageHotspot
|
|
3271
|
+
| Faq
|
|
3272
|
+
| Author
|
|
3273
|
+
| AuthorReference
|
|
3274
|
+
| Blog
|
|
3275
|
+
| Slug
|
|
3276
|
+
| Page
|
|
3277
|
+
| SanityImagePaletteSwatch
|
|
3278
|
+
| SanityImagePalette
|
|
3279
|
+
| SanityImageDimensions
|
|
3280
|
+
| SanityImageMetadata
|
|
3281
|
+
| SanityFileAsset
|
|
3282
|
+
| SanityAssetSourceData
|
|
3283
|
+
| SanityImageAsset
|
|
3284
|
+
| Geopoint;
|
|
3285
|
+
|
|
3286
|
+
// Source: ../../packages/sanity/src/queries.ts
|
|
3287
|
+
// Variable: HOME_PAGE_QUERY
|
|
3288
|
+
// 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, "#" ) } } }}
|
|
3289
|
+
export type HOME_PAGE_QUERY_RESULT = {
|
|
3290
|
+
_id: string;
|
|
3291
|
+
_type: "page";
|
|
3292
|
+
title: string;
|
|
3293
|
+
description: string | null;
|
|
3294
|
+
slug: string;
|
|
3295
|
+
pageBuilder: Array<
|
|
3296
|
+
| {
|
|
3297
|
+
_key: string;
|
|
3298
|
+
_type: "cta";
|
|
3299
|
+
eyebrow?: string;
|
|
3300
|
+
title?: string;
|
|
3301
|
+
richText?: RichText;
|
|
3302
|
+
buttons: Array<{
|
|
3303
|
+
_key: string;
|
|
3304
|
+
_type: "button";
|
|
3305
|
+
text: string | null;
|
|
3306
|
+
variant: "default" | "link" | "outline" | "secondary" | null;
|
|
3307
|
+
openInNewTab: boolean | null;
|
|
3308
|
+
href: string | "/" | "#" | null;
|
|
3309
|
+
}> | null;
|
|
3310
|
+
}
|
|
3311
|
+
| {
|
|
3312
|
+
_key: string;
|
|
3313
|
+
_type: "faqAccordion";
|
|
3314
|
+
eyebrow?: string;
|
|
3315
|
+
title: string;
|
|
3316
|
+
subtitle?: string;
|
|
3317
|
+
faqs: Array<{
|
|
3318
|
+
_id: string;
|
|
3319
|
+
title: string;
|
|
3320
|
+
richText: RichText | null;
|
|
3321
|
+
}> | null;
|
|
3322
|
+
buttons: null;
|
|
3323
|
+
}
|
|
3324
|
+
| {
|
|
3325
|
+
_key: string;
|
|
3326
|
+
_type: "featureCardsIcon";
|
|
3327
|
+
eyebrow?: string;
|
|
3328
|
+
title?: string;
|
|
3329
|
+
richText?: RichText;
|
|
3330
|
+
cards?: Array<{
|
|
3331
|
+
icon?: string;
|
|
3332
|
+
title?: string;
|
|
3333
|
+
richText?: RichText;
|
|
3334
|
+
_type: "featureCardIcon";
|
|
3335
|
+
_key: string;
|
|
3336
|
+
}>;
|
|
3337
|
+
buttons: null;
|
|
3338
|
+
}
|
|
3339
|
+
| {
|
|
3340
|
+
_key: string;
|
|
3341
|
+
_type: "hero";
|
|
3342
|
+
badge?: string;
|
|
3343
|
+
title?: string;
|
|
3344
|
+
richText?: RichText;
|
|
3345
|
+
image?: Image1;
|
|
3346
|
+
buttons: Array<{
|
|
3347
|
+
_key: string;
|
|
3348
|
+
_type: "button";
|
|
3349
|
+
text: string | null;
|
|
3350
|
+
variant: "default" | "link" | "outline" | "secondary" | null;
|
|
3351
|
+
openInNewTab: boolean | null;
|
|
3352
|
+
href: string | "/" | "#" | null;
|
|
3353
|
+
}> | null;
|
|
3354
|
+
}
|
|
3355
|
+
| {
|
|
3356
|
+
_key: string;
|
|
3357
|
+
_type: "imageLinkCards";
|
|
3358
|
+
eyebrow?: string;
|
|
3359
|
+
title: string;
|
|
3360
|
+
richText?: RichText;
|
|
3361
|
+
buttons: Array<{
|
|
3362
|
+
_key: string;
|
|
3363
|
+
_type: "button";
|
|
3364
|
+
text: string | null;
|
|
3365
|
+
variant: "default" | "link" | "outline" | "secondary" | null;
|
|
3366
|
+
openInNewTab: boolean | null;
|
|
3367
|
+
href: string | "/" | "#" | null;
|
|
3368
|
+
}> | null;
|
|
3369
|
+
cards: Array<{
|
|
3370
|
+
title: string;
|
|
3371
|
+
description?: string;
|
|
3372
|
+
image?: Image1;
|
|
3373
|
+
url?: CustomUrl;
|
|
3374
|
+
_type: "imageLinkCard";
|
|
3375
|
+
_key: string;
|
|
3376
|
+
openInNewTab: boolean | null;
|
|
3377
|
+
href: string | "/" | "#" | null;
|
|
3378
|
+
}> | null;
|
|
3379
|
+
}
|
|
3380
|
+
| {
|
|
3381
|
+
_key: string;
|
|
3382
|
+
_type: "richTextBlock";
|
|
3383
|
+
eyebrow?: string;
|
|
3384
|
+
title?: string;
|
|
3385
|
+
richText?: RichText;
|
|
3386
|
+
buttons: null;
|
|
3387
|
+
}
|
|
3388
|
+
| {
|
|
3389
|
+
_key: string;
|
|
3390
|
+
_type: "subscribeNewsletter";
|
|
3391
|
+
title?: string;
|
|
3392
|
+
subTitle?: RichText;
|
|
3393
|
+
helperText?: RichText;
|
|
3394
|
+
buttons: null;
|
|
3395
|
+
}
|
|
3396
|
+
> | null;
|
|
3397
|
+
} | null;
|
|
3398
|
+
|
|
3399
|
+
// Source: ../../packages/sanity/src/queries.ts
|
|
3400
|
+
// Variable: PAGE_QUERY
|
|
3401
|
+
// 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, "#" ) } } }}
|
|
3402
|
+
export type PAGE_QUERY_RESULT = {
|
|
3403
|
+
_id: string;
|
|
3404
|
+
_type: "page";
|
|
3405
|
+
title: string;
|
|
3406
|
+
description: string | null;
|
|
3407
|
+
slug: string;
|
|
3408
|
+
pageBuilder: Array<
|
|
3409
|
+
| {
|
|
3410
|
+
_key: string;
|
|
3411
|
+
_type: "cta";
|
|
3412
|
+
eyebrow?: string;
|
|
3413
|
+
title?: string;
|
|
3414
|
+
richText?: RichText;
|
|
3415
|
+
buttons: Array<{
|
|
3416
|
+
_key: string;
|
|
3417
|
+
_type: "button";
|
|
3418
|
+
text: string | null;
|
|
3419
|
+
variant: "default" | "link" | "outline" | "secondary" | null;
|
|
3420
|
+
openInNewTab: boolean | null;
|
|
3421
|
+
href: string | "/" | "#" | null;
|
|
3422
|
+
}> | null;
|
|
3423
|
+
}
|
|
3424
|
+
| {
|
|
3425
|
+
_key: string;
|
|
3426
|
+
_type: "faqAccordion";
|
|
3427
|
+
eyebrow?: string;
|
|
3428
|
+
title: string;
|
|
3429
|
+
subtitle?: string;
|
|
3430
|
+
faqs: Array<{
|
|
3431
|
+
_id: string;
|
|
3432
|
+
title: string;
|
|
3433
|
+
richText: RichText | null;
|
|
3434
|
+
}> | null;
|
|
3435
|
+
buttons: null;
|
|
3436
|
+
}
|
|
3437
|
+
| {
|
|
3438
|
+
_key: string;
|
|
3439
|
+
_type: "featureCardsIcon";
|
|
3440
|
+
eyebrow?: string;
|
|
3441
|
+
title?: string;
|
|
3442
|
+
richText?: RichText;
|
|
3443
|
+
cards?: Array<{
|
|
3444
|
+
icon?: string;
|
|
3445
|
+
title?: string;
|
|
3446
|
+
richText?: RichText;
|
|
3447
|
+
_type: "featureCardIcon";
|
|
3448
|
+
_key: string;
|
|
3449
|
+
}>;
|
|
3450
|
+
buttons: null;
|
|
3451
|
+
}
|
|
3452
|
+
| {
|
|
3453
|
+
_key: string;
|
|
3454
|
+
_type: "hero";
|
|
3455
|
+
badge?: string;
|
|
3456
|
+
title?: string;
|
|
3457
|
+
richText?: RichText;
|
|
3458
|
+
image?: Image1;
|
|
3459
|
+
buttons: Array<{
|
|
3460
|
+
_key: string;
|
|
3461
|
+
_type: "button";
|
|
3462
|
+
text: string | null;
|
|
3463
|
+
variant: "default" | "link" | "outline" | "secondary" | null;
|
|
3464
|
+
openInNewTab: boolean | null;
|
|
3465
|
+
href: string | "/" | "#" | null;
|
|
3466
|
+
}> | null;
|
|
3467
|
+
}
|
|
3468
|
+
| {
|
|
3469
|
+
_key: string;
|
|
3470
|
+
_type: "imageLinkCards";
|
|
3471
|
+
eyebrow?: string;
|
|
3472
|
+
title: string;
|
|
3473
|
+
richText?: RichText;
|
|
3474
|
+
buttons: Array<{
|
|
3475
|
+
_key: string;
|
|
3476
|
+
_type: "button";
|
|
3477
|
+
text: string | null;
|
|
3478
|
+
variant: "default" | "link" | "outline" | "secondary" | null;
|
|
3479
|
+
openInNewTab: boolean | null;
|
|
3480
|
+
href: string | "/" | "#" | null;
|
|
3481
|
+
}> | null;
|
|
3482
|
+
cards: Array<{
|
|
3483
|
+
title: string;
|
|
3484
|
+
description?: string;
|
|
3485
|
+
image?: Image1;
|
|
3486
|
+
url?: CustomUrl;
|
|
3487
|
+
_type: "imageLinkCard";
|
|
3488
|
+
_key: string;
|
|
3489
|
+
openInNewTab: boolean | null;
|
|
3490
|
+
href: string | "/" | "#" | null;
|
|
3491
|
+
}> | null;
|
|
3492
|
+
}
|
|
3493
|
+
| {
|
|
3494
|
+
_key: string;
|
|
3495
|
+
_type: "richTextBlock";
|
|
3496
|
+
eyebrow?: string;
|
|
3497
|
+
title?: string;
|
|
3498
|
+
richText?: RichText;
|
|
3499
|
+
buttons: null;
|
|
3500
|
+
}
|
|
3501
|
+
| {
|
|
3502
|
+
_key: string;
|
|
3503
|
+
_type: "subscribeNewsletter";
|
|
3504
|
+
title?: string;
|
|
3505
|
+
subTitle?: RichText;
|
|
3506
|
+
helperText?: RichText;
|
|
3507
|
+
buttons: null;
|
|
3508
|
+
}
|
|
3509
|
+
> | null;
|
|
1451
3510
|
} | null;
|
|
1452
|
-
`, ASTRO_PAGE_BUILDER = `---
|
|
1453
|
-
import { PortableText } from "astro-portabletext";
|
|
1454
|
-
import type { PageBlock } from "../../sanity/types";
|
|
1455
|
-
import { urlFor } from "../../sanity/image";
|
|
1456
3511
|
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
3512
|
+
// Source: ../../packages/sanity/src/queries.ts
|
|
3513
|
+
// Variable: PAGE_SLUGS_QUERY
|
|
3514
|
+
// Query: *[_type == "page" && defined(slug.current) && slug.current != "home"].slug.current
|
|
3515
|
+
export type PAGE_SLUGS_QUERY_RESULT = Array<string>;
|
|
1460
3516
|
|
|
1461
|
-
|
|
1462
|
-
|
|
3517
|
+
// Source: ../../packages/sanity/src/queries.ts
|
|
3518
|
+
// Variable: BLOG_INDEX_QUERY
|
|
3519
|
+
// Query: *[_type == "blog" && defined(slug.current)]|order(publishedAt desc){ _id, title, description, publishedAt, image, "slug": slug.current, "authors": authors[]->{ _id, name, position, image }}
|
|
3520
|
+
export type BLOG_INDEX_QUERY_RESULT = Array<{
|
|
3521
|
+
_id: string;
|
|
3522
|
+
title: string;
|
|
3523
|
+
description: string | null;
|
|
3524
|
+
publishedAt: string | null;
|
|
3525
|
+
image: {
|
|
3526
|
+
asset?: SanityImageAssetReference;
|
|
3527
|
+
media?: unknown;
|
|
3528
|
+
hotspot?: SanityImageHotspot;
|
|
3529
|
+
crop?: SanityImageCrop;
|
|
3530
|
+
alt?: string;
|
|
3531
|
+
_type: "image";
|
|
3532
|
+
} | null;
|
|
3533
|
+
slug: string;
|
|
3534
|
+
authors: Array<{
|
|
3535
|
+
_id: string;
|
|
3536
|
+
name: string;
|
|
3537
|
+
position: string | null;
|
|
3538
|
+
image: {
|
|
3539
|
+
asset?: SanityImageAssetReference;
|
|
3540
|
+
media?: unknown;
|
|
3541
|
+
hotspot?: SanityImageHotspot;
|
|
3542
|
+
crop?: SanityImageCrop;
|
|
3543
|
+
_type: "image";
|
|
3544
|
+
} | null;
|
|
3545
|
+
}> | null;
|
|
3546
|
+
}>;
|
|
3547
|
+
|
|
3548
|
+
// Source: ../../packages/sanity/src/queries.ts
|
|
3549
|
+
// Variable: BLOG_QUERY
|
|
3550
|
+
// Query: *[_type == "blog" && slug.current == $slug][0]{ _id, title, description, publishedAt, image, "slug": slug.current, "authors": authors[]->{ _id, name, position, image }, richText}
|
|
3551
|
+
export type BLOG_QUERY_RESULT = {
|
|
3552
|
+
_id: string;
|
|
3553
|
+
title: string;
|
|
3554
|
+
description: string | null;
|
|
3555
|
+
publishedAt: string | null;
|
|
3556
|
+
image: {
|
|
3557
|
+
asset?: SanityImageAssetReference;
|
|
3558
|
+
media?: unknown;
|
|
3559
|
+
hotspot?: SanityImageHotspot;
|
|
3560
|
+
crop?: SanityImageCrop;
|
|
3561
|
+
alt?: string;
|
|
3562
|
+
_type: "image";
|
|
3563
|
+
} | null;
|
|
3564
|
+
slug: string;
|
|
3565
|
+
authors: Array<{
|
|
3566
|
+
_id: string;
|
|
3567
|
+
name: string;
|
|
3568
|
+
position: string | null;
|
|
3569
|
+
image: {
|
|
3570
|
+
asset?: SanityImageAssetReference;
|
|
3571
|
+
media?: unknown;
|
|
3572
|
+
hotspot?: SanityImageHotspot;
|
|
3573
|
+
crop?: SanityImageCrop;
|
|
3574
|
+
_type: "image";
|
|
3575
|
+
} | null;
|
|
3576
|
+
}> | null;
|
|
3577
|
+
richText: RichText | null;
|
|
3578
|
+
} | null;
|
|
1463
3579
|
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
}
|
|
1484
|
-
if (block._type === "cta") {
|
|
1485
|
-
return (
|
|
1486
|
-
<section>
|
|
1487
|
-
<h2>{block.heading}</h2>
|
|
1488
|
-
{block.text && <p>{block.text}</p>}
|
|
1489
|
-
{block.buttonHref && block.buttonLabel && (
|
|
1490
|
-
<a href={block.buttonHref}>{block.buttonLabel}</a>
|
|
1491
|
-
)}
|
|
1492
|
-
</section>
|
|
1493
|
-
);
|
|
1494
|
-
}
|
|
1495
|
-
return null;
|
|
1496
|
-
})
|
|
1497
|
-
}
|
|
1498
|
-
`, ASTRO_SLUG_PAGE = `---
|
|
1499
|
-
import { VisualEditing } from "@sanity/astro/visual-editing";
|
|
1500
|
-
import PageBuilder from "../components/PageBuilder.astro";
|
|
1501
|
-
import { visualEditingEnabled } from "../../sanity/env";
|
|
1502
|
-
import { loadQuery } from "../../sanity/fetch";
|
|
1503
|
-
import { PAGE_QUERY, PAGE_SLUGS_QUERY } from "../../sanity/queries";
|
|
1504
|
-
import type { SanityPage } from "../../sanity/types";
|
|
3580
|
+
// Source: ../../packages/sanity/src/queries.ts
|
|
3581
|
+
// Variable: BLOG_SLUGS_QUERY
|
|
3582
|
+
// Query: *[_type == "blog" && defined(slug.current)].slug.current
|
|
3583
|
+
export type BLOG_SLUGS_QUERY_RESULT = Array<string>;
|
|
3584
|
+
|
|
3585
|
+
// Source: ../../packages/sanity/src/queries.ts
|
|
3586
|
+
// Variable: SETTINGS_QUERY
|
|
3587
|
+
// Query: *[_type == "settings"][0]{ siteTitle, siteDescription, socialImage }
|
|
3588
|
+
export type SETTINGS_QUERY_RESULT = {
|
|
3589
|
+
siteTitle: string;
|
|
3590
|
+
siteDescription: string | null;
|
|
3591
|
+
socialImage: {
|
|
3592
|
+
asset?: SanityImageAssetReference;
|
|
3593
|
+
media?: unknown;
|
|
3594
|
+
hotspot?: SanityImageHotspot;
|
|
3595
|
+
crop?: SanityImageCrop;
|
|
3596
|
+
_type: "image";
|
|
3597
|
+
} | null;
|
|
3598
|
+
} | null;
|
|
1505
3599
|
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
3600
|
+
// Query TypeMap
|
|
3601
|
+
import "@sanity/client";
|
|
3602
|
+
declare module "@sanity/client" {
|
|
3603
|
+
interface SanityQueries {
|
|
3604
|
+
'*[_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;
|
|
3605
|
+
'*[_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;
|
|
3606
|
+
'*[_type == "page" && defined(slug.current) && slug.current != "home"].slug.current': PAGE_SLUGS_QUERY_RESULT;
|
|
3607
|
+
'*[_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;
|
|
3608
|
+
'*[_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;
|
|
3609
|
+
'*[_type == "blog" && defined(slug.current)].slug.current': BLOG_SLUGS_QUERY_RESULT;
|
|
3610
|
+
'*[_type == "settings"][0]{ siteTitle, siteDescription, socialImage }': SETTINGS_QUERY_RESULT;
|
|
3611
|
+
}
|
|
1510
3612
|
}
|
|
3613
|
+
`;
|
|
1511
3614
|
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
<html lang="en">
|
|
1516
|
-
<head>
|
|
1517
|
-
<meta charset="utf-8" />
|
|
1518
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1519
|
-
<title>{page?.title ?? "web"}</title>
|
|
1520
|
-
</head>
|
|
1521
|
-
<body style="font-family: system-ui, sans-serif; padding: 3rem;">
|
|
1522
|
-
<h1>{page?.title}</h1>
|
|
1523
|
-
<PageBuilder blocks={page?.pageBuilder ?? []} />
|
|
1524
|
-
<VisualEditing enabled={visualEditingEnabled} />
|
|
1525
|
-
</body>
|
|
1526
|
-
</html>
|
|
1527
|
-
`, ASTRO_SANITY_CONFIG, ASTRO_SANITY_CLI = `import { defineCliConfig } from "sanity/cli";
|
|
1528
|
-
|
|
1529
|
-
// Run from apps/web. The Sanity CLI is a Node process, so it reads process.env rather
|
|
1530
|
-
// than Astro's import.meta.env; astro.config.mjs loads apps/web/.env into it.
|
|
1531
|
-
export default defineCliConfig({
|
|
1532
|
-
api: {
|
|
1533
|
-
projectId: process.env.PUBLIC_SANITY_PROJECT_ID || "placeholder",
|
|
1534
|
-
dataset: process.env.PUBLIC_SANITY_DATASET || "production",
|
|
1535
|
-
},
|
|
1536
|
-
typegen: {
|
|
1537
|
-
path: "./sanity/**/*.{ts,tsx}",
|
|
1538
|
-
schema: "sanity/schema.json",
|
|
1539
|
-
generates: "sanity/sanity.types.ts",
|
|
1540
|
-
},
|
|
1541
|
-
});
|
|
1542
|
-
`, 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.
|
|
1543
|
-
try {
|
|
1544
|
-
process.loadEnvFile(".env");
|
|
1545
|
-
} catch {
|
|
1546
|
-
// No .env — the placeholder ids below keep the build working.
|
|
1547
|
-
}
|
|
1548
|
-
`, prependAstroConfigStatement = (content, block, marker) => content.includes(marker) ? content : content.replace("export default defineConfig({", `${block}
|
|
3615
|
+
// ../../packages/registry/src/content/sanity/index.ts
|
|
3616
|
+
var j6 = (value) => `${JSON.stringify(value, null, 2)}
|
|
3617
|
+
`, SANITY_PACKAGE_JSON, SANITY_BLOCKS_PACKAGE_JSON, prependAstroConfigStatement = (content, block, marker) => content.includes(marker) ? content : content.replace("export default defineConfig({", `${block}
|
|
1549
3618
|
export default defineConfig({`), addAstroOptimizeDeps = (content) => {
|
|
1550
3619
|
if (content.includes("react/compiler-runtime"))
|
|
1551
3620
|
return content;
|
|
@@ -1562,23 +3631,12 @@ export default defineConfig({`), addAstroOptimizeDeps = (content) => {
|
|
|
1562
3631
|
return content.replace(/vite:\s*\{/, (m) => `${m} ${field}`);
|
|
1563
3632
|
return content.replace(/defineConfig\(\{/, (m) => `${m}
|
|
1564
3633
|
vite: { ${field} },`);
|
|
1565
|
-
},
|
|
1566
|
-
const pkg = JSON.parse(pkgJson);
|
|
1567
|
-
const merged = { ...pkg.scripts, ...scripts };
|
|
1568
|
-
const sorted = Object.fromEntries(Object.entries(merged).sort(([a], [b]) => a.localeCompare(b)));
|
|
1569
|
-
return `${JSON.stringify({ ...pkg, scripts: sorted }, null, 2)}
|
|
1570
|
-
`;
|
|
1571
|
-
}, TYPEGEN = "sanity schema extract --enforce-required-fields --path sanity/schema.json && sanity typegen generate", patchWebPackageJson = (content) => addScripts(content, {
|
|
1572
|
-
"sanity:typegen": TYPEGEN,
|
|
1573
|
-
prebuild: TYPEGEN,
|
|
1574
|
-
...content.includes('"next build"') ? { "precheck-types": TYPEGEN } : {},
|
|
1575
|
-
"sanity:seed": "sanity dataset import sanity/seed.ndjson"
|
|
1576
|
-
}), NEXT_LAYOUT_SIGNATURE = "export default function RootLayout({ children }: { children: ReactNode }) {", patchNextLayout = (content) => {
|
|
3634
|
+
}, NEXT_LAYOUT_SIGNATURE = "export default function RootLayout({ children }: { children: ReactNode }) {", patchNextLayout = (content) => {
|
|
1577
3635
|
const withImports = [
|
|
1578
3636
|
'import { draftMode } from "next/headers";',
|
|
1579
3637
|
'import { VisualEditing } from "next-sanity/visual-editing";',
|
|
1580
3638
|
'import { DisableDraftMode } from "../sanity/disable-draft-mode";',
|
|
1581
|
-
'import { SanityLive } from "
|
|
3639
|
+
'import { SanityLive } from "@workspace/sanity/live";'
|
|
1582
3640
|
].reduce(ensureImport, content);
|
|
1583
3641
|
const asAsync = replaceOnce(withImports, NEXT_LAYOUT_SIGNATURE, `export default async function RootLayout({ children }: { children: ReactNode }) {
|
|
1584
3642
|
const { isEnabled: isDraft } = await draftMode();`);
|
|
@@ -1600,15 +3658,15 @@ export default defineConfig({`), addAstroOptimizeDeps = (content) => {
|
|
|
1600
3658
|
return content;
|
|
1601
3659
|
const withImports = [
|
|
1602
3660
|
'import { VisualEditing } from "@sanity/astro/visual-editing";',
|
|
1603
|
-
'import
|
|
1604
|
-
'import {
|
|
1605
|
-
'import {
|
|
1606
|
-
'import {
|
|
1607
|
-
'import
|
|
3661
|
+
'import { visualEditingEnabled } from "@workspace/sanity/env";',
|
|
3662
|
+
'import { loadQuery } from "@workspace/sanity/fetch";',
|
|
3663
|
+
'import { HOME_PAGE_QUERY } from "@workspace/sanity/queries";',
|
|
3664
|
+
'import type { HOME_PAGE_QUERY_RESULT } from "@workspace/sanity/types";',
|
|
3665
|
+
'import PageBuilder from "@workspace/sanity-blocks/PageBuilder.astro";'
|
|
1608
3666
|
].reduce(ensureImport, content);
|
|
1609
3667
|
const withFetch = replaceOnce(withImports, 'log.info("rendering home");', `log.info("rendering home");
|
|
1610
3668
|
|
|
1611
|
-
const page = await loadQuery<
|
|
3669
|
+
const page = await loadQuery<HOME_PAGE_QUERY_RESULT>(HOME_PAGE_QUERY);`);
|
|
1612
3670
|
const withHeading = replaceOnce(withFetch, " <h1>create-turbo-wizard</h1>", ' <h1>{page?.title ?? "create-turbo-wizard"}</h1>');
|
|
1613
3671
|
const withBlocks = replaceOnce(withHeading, " <p>Astro + Turborepo, scaffolded.</p>", [
|
|
1614
3672
|
" <PageBuilder blocks={page?.pageBuilder ?? []} />",
|
|
@@ -1622,136 +3680,142 @@ const page = await loadQuery<SanityPage>(HOME_PAGE_QUERY);`);
|
|
|
1622
3680
|
}, README_SECTION = `
|
|
1623
3681
|
## Sanity
|
|
1624
3682
|
|
|
1625
|
-
The Studio is
|
|
1626
|
-
|
|
3683
|
+
The Studio is its own app, \`apps/studio\` (\`pnpm dev:studio\`, port 3333) — a Vite app, so
|
|
3684
|
+
both frontends share the same one. It owns the document schemas, Studio structure and
|
|
3685
|
+
sample content. The read path (client, queries, generated types) is \`packages/sanity\` and
|
|
3686
|
+
the page-builder blocks — schemas and renderers together — are \`packages/sanity-blocks\`.
|
|
1627
3687
|
|
|
1628
3688
|
\`\`\`sh
|
|
1629
|
-
|
|
1630
|
-
cp
|
|
1631
|
-
pnpm
|
|
1632
|
-
pnpm sanity:
|
|
3689
|
+
cp .env.example apps/web/.env.local # or apps/web/.env on Astro — the project id
|
|
3690
|
+
cp apps/studio/.env.example apps/studio/.env # and SANITY_STUDIO_PROJECT_ID
|
|
3691
|
+
pnpm dev:studio # the Studio on http://localhost:3333
|
|
3692
|
+
cd apps/studio && pnpm sanity:seed # import seed.ndjson into the dataset
|
|
3693
|
+
cd apps/studio && pnpm extract && pnpm type # regenerate packages/sanity/src/sanity.types.ts
|
|
1633
3694
|
\`\`\`
|
|
1634
3695
|
|
|
3696
|
+
\`packages/sanity/src/sanity.types.ts\` ships pre-generated, so a fresh clone type-checks
|
|
3697
|
+
before anything is run; re-run \`extract\` + \`type\` after every schema or query edit.
|
|
3698
|
+
|
|
1635
3699
|
Without a project id the site builds and serves an empty state — no credentials required.
|
|
1636
3700
|
|
|
3701
|
+
### What's in the schema
|
|
3702
|
+
|
|
3703
|
+
\`page\` documents compose a page builder out of seven blocks — hero, rich text, feature
|
|
3704
|
+
cards, image link cards, FAQ accordion, newsletter and CTA — and \`blog\` posts carry an
|
|
3705
|
+
\`author\` reference, a publish date and a Portable Text body. Pages route by slug
|
|
3706
|
+
(\`/about\`), posts live under \`/blog\`. The shape follows Roboto Studio's
|
|
3707
|
+
\`turbo-start-sanity\`, so components and GROQ from that repo drop in unchanged.
|
|
3708
|
+
|
|
3709
|
+
Links are stored as \`customUrl\` (internal document or external URL) and flattened to a
|
|
3710
|
+
plain \`href\` in \`packages/sanity/src/queries.ts\`, so components never branch on the
|
|
3711
|
+
link type.
|
|
3712
|
+
|
|
1637
3713
|
### Visual Editing
|
|
1638
3714
|
|
|
1639
3715
|
Next: open the Studio's **Presentation** tab — draft mode enables via
|
|
1640
3716
|
\`/api/draft-mode/enable\`; leave it with the on-page "Disable draft mode" button.
|
|
1641
3717
|
Astro: set \`PUBLIC_SANITY_VISUAL_EDITING_ENABLED=true\` and \`SANITY_API_READ_TOKEN\`,
|
|
1642
3718
|
then use the dev server — overlays need server rendering (add an SSR adapter for a
|
|
1643
|
-
production preview deployment).
|
|
3719
|
+
production preview deployment). \`PUBLIC_SANITY_STUDIO_URL\` is where stega sends a
|
|
3720
|
+
click-through; it defaults to the Studio's own dev origin.
|
|
1644
3721
|
`, sanity;
|
|
1645
3722
|
var init_sanity = __esm(() => {
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
structureTool({ structure }),
|
|
1675
|
-
],
|
|
1676
|
-
schema: { types: schemaTypes },
|
|
1677
|
-
${DOCUMENT_GUARD}
|
|
1678
|
-
});
|
|
1679
|
-
`;
|
|
1680
|
-
ASTRO_SANITY_CONFIG = `import { defineConfig } from "sanity";
|
|
1681
|
-
import { presentationTool } from "sanity/presentation";
|
|
1682
|
-
import { structureTool } from "sanity/structure";
|
|
1683
|
-
import { locations, mainDocuments } from "./sanity/presentation";
|
|
1684
|
-
import { schemaTypes } from "./sanity/schema/index";
|
|
1685
|
-
import { singletonActions, singletonTypes, structure } from "./sanity/structure";
|
|
1686
|
-
|
|
1687
|
-
export default defineConfig({
|
|
1688
|
-
name: "default",
|
|
1689
|
-
title: "Studio",
|
|
1690
|
-
projectId: import.meta.env.PUBLIC_SANITY_PROJECT_ID || "placeholder",
|
|
1691
|
-
dataset: import.meta.env.PUBLIC_SANITY_DATASET || "production",
|
|
1692
|
-
plugins: [
|
|
1693
|
-
presentationTool({
|
|
1694
|
-
resolve: { mainDocuments, locations },
|
|
1695
|
-
// No previewMode: Astro has no draft-mode routes. Draft content comes from
|
|
1696
|
-
// loadQuery's perspective switch (PUBLIC_SANITY_VISUAL_EDITING_ENABLED + token),
|
|
1697
|
-
// which needs a rendering server — \`astro dev\`, or an SSR adapter in production.
|
|
1698
|
-
previewUrl: { initial: "/" },
|
|
1699
|
-
}),
|
|
1700
|
-
structureTool({ structure }),
|
|
1701
|
-
],
|
|
1702
|
-
schema: { types: schemaTypes },
|
|
1703
|
-
${DOCUMENT_GUARD}
|
|
1704
|
-
});
|
|
1705
|
-
`;
|
|
3723
|
+
init_seed();
|
|
3724
|
+
init_studio();
|
|
3725
|
+
SANITY_PACKAGE_JSON = j6({
|
|
3726
|
+
name: "@workspace/sanity",
|
|
3727
|
+
version: "0.0.0",
|
|
3728
|
+
private: true,
|
|
3729
|
+
type: "module",
|
|
3730
|
+
exports: {
|
|
3731
|
+
"./env": "./src/env.ts",
|
|
3732
|
+
"./client": "./src/client.ts",
|
|
3733
|
+
"./live": "./src/live.ts",
|
|
3734
|
+
"./fetch": "./src/fetch.ts",
|
|
3735
|
+
"./image": "./src/image.ts",
|
|
3736
|
+
"./queries": "./src/queries.ts",
|
|
3737
|
+
"./types": "./src/sanity.types.ts"
|
|
3738
|
+
}
|
|
3739
|
+
});
|
|
3740
|
+
SANITY_BLOCKS_PACKAGE_JSON = j6({
|
|
3741
|
+
name: "@workspace/sanity-blocks",
|
|
3742
|
+
version: "0.0.0",
|
|
3743
|
+
private: true,
|
|
3744
|
+
type: "module",
|
|
3745
|
+
exports: {
|
|
3746
|
+
".": "./src/schema.ts",
|
|
3747
|
+
"./blocks": "./src/blocks.tsx",
|
|
3748
|
+
"./PageBuilder.astro": "./src/PageBuilder.astro"
|
|
3749
|
+
}
|
|
3750
|
+
});
|
|
1706
3751
|
sanity = {
|
|
1707
3752
|
id: "sanity",
|
|
1708
3753
|
kind: "content",
|
|
1709
3754
|
prompt: {
|
|
1710
3755
|
label: "Sanity",
|
|
1711
|
-
hint: "Headless CMS with
|
|
3756
|
+
hint: "Headless CMS with a Studio of its own (apps/studio)"
|
|
1712
3757
|
},
|
|
1713
3758
|
templates: [
|
|
1714
|
-
{ path: "
|
|
1715
|
-
{ path: "
|
|
1716
|
-
{ path: "
|
|
1717
|
-
{ path: "
|
|
1718
|
-
{ path: "
|
|
1719
|
-
{ path: "apps/
|
|
1720
|
-
{ path: "apps/
|
|
1721
|
-
{ path: "apps/
|
|
3759
|
+
{ path: "packages/sanity/package.json", content: SANITY_PACKAGE_JSON },
|
|
3760
|
+
{ path: "packages/sanity/src/queries.ts", content: QUERIES },
|
|
3761
|
+
{ path: "packages/sanity/src/sanity.types.ts", content: SANITY_TYPES },
|
|
3762
|
+
{ path: "packages/sanity-blocks/package.json", content: SANITY_BLOCKS_PACKAGE_JSON },
|
|
3763
|
+
{ path: "packages/sanity-blocks/src/schema.ts", content: SCHEMA_BLOCKS },
|
|
3764
|
+
{ path: "apps/studio/package.json", content: STUDIO_PACKAGE_JSON },
|
|
3765
|
+
{ path: "apps/studio/tsconfig.json", content: STUDIO_TSCONFIG },
|
|
3766
|
+
{ path: "apps/studio/sanity.cli.ts", content: STUDIO_CLI },
|
|
3767
|
+
{ path: "apps/studio/schemaTypes/index.ts", content: SCHEMA_INDEX },
|
|
3768
|
+
{ path: "apps/studio/schemaTypes/definitions.ts", content: SCHEMA_DEFINITIONS },
|
|
3769
|
+
{ path: "apps/studio/schemaTypes/page.ts", content: SCHEMA_PAGE },
|
|
3770
|
+
{ path: "apps/studio/schemaTypes/blog.ts", content: SCHEMA_BLOG },
|
|
3771
|
+
{ path: "apps/studio/schemaTypes/settings.ts", content: SCHEMA_SETTINGS },
|
|
3772
|
+
{ path: "apps/studio/structure.ts", content: STRUCTURE },
|
|
3773
|
+
{ path: "apps/studio/presentation.ts", content: PRESENTATION },
|
|
3774
|
+
{ path: "apps/studio/seed.ndjson", content: SEED_NDJSON }
|
|
1722
3775
|
],
|
|
1723
3776
|
fragments: {
|
|
1724
3777
|
next: [
|
|
1725
|
-
{ path: "
|
|
1726
|
-
{ path: "
|
|
1727
|
-
{ path: "
|
|
1728
|
-
{ path: "
|
|
1729
|
-
{ path: "
|
|
1730
|
-
{ path: "
|
|
3778
|
+
{ path: "packages/sanity/src/env.ts", content: NEXT_ENV },
|
|
3779
|
+
{ path: "packages/sanity/src/client.ts", content: NEXT_CLIENT },
|
|
3780
|
+
{ path: "packages/sanity/src/live.ts", content: NEXT_LIVE },
|
|
3781
|
+
{ path: "packages/sanity/src/fetch.ts", content: NEXT_FETCH },
|
|
3782
|
+
{ path: "packages/sanity/src/image.ts", content: NEXT_IMAGE },
|
|
3783
|
+
{ path: "packages/sanity-blocks/src/blocks.tsx", content: NEXT_PAGE_BUILDER },
|
|
3784
|
+
{ path: "apps/studio/sanity.config.ts", content: STUDIO_CONFIG_NEXT },
|
|
3785
|
+
{ path: "apps/studio/.env.example", content: STUDIO_ENV_EXAMPLE_NEXT },
|
|
1731
3786
|
{ path: "apps/web/sanity/disable-draft-mode.tsx", content: NEXT_DISABLE_DRAFT_MODE },
|
|
1732
|
-
{ path: "apps/web/sanity.config.ts", content: NEXT_SANITY_CONFIG },
|
|
1733
|
-
{ path: "apps/web/sanity.cli.ts", content: NEXT_SANITY_CLI },
|
|
1734
|
-
{ path: "apps/web/app/studio/[[...tool]]/page.tsx", content: NEXT_STUDIO_PAGE },
|
|
1735
3787
|
{ path: "apps/web/app/api/draft-mode/enable/route.ts", content: NEXT_DRAFT_MODE_ROUTE },
|
|
1736
3788
|
{
|
|
1737
3789
|
path: "apps/web/app/api/draft-mode/disable/route.ts",
|
|
1738
3790
|
content: NEXT_DRAFT_MODE_DISABLE_ROUTE
|
|
1739
3791
|
},
|
|
1740
|
-
{ path: "apps/web/app/[slug]/page.tsx", content: NEXT_SLUG_PAGE }
|
|
3792
|
+
{ path: "apps/web/app/[slug]/page.tsx", content: NEXT_SLUG_PAGE },
|
|
3793
|
+
{ path: "apps/web/app/blog/page.tsx", content: NEXT_BLOG_INDEX_PAGE },
|
|
3794
|
+
{ path: "apps/web/app/blog/[slug]/page.tsx", content: NEXT_BLOG_POST_PAGE }
|
|
1741
3795
|
],
|
|
1742
3796
|
astro: [
|
|
1743
|
-
{ path: "
|
|
1744
|
-
{ path: "
|
|
1745
|
-
{ path: "
|
|
1746
|
-
{ path: "
|
|
1747
|
-
{ path: "apps/
|
|
1748
|
-
{ path: "apps/
|
|
1749
|
-
{ path: "apps/web/src/
|
|
1750
|
-
{ path: "apps/web/src/pages/
|
|
3797
|
+
{ path: "packages/sanity/src/env.ts", content: ASTRO_ENV },
|
|
3798
|
+
{ path: "packages/sanity/src/fetch.ts", content: ASTRO_FETCH },
|
|
3799
|
+
{ path: "packages/sanity/src/image.ts", content: ASTRO_IMAGE },
|
|
3800
|
+
{ path: "packages/sanity-blocks/src/PageBuilder.astro", content: ASTRO_PAGE_BUILDER },
|
|
3801
|
+
{ path: "apps/studio/sanity.config.ts", content: STUDIO_CONFIG_ASTRO },
|
|
3802
|
+
{ path: "apps/studio/.env.example", content: STUDIO_ENV_EXAMPLE_ASTRO },
|
|
3803
|
+
{ path: "apps/web/src/pages/[slug].astro", content: ASTRO_SLUG_PAGE },
|
|
3804
|
+
{ path: "apps/web/src/pages/blog/index.astro", content: ASTRO_BLOG_INDEX_PAGE },
|
|
3805
|
+
{ path: "apps/web/src/pages/blog/[slug].astro", content: ASTRO_BLOG_POST_PAGE }
|
|
1751
3806
|
]
|
|
1752
3807
|
},
|
|
3808
|
+
scripts: {
|
|
3809
|
+
"dev:studio": "turbo run dev --filter=studio",
|
|
3810
|
+
"build:studio": "turbo run build --filter=studio"
|
|
3811
|
+
},
|
|
1753
3812
|
deps: [
|
|
1754
|
-
{ name: "
|
|
3813
|
+
{ name: "@workspace/sanity", version: "workspace:*", target: "app" },
|
|
3814
|
+
{ name: "@workspace/sanity-blocks", version: "workspace:*", target: "app" },
|
|
3815
|
+
{ name: "groq", version: "^6.7.0", target: "sanity" },
|
|
3816
|
+
{ name: "@sanity/client", version: "^7.26.0", target: "sanity" },
|
|
3817
|
+
{ name: "sanity", version: "^5.31.1", target: "sanity-blocks" },
|
|
3818
|
+
{ name: "@workspace/sanity", version: "workspace:*", target: "sanity-blocks" }
|
|
1755
3819
|
],
|
|
1756
3820
|
frontendDeps: {
|
|
1757
3821
|
next: [
|
|
@@ -1759,59 +3823,40 @@ ${DOCUMENT_GUARD}
|
|
|
1759
3823
|
{ name: "@portabletext/react", version: "^7.0.1", target: "app" },
|
|
1760
3824
|
{ name: "@sanity/image-url", version: "^2.1.1", target: "app" },
|
|
1761
3825
|
{ name: "@sanity/client", version: "^7.26.0", target: "app" },
|
|
1762
|
-
{ name: "sanity", version: "^
|
|
1763
|
-
{ name: "
|
|
3826
|
+
{ name: "next-sanity", version: "^13.2.3", target: "sanity" },
|
|
3827
|
+
{ name: "@sanity/image-url", version: "^2.1.1", target: "sanity" },
|
|
3828
|
+
{ name: "next", version: "^16.2.0", target: "sanity" },
|
|
3829
|
+
{ name: "@portabletext/react", version: "^7.0.1", target: "sanity-blocks" },
|
|
3830
|
+
{ name: "next", version: "^16.2.0", target: "sanity-blocks" },
|
|
3831
|
+
{ name: "react", version: "^19.0.0", target: "sanity-blocks" }
|
|
1764
3832
|
],
|
|
1765
3833
|
astro: [
|
|
1766
3834
|
{ name: "@sanity/astro", version: "^3.5.0", target: "app" },
|
|
1767
3835
|
{ name: "astro-portabletext", version: "^0.13.0", target: "app" },
|
|
1768
|
-
{ name: "@portabletext/types", version: "^4.0.2", target: "app" },
|
|
1769
3836
|
{ name: "@sanity/image-url", version: "^2.1.1", target: "app" },
|
|
1770
3837
|
{ name: "@sanity/client", version: "^7.26.0", target: "app" },
|
|
1771
|
-
{ name: "sanity", version: "^5.31.1", target: "app" },
|
|
1772
3838
|
{ name: "@sanity/ui", version: "^3.5.1", target: "app" },
|
|
1773
3839
|
{ name: "react-compiler-runtime", version: "^1.0.0", target: "app" },
|
|
1774
3840
|
{ name: "react-is", version: "^19.0.0", target: "app" },
|
|
1775
3841
|
{ name: "lodash", version: "^4.17.21", target: "app" },
|
|
1776
|
-
{ name: "
|
|
1777
|
-
{ name: "
|
|
3842
|
+
{ name: "@sanity/image-url", version: "^2.1.1", target: "sanity" },
|
|
3843
|
+
{ name: "astro-portabletext", version: "^0.13.0", target: "sanity-blocks" }
|
|
1778
3844
|
]
|
|
1779
3845
|
},
|
|
1780
3846
|
env: [
|
|
1781
3847
|
{ key: "NEXT_PUBLIC_SANITY_PROJECT_ID", scope: "client" },
|
|
1782
3848
|
{ key: "NEXT_PUBLIC_SANITY_DATASET", scope: "client" },
|
|
1783
3849
|
{ key: "NEXT_PUBLIC_SANITY_API_VERSION", scope: "client" },
|
|
3850
|
+
{ key: "NEXT_PUBLIC_SANITY_STUDIO_URL", scope: "client" },
|
|
3851
|
+
{ key: "SANITY_STUDIO_PROJECT_ID", scope: "server" },
|
|
3852
|
+
{ key: "SANITY_STUDIO_DATASET", scope: "server" },
|
|
3853
|
+
{ key: "SANITY_STUDIO_PRESENTATION_URL", scope: "server" },
|
|
1784
3854
|
{ key: "SANITY_API_READ_TOKEN", scope: "server" },
|
|
1785
3855
|
{ key: "PUBLIC_SANITY_PROJECT_ID", scope: "client" },
|
|
1786
3856
|
{ key: "PUBLIC_SANITY_DATASET", scope: "client" },
|
|
3857
|
+
{ key: "PUBLIC_SANITY_STUDIO_URL", scope: "client" },
|
|
1787
3858
|
{ key: "PUBLIC_SANITY_VISUAL_EDITING_ENABLED", scope: "client" }
|
|
1788
3859
|
],
|
|
1789
|
-
owns: [
|
|
1790
|
-
"apps/web/sanity/schema/blocks.ts",
|
|
1791
|
-
"apps/web/sanity/schema/page.ts",
|
|
1792
|
-
"apps/web/sanity/schema/settings.ts",
|
|
1793
|
-
"apps/web/sanity/schema/index.ts",
|
|
1794
|
-
"apps/web/sanity/structure.ts",
|
|
1795
|
-
"apps/web/sanity/presentation.ts",
|
|
1796
|
-
"apps/web/sanity/queries.ts",
|
|
1797
|
-
"apps/web/sanity/seed.ndjson",
|
|
1798
|
-
"apps/web/sanity/env.ts",
|
|
1799
|
-
"apps/web/sanity/client.ts",
|
|
1800
|
-
"apps/web/sanity/live.ts",
|
|
1801
|
-
"apps/web/sanity/fetch.ts",
|
|
1802
|
-
"apps/web/sanity/image.ts",
|
|
1803
|
-
"apps/web/sanity/types.ts",
|
|
1804
|
-
"apps/web/sanity/page-builder.tsx",
|
|
1805
|
-
"apps/web/sanity/disable-draft-mode.tsx",
|
|
1806
|
-
"apps/web/sanity.config.ts",
|
|
1807
|
-
"apps/web/sanity.cli.ts",
|
|
1808
|
-
"apps/web/app/studio/[[...tool]]/page.tsx",
|
|
1809
|
-
"apps/web/app/api/draft-mode/enable/route.ts",
|
|
1810
|
-
"apps/web/app/api/draft-mode/disable/route.ts",
|
|
1811
|
-
"apps/web/app/[slug]/page.tsx",
|
|
1812
|
-
"apps/web/src/components/PageBuilder.astro",
|
|
1813
|
-
"apps/web/src/pages/[slug].astro"
|
|
1814
|
-
],
|
|
1815
3860
|
patches: [
|
|
1816
3861
|
{
|
|
1817
3862
|
path: "apps/web/next.config.ts",
|
|
@@ -1821,14 +3866,13 @@ ${DOCUMENT_GUARD}
|
|
|
1821
3866
|
{ path: "apps/web/app/page.tsx", apply: replaceHomePage(NEXT_HOME_PAGE) },
|
|
1822
3867
|
{
|
|
1823
3868
|
path: "apps/web/astro.config.mjs",
|
|
1824
|
-
apply: (content) => addAstroOptimizeDeps(addAstroIntegration(prependAstroConfigStatement(content, ASTRO_ENV_LOADER, "process.loadEnvFile"), 'import sanity from "@sanity/astro";', `sanity({ projectId: process.env.PUBLIC_SANITY_PROJECT_ID ?? 'placeholder', dataset: process.env.PUBLIC_SANITY_DATASET ?? 'production', apiVersion: '2026-02-01', useCdn: false,
|
|
3869
|
+
apply: (content) => addAstroOptimizeDeps(addAstroIntegration(prependAstroConfigStatement(content, ASTRO_ENV_LOADER, "process.loadEnvFile"), 'import sanity from "@sanity/astro";', `sanity({ projectId: process.env.PUBLIC_SANITY_PROJECT_ID ?? 'placeholder', dataset: process.env.PUBLIC_SANITY_DATASET ?? 'production', apiVersion: '2026-02-01', useCdn: false, stega: { studioUrl: process.env.PUBLIC_SANITY_STUDIO_URL ?? 'http://localhost:3333' } })`))
|
|
1825
3870
|
},
|
|
1826
3871
|
{
|
|
1827
3872
|
path: "apps/web/src/env.d.ts",
|
|
1828
3873
|
apply: (content) => ensureImport(content, ASTRO_MODULE_REF)
|
|
1829
3874
|
},
|
|
1830
3875
|
{ path: "apps/web/src/pages/index.astro", apply: patchAstroIndex },
|
|
1831
|
-
{ path: "apps/web/package.json", apply: patchWebPackageJson },
|
|
1832
3876
|
{
|
|
1833
3877
|
path: "README.md",
|
|
1834
3878
|
apply: (content) => content.includes("## Sanity") ? content : content + README_SECTION
|
|
@@ -1838,7 +3882,7 @@ ${DOCUMENT_GUARD}
|
|
|
1838
3882
|
});
|
|
1839
3883
|
|
|
1840
3884
|
// ../../packages/registry/src/frontends/astro.ts
|
|
1841
|
-
var
|
|
3885
|
+
var j7 = (value) => `${JSON.stringify(value, null, 2)}
|
|
1842
3886
|
`, ASTRO_CONFIG = `import react from "@astrojs/react";
|
|
1843
3887
|
import { defineConfig } from "astro/config";
|
|
1844
3888
|
|
|
@@ -1868,10 +3912,14 @@ var init_astro = __esm(() => {
|
|
|
1868
3912
|
astroFrontend = {
|
|
1869
3913
|
id: "astro",
|
|
1870
3914
|
kind: "frontend",
|
|
3915
|
+
prompt: {
|
|
3916
|
+
label: "Astro",
|
|
3917
|
+
hint: "Islands architecture, zero client JS by default"
|
|
3918
|
+
},
|
|
1871
3919
|
templates: [
|
|
1872
3920
|
{
|
|
1873
3921
|
path: "apps/web/package.json",
|
|
1874
|
-
content:
|
|
3922
|
+
content: j7({
|
|
1875
3923
|
name: "web",
|
|
1876
3924
|
version: "0.0.0",
|
|
1877
3925
|
private: true,
|
|
@@ -1903,7 +3951,7 @@ var init_astro = __esm(() => {
|
|
|
1903
3951
|
{ path: "apps/web/astro.config.mjs", content: ASTRO_CONFIG },
|
|
1904
3952
|
{
|
|
1905
3953
|
path: "apps/web/tsconfig.json",
|
|
1906
|
-
content:
|
|
3954
|
+
content: j7({
|
|
1907
3955
|
extends: "astro/tsconfigs/strict",
|
|
1908
3956
|
compilerOptions: { jsx: "react-jsx", jsxImportSource: "react" },
|
|
1909
3957
|
include: [".astro/types.d.ts", "**/*"],
|
|
@@ -1913,17 +3961,44 @@ var init_astro = __esm(() => {
|
|
|
1913
3961
|
{ path: "apps/web/src/env.d.ts", content: `/// <reference types="astro/client" />
|
|
1914
3962
|
` },
|
|
1915
3963
|
{ path: "apps/web/src/pages/index.astro", content: INDEX_ASTRO }
|
|
1916
|
-
],
|
|
1917
|
-
owns: [
|
|
1918
|
-
"apps/web/package.json",
|
|
1919
|
-
"apps/web/astro.config.mjs",
|
|
1920
|
-
"apps/web/tsconfig.json",
|
|
1921
|
-
"apps/web/src/env.d.ts",
|
|
1922
|
-
"apps/web/src/pages/index.astro"
|
|
1923
3964
|
]
|
|
1924
3965
|
};
|
|
1925
3966
|
});
|
|
1926
3967
|
|
|
3968
|
+
// ../../packages/registry/src/frontends/deploy-target.ts
|
|
3969
|
+
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) => {
|
|
3970
|
+
if (config.frontend !== "astro" || config.deploy !== "vercel")
|
|
3971
|
+
return null;
|
|
3972
|
+
if (!config.modules.some((id) => ASTRO_SSR_MODULES.includes(id)))
|
|
3973
|
+
return null;
|
|
3974
|
+
return {
|
|
3975
|
+
id: "deploy-vercel",
|
|
3976
|
+
kind: "frontend",
|
|
3977
|
+
frontendDeps: {
|
|
3978
|
+
astro: [{ name: "@astrojs/vercel", version: ASTROJS_VERCEL_VERSION, target: "app" }]
|
|
3979
|
+
},
|
|
3980
|
+
patches: [
|
|
3981
|
+
{
|
|
3982
|
+
path: "apps/web/astro.config.mjs",
|
|
3983
|
+
apply: (content) => {
|
|
3984
|
+
if (content.includes(VERCEL_IMPORT))
|
|
3985
|
+
return content;
|
|
3986
|
+
if (!content.includes(NODE_IMPORT))
|
|
3987
|
+
return content;
|
|
3988
|
+
return content.replace(NODE_IMPORT, VERCEL_IMPORT).replace(NODE_ADAPTER, VERCEL_ADAPTER);
|
|
3989
|
+
}
|
|
3990
|
+
},
|
|
3991
|
+
{
|
|
3992
|
+
path: "apps/web/package.json",
|
|
3993
|
+
apply: (content) => content.includes('"@astrojs/node"') ? content.replace(/^\s*"@astrojs\/node":\s*"[^"]*",?\n/m, "") : content
|
|
3994
|
+
}
|
|
3995
|
+
]
|
|
3996
|
+
};
|
|
3997
|
+
};
|
|
3998
|
+
var init_deploy_target = __esm(() => {
|
|
3999
|
+
ASTRO_SSR_MODULES = ["forms"];
|
|
4000
|
+
});
|
|
4001
|
+
|
|
1927
4002
|
// ../../packages/registry/src/frontends/experimental.ts
|
|
1928
4003
|
var BABEL_REACT_COMPILER_VERSION = "^1.0.0", experimentalDescriptor = (config) => {
|
|
1929
4004
|
const { cacheComponents, reactCompiler } = config.experimental;
|
|
@@ -1960,7 +4035,7 @@ var BABEL_REACT_COMPILER_VERSION = "^1.0.0", experimentalDescriptor = (config) =
|
|
|
1960
4035
|
var init_experimental = () => {};
|
|
1961
4036
|
|
|
1962
4037
|
// ../../packages/registry/src/frontends/next.ts
|
|
1963
|
-
var
|
|
4038
|
+
var j8 = (value) => `${JSON.stringify(value, null, 2)}
|
|
1964
4039
|
`, NEXT_CONFIG = `import { createRequire } from "node:module";
|
|
1965
4040
|
import type { NextConfig } from "next";
|
|
1966
4041
|
|
|
@@ -2016,10 +4091,14 @@ var init_next = __esm(() => {
|
|
|
2016
4091
|
nextFrontend = {
|
|
2017
4092
|
id: "next",
|
|
2018
4093
|
kind: "frontend",
|
|
4094
|
+
prompt: {
|
|
4095
|
+
label: "Next.js",
|
|
4096
|
+
hint: "App Router, React Server Components, Turbopack"
|
|
4097
|
+
},
|
|
2019
4098
|
templates: [
|
|
2020
4099
|
{
|
|
2021
4100
|
path: "apps/web/package.json",
|
|
2022
|
-
content:
|
|
4101
|
+
content: j8({
|
|
2023
4102
|
name: "web",
|
|
2024
4103
|
version: "0.0.0",
|
|
2025
4104
|
private: true,
|
|
@@ -2050,7 +4129,7 @@ var init_next = __esm(() => {
|
|
|
2050
4129
|
{ path: "apps/web/next.config.ts", content: NEXT_CONFIG },
|
|
2051
4130
|
{
|
|
2052
4131
|
path: "apps/web/tsconfig.json",
|
|
2053
|
-
content:
|
|
4132
|
+
content: j8({
|
|
2054
4133
|
extends: "@workspace/typescript-config/nextjs.json",
|
|
2055
4134
|
compilerOptions: { plugins: [{ name: "next" }], paths: { "@/*": ["./*"] } },
|
|
2056
4135
|
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
@@ -2060,14 +4139,6 @@ var init_next = __esm(() => {
|
|
|
2060
4139
|
{ path: "apps/web/app/layout.tsx", content: LAYOUT },
|
|
2061
4140
|
{ path: "apps/web/app/page.tsx", content: PAGE },
|
|
2062
4141
|
{ path: "apps/web/app/globals.css", content: GLOBALS }
|
|
2063
|
-
],
|
|
2064
|
-
owns: [
|
|
2065
|
-
"apps/web/package.json",
|
|
2066
|
-
"apps/web/next.config.ts",
|
|
2067
|
-
"apps/web/tsconfig.json",
|
|
2068
|
-
"apps/web/app/layout.tsx",
|
|
2069
|
-
"apps/web/app/page.tsx",
|
|
2070
|
-
"apps/web/app/globals.css"
|
|
2071
4142
|
]
|
|
2072
4143
|
};
|
|
2073
4144
|
});
|
|
@@ -2236,18 +4307,12 @@ export default function AlgoliaSearch({
|
|
|
2236
4307
|
{ key: "PUBLIC_ALGOLIA_APP_ID", scope: "client" },
|
|
2237
4308
|
{ key: "PUBLIC_ALGOLIA_SEARCH_KEY", scope: "client" },
|
|
2238
4309
|
{ key: "PUBLIC_ALGOLIA_INDEX", scope: "client" }
|
|
2239
|
-
],
|
|
2240
|
-
owns: [
|
|
2241
|
-
"apps/web/app/search/Search.tsx",
|
|
2242
|
-
"apps/web/app/search/page.tsx",
|
|
2243
|
-
"apps/web/src/components/AlgoliaSearch.tsx",
|
|
2244
|
-
"apps/web/src/pages/search.astro"
|
|
2245
4310
|
]
|
|
2246
4311
|
};
|
|
2247
4312
|
});
|
|
2248
4313
|
|
|
2249
4314
|
// ../../packages/registry/src/modules/changesets.ts
|
|
2250
|
-
var
|
|
4315
|
+
var j9 = (v) => `${JSON.stringify(v, null, 2)}
|
|
2251
4316
|
`, CONFIG, README = `# Changesets
|
|
2252
4317
|
|
|
2253
4318
|
This folder is managed by [Changesets](https://github.com/changesets/changesets).
|
|
@@ -2258,7 +4323,7 @@ the \`version-packages\` script consumes them to update versions and changelogs,
|
|
|
2258
4323
|
the \`release\` script builds and publishes.
|
|
2259
4324
|
`, changesets;
|
|
2260
4325
|
var init_changesets = __esm(() => {
|
|
2261
|
-
CONFIG =
|
|
4326
|
+
CONFIG = j9({
|
|
2262
4327
|
$schema: "https://unpkg.com/@changesets/config@3.1.4/schema.json",
|
|
2263
4328
|
changelog: "@changesets/cli/changelog",
|
|
2264
4329
|
commit: false,
|
|
@@ -2286,8 +4351,7 @@ var init_changesets = __esm(() => {
|
|
|
2286
4351
|
changeset: "changeset",
|
|
2287
4352
|
"version-packages": "changeset version",
|
|
2288
4353
|
release: "turbo run build && changeset publish"
|
|
2289
|
-
}
|
|
2290
|
-
owns: [".changeset/config.json", ".changeset/README.md"]
|
|
4354
|
+
}
|
|
2291
4355
|
};
|
|
2292
4356
|
});
|
|
2293
4357
|
|
|
@@ -2336,8 +4400,7 @@ var init_e2e = __esm(() => {
|
|
|
2336
4400
|
]
|
|
2337
4401
|
},
|
|
2338
4402
|
scripts: { e2e: "playwright test" },
|
|
2339
|
-
deps: [{ name: "@playwright/test", version: "^1.60.0", target: "root", dev: true }]
|
|
2340
|
-
owns: ["tests/e2e/home.spec.ts", "playwright.config.ts"]
|
|
4403
|
+
deps: [{ name: "@playwright/test", version: "^1.60.0", target: "root", dev: true }]
|
|
2341
4404
|
};
|
|
2342
4405
|
});
|
|
2343
4406
|
|
|
@@ -2577,13 +4640,6 @@ export const POST: APIRoute = async ({ request }) => {
|
|
|
2577
4640
|
path: "apps/web/astro.config.mjs",
|
|
2578
4641
|
apply: (content) => ensureAstroAdapter(content, 'import node from "@astrojs/node";', 'node({ mode: "standalone" })')
|
|
2579
4642
|
}
|
|
2580
|
-
],
|
|
2581
|
-
owns: [
|
|
2582
|
-
"apps/web/app/contact/page.tsx",
|
|
2583
|
-
"apps/web/app/api/contact/route.ts",
|
|
2584
|
-
"apps/web/src/components/ContactForm.tsx",
|
|
2585
|
-
"apps/web/src/pages/contact.astro",
|
|
2586
|
-
"apps/web/src/pages/api/contact.ts"
|
|
2587
4643
|
]
|
|
2588
4644
|
};
|
|
2589
4645
|
});
|
|
@@ -2625,8 +4681,7 @@ var init_git_hooks = __esm(() => {
|
|
|
2625
4681
|
target: "root",
|
|
2626
4682
|
dev: true
|
|
2627
4683
|
}
|
|
2628
|
-
]
|
|
2629
|
-
owns: ["lefthook.yml", "commitlint.config.mjs"]
|
|
4684
|
+
]
|
|
2630
4685
|
};
|
|
2631
4686
|
});
|
|
2632
4687
|
|
|
@@ -2680,8 +4735,7 @@ var init_github_ci = __esm(() => {
|
|
|
2680
4735
|
label: "GitHub Actions CI",
|
|
2681
4736
|
hint: "Workflow that installs, type-checks, and builds on every push and PR."
|
|
2682
4737
|
},
|
|
2683
|
-
templates: [{ path: ".github/workflows/ci.yml", content: CI_WORKFLOW }]
|
|
2684
|
-
owns: [".github/workflows/ci.yml"]
|
|
4738
|
+
templates: [{ path: ".github/workflows/ci.yml", content: CI_WORKFLOW }]
|
|
2685
4739
|
};
|
|
2686
4740
|
});
|
|
2687
4741
|
|
|
@@ -2844,18 +4898,7 @@ ${LLMS_TXT_REST}\`;
|
|
|
2844
4898
|
{ path: "apps/web/src/content/pages.ts", content: ASTRO_CONTENT },
|
|
2845
4899
|
{ path: "apps/web/src/pages/[slug].md.ts", content: ASTRO_MD_ENDPOINT }
|
|
2846
4900
|
]
|
|
2847
|
-
}
|
|
2848
|
-
owns: [
|
|
2849
|
-
"skills.md",
|
|
2850
|
-
"apps/web/app/llms.txt/route.ts",
|
|
2851
|
-
"apps/web/app/docs/content.ts",
|
|
2852
|
-
"apps/web/app/docs/page.tsx",
|
|
2853
|
-
"apps/web/app/docs.md/route.ts",
|
|
2854
|
-
"apps/web/app/lib/markdown-variant.ts",
|
|
2855
|
-
"apps/web/src/pages/llms.txt.ts",
|
|
2856
|
-
"apps/web/src/content/pages.ts",
|
|
2857
|
-
"apps/web/src/pages/[slug].md.ts"
|
|
2858
|
-
]
|
|
4901
|
+
}
|
|
2859
4902
|
};
|
|
2860
4903
|
});
|
|
2861
4904
|
|
|
@@ -2916,7 +4959,6 @@ var init_posthog = __esm(() => {
|
|
|
2916
4959
|
{ key: "PUBLIC_POSTHOG_KEY", scope: "client" },
|
|
2917
4960
|
{ key: "PUBLIC_POSTHOG_HOST", scope: "client" }
|
|
2918
4961
|
],
|
|
2919
|
-
owns: ["apps/web/instrumentation-client.ts", "apps/web/src/components/PostHog.astro"],
|
|
2920
4962
|
patches: [
|
|
2921
4963
|
{ path: "apps/web/src/pages/index.astro", apply: patchIndexAstro },
|
|
2922
4964
|
{ path: "apps/web/src/layouts/Layout.astro", apply: patchIndexAstro }
|
|
@@ -3444,15 +5486,6 @@ var init_sections = __esm(() => {
|
|
|
3444
5486
|
{ path: "packages/ui/src/components/sections/faq.tsx", content: FAQ_TSX },
|
|
3445
5487
|
{ path: "packages/ui/src/components/sections/cta.tsx", content: CTA_TSX }
|
|
3446
5488
|
],
|
|
3447
|
-
owns: [
|
|
3448
|
-
"packages/ui/src/components/sections/hero.tsx",
|
|
3449
|
-
"packages/ui/src/components/sections/features.tsx",
|
|
3450
|
-
"packages/ui/src/components/sections/logo-cloud.tsx",
|
|
3451
|
-
"packages/ui/src/components/sections/testimonials.tsx",
|
|
3452
|
-
"packages/ui/src/components/sections/pricing.tsx",
|
|
3453
|
-
"packages/ui/src/components/sections/faq.tsx",
|
|
3454
|
-
"packages/ui/src/components/sections/cta.tsx"
|
|
3455
|
-
],
|
|
3456
5489
|
patches: [
|
|
3457
5490
|
{ path: "apps/web/app/page.tsx", apply: replaceWith(COMPOSED_NEXT_HOME_PAGE) },
|
|
3458
5491
|
{ path: "apps/web/src/pages/index.astro", apply: replaceWith(COMPOSED_ASTRO_HOME_PAGE) }
|
|
@@ -3720,14 +5753,6 @@ var init_seo = __esm(() => {
|
|
|
3720
5753
|
{ key: "PUBLIC_SITE_NAME", scope: "client" },
|
|
3721
5754
|
{ key: "PUBLIC_SITE_DESCRIPTION", scope: "client" }
|
|
3722
5755
|
],
|
|
3723
|
-
owns: [
|
|
3724
|
-
"apps/web/app/lib/metadata.ts",
|
|
3725
|
-
"apps/web/app/sitemap.ts",
|
|
3726
|
-
"apps/web/app/robots.ts",
|
|
3727
|
-
"apps/web/app/opengraph-image.tsx",
|
|
3728
|
-
"apps/web/src/components/SEO.astro",
|
|
3729
|
-
"apps/web/src/pages/og.png.ts"
|
|
3730
|
-
],
|
|
3731
5756
|
patches: [
|
|
3732
5757
|
{ path: "apps/web/astro.config.mjs", apply: patchAstroConfig },
|
|
3733
5758
|
{ path: "apps/web/app/layout.tsx", apply: patchLayout },
|
|
@@ -4048,7 +6073,7 @@ export function SiteHeader({
|
|
|
4048
6073
|
))}
|
|
4049
6074
|
</nav>
|
|
4050
6075
|
<div className="ml-auto">
|
|
4051
|
-
<Button size="sm" render={<a href={action.href} />}>
|
|
6076
|
+
<Button size="sm" nativeButton={false} render={<a href={action.href} />}>
|
|
4052
6077
|
{action.label}
|
|
4053
6078
|
</Button>
|
|
4054
6079
|
</div>
|
|
@@ -4111,7 +6136,9 @@ export default function NotFound() {
|
|
|
4111
6136
|
<p className="text-muted-foreground">
|
|
4112
6137
|
The link may be out of date, or the page may have moved.
|
|
4113
6138
|
</p>
|
|
4114
|
-
<Button render={<a href="/" />}>
|
|
6139
|
+
<Button nativeButton={false} render={<a href="/" />}>
|
|
6140
|
+
Back home
|
|
6141
|
+
</Button>
|
|
4115
6142
|
</main>
|
|
4116
6143
|
);
|
|
4117
6144
|
}
|
|
@@ -4562,8 +6589,8 @@ ${DARK_TOKENS}
|
|
|
4562
6589
|
});
|
|
4563
6590
|
|
|
4564
6591
|
// ../../packages/registry/src/modules/ui/index.ts
|
|
4565
|
-
var
|
|
4566
|
-
`, UI_PACKAGE_JSON, componentsJson = (rsc) =>
|
|
6592
|
+
var j10 = (value) => `${JSON.stringify(value, null, 2)}
|
|
6593
|
+
`, UI_PACKAGE_JSON, componentsJson = (rsc) => j10({
|
|
4567
6594
|
$schema: "https://ui.shadcn.com/schema.json",
|
|
4568
6595
|
style: "base-nova",
|
|
4569
6596
|
rsc,
|
|
@@ -4586,20 +6613,22 @@ var j7 = (value) => `${JSON.stringify(value, null, 2)}
|
|
|
4586
6613
|
menuColor: "default",
|
|
4587
6614
|
menuAccent: "subtle"
|
|
4588
6615
|
}), patchNextLayout2 = (content) => {
|
|
4589
|
-
if (content.includes("
|
|
6616
|
+
if (content.includes("<SiteHeader"))
|
|
4590
6617
|
return content;
|
|
4591
6618
|
const withImports = [
|
|
4592
6619
|
'import { SiteFooter } from "@workspace/ui/components/site-footer";',
|
|
4593
6620
|
'import { SiteHeader } from "@workspace/ui/components/site-header";',
|
|
4594
|
-
'import rootPkg from "../../../package.json";'
|
|
4595
|
-
|
|
6621
|
+
'import rootPkg from "../../../package.json";'
|
|
6622
|
+
].reduce(ensureImport, content);
|
|
6623
|
+
const expanded = withImports.replace(/^([ \t]*)<body([^>]*)>\{children\}<\/body>/m, (_m, indent, attrs) => `${indent}<body${attrs}>
|
|
6624
|
+
${indent} {children}
|
|
6625
|
+
${indent}</body>`);
|
|
6626
|
+
return expanded.replace(/<body([^>]*)>/, '<body$1 className="flex min-h-svh flex-col">').replace(/^([ \t]*)\{children\}$/m, (_m, indent) => [
|
|
6627
|
+
`${indent}<SiteHeader siteName={rootPkg.name} />`,
|
|
6628
|
+
`${indent}<div className="flex-1">{children}</div>`,
|
|
6629
|
+
`${indent}<SiteFooter siteName={rootPkg.name} />`
|
|
4596
6630
|
].join(`
|
|
4597
|
-
`);
|
|
4598
|
-
return withImports.replace(/<body([^>]*)>\{children\}<\/body>/, `<body$1 className="flex min-h-svh flex-col">
|
|
4599
|
-
<SiteHeader siteName={rootPkg.name} />
|
|
4600
|
-
<div className="flex-1">{children}</div>
|
|
4601
|
-
<SiteFooter siteName={rootPkg.name} />
|
|
4602
|
-
</body>`);
|
|
6631
|
+
`));
|
|
4603
6632
|
}, NEXT_POSTCSS_CONFIG = `export default {
|
|
4604
6633
|
plugins: {
|
|
4605
6634
|
"@tailwindcss/postcss": {},
|
|
@@ -4610,7 +6639,7 @@ var j7 = (value) => `${JSON.stringify(value, null, 2)}
|
|
|
4610
6639
|
var init_ui = __esm(() => {
|
|
4611
6640
|
init_pages();
|
|
4612
6641
|
init_theme();
|
|
4613
|
-
UI_PACKAGE_JSON =
|
|
6642
|
+
UI_PACKAGE_JSON = j10({
|
|
4614
6643
|
name: "@workspace/ui",
|
|
4615
6644
|
version: "0.0.0",
|
|
4616
6645
|
private: true,
|
|
@@ -4674,25 +6703,6 @@ var init_ui = __esm(() => {
|
|
|
4674
6703
|
]
|
|
4675
6704
|
},
|
|
4676
6705
|
deps: [{ name: "@workspace/ui", version: "workspace:*", target: "app" }],
|
|
4677
|
-
owns: [
|
|
4678
|
-
"packages/ui/package.json",
|
|
4679
|
-
"packages/ui/components.json",
|
|
4680
|
-
"packages/ui/src/styles/globals.css",
|
|
4681
|
-
"packages/ui/src/lib/utils.ts",
|
|
4682
|
-
"packages/ui/src/components/button.tsx",
|
|
4683
|
-
"packages/ui/src/components/card.tsx",
|
|
4684
|
-
"packages/ui/src/components/input.tsx",
|
|
4685
|
-
"packages/ui/src/components/textarea.tsx",
|
|
4686
|
-
"packages/ui/src/components/label.tsx",
|
|
4687
|
-
"packages/ui/src/components/badge.tsx",
|
|
4688
|
-
"packages/ui/src/components/site-header.tsx",
|
|
4689
|
-
"packages/ui/src/components/site-footer.tsx",
|
|
4690
|
-
"apps/web/postcss.config.mjs",
|
|
4691
|
-
"apps/web/components.json",
|
|
4692
|
-
"apps/web/app/not-found.tsx",
|
|
4693
|
-
"apps/web/src/layouts/Layout.astro",
|
|
4694
|
-
"apps/web/src/pages/404.astro"
|
|
4695
|
-
],
|
|
4696
6706
|
patches: [
|
|
4697
6707
|
{
|
|
4698
6708
|
path: "apps/web/app/globals.css",
|
|
@@ -4710,7 +6720,7 @@ var init_ui = __esm(() => {
|
|
|
4710
6720
|
if (!plugin || pkg.devDependencies?.[plugin])
|
|
4711
6721
|
return content;
|
|
4712
6722
|
const devDependencies = Object.fromEntries(Object.entries({ ...pkg.devDependencies, [plugin]: "^4.3.0" }).sort(([a], [b]) => a.localeCompare(b)));
|
|
4713
|
-
return
|
|
6723
|
+
return j10({ ...pkg, devDependencies });
|
|
4714
6724
|
}
|
|
4715
6725
|
},
|
|
4716
6726
|
{ path: "apps/web/app/layout.tsx", apply: patchNextLayout2 },
|
|
@@ -4751,21 +6761,51 @@ var init_vitest = __esm(() => {
|
|
|
4751
6761
|
{ path: "tests/example.test.ts", content: EXAMPLE_TEST }
|
|
4752
6762
|
],
|
|
4753
6763
|
scripts: { test: "vitest run", "test:watch": "vitest" },
|
|
4754
|
-
deps: [{ name: "vitest", version: "^4.1.0", target: "root", dev: true }]
|
|
4755
|
-
owns: ["vitest.config.ts", "tests/example.test.ts"]
|
|
6764
|
+
deps: [{ name: "vitest", version: "^4.1.0", target: "root", dev: true }]
|
|
4756
6765
|
};
|
|
4757
6766
|
});
|
|
4758
6767
|
|
|
6768
|
+
// ../../packages/registry/src/catalog.ts
|
|
6769
|
+
function withPrerequisites(selected) {
|
|
6770
|
+
const chosen = new Set(selected);
|
|
6771
|
+
const added = [];
|
|
6772
|
+
const queue = [...selected];
|
|
6773
|
+
for (const id of queue) {
|
|
6774
|
+
for (const required of MODULE_DESCRIPTORS[id]?.compat?.requiresModules ?? []) {
|
|
6775
|
+
if (chosen.has(required))
|
|
6776
|
+
continue;
|
|
6777
|
+
chosen.add(required);
|
|
6778
|
+
added.push([required, id]);
|
|
6779
|
+
queue.push(required);
|
|
6780
|
+
}
|
|
6781
|
+
}
|
|
6782
|
+
return { modules: MODULES.filter((id) => chosen.has(id)), added };
|
|
6783
|
+
}
|
|
6784
|
+
var entry = (id, descriptor) => ({
|
|
6785
|
+
id,
|
|
6786
|
+
label: descriptor?.prompt?.label ?? id,
|
|
6787
|
+
hint: descriptor?.prompt?.hint ?? "",
|
|
6788
|
+
category: descriptor?.category,
|
|
6789
|
+
requires: descriptor?.compat?.requiresModules ?? []
|
|
6790
|
+
}), moduleCatalog = () => MODULES.map((id) => entry(id, MODULE_DESCRIPTORS[id])), frontendCatalog = () => FRONTENDS.map((id) => entry(id, FRONTEND_DESCRIPTORS[id])), NO_CONTENT, contentCatalog = () => CONTENT_SOURCES.map((id) => id === "none" ? { id, ...NO_CONTENT, category: undefined, requires: [] } : entry(id, CONTENT_DESCRIPTORS[id]));
|
|
6791
|
+
var init_catalog = __esm(() => {
|
|
6792
|
+
init_src();
|
|
6793
|
+
init_src3();
|
|
6794
|
+
NO_CONTENT = { label: "None", hint: "No CMS and no MDX — just the frontend" };
|
|
6795
|
+
});
|
|
6796
|
+
|
|
4759
6797
|
// ../../packages/registry/src/index.ts
|
|
4760
6798
|
function buildRegistry(config2) {
|
|
4761
6799
|
const content = CONTENT_DESCRIPTORS[config2.content];
|
|
4762
6800
|
const experimental = experimentalDescriptor(config2);
|
|
6801
|
+
const deploy = deployDescriptor(config2);
|
|
4763
6802
|
return [
|
|
4764
6803
|
...baselineDescriptors,
|
|
4765
6804
|
FRONTEND_DESCRIPTORS[config2.frontend],
|
|
4766
6805
|
...experimental ? [experimental] : [],
|
|
4767
6806
|
...content ? [content] : [],
|
|
4768
|
-
...config2.modules.map((id) => MODULE_DESCRIPTORS[id])
|
|
6807
|
+
...config2.modules.map((id) => MODULE_DESCRIPTORS[id]),
|
|
6808
|
+
...deploy ? [deploy] : []
|
|
4769
6809
|
];
|
|
4770
6810
|
}
|
|
4771
6811
|
var baselineDescriptors, FRONTEND_DESCRIPTORS, MODULE_DESCRIPTORS, CONTENT_DESCRIPTORS;
|
|
@@ -4777,6 +6817,7 @@ var init_src3 = __esm(() => {
|
|
|
4777
6817
|
init_markdown();
|
|
4778
6818
|
init_sanity();
|
|
4779
6819
|
init_astro();
|
|
6820
|
+
init_deploy_target();
|
|
4780
6821
|
init_experimental();
|
|
4781
6822
|
init_next();
|
|
4782
6823
|
init_algolia();
|
|
@@ -4791,6 +6832,7 @@ var init_src3 = __esm(() => {
|
|
|
4791
6832
|
init_seo();
|
|
4792
6833
|
init_ui();
|
|
4793
6834
|
init_vitest();
|
|
6835
|
+
init_catalog();
|
|
4794
6836
|
baselineDescriptors = [typescriptConfig, env, logger, biome];
|
|
4795
6837
|
FRONTEND_DESCRIPTORS = {
|
|
4796
6838
|
next: nextFrontend,
|
|
@@ -4817,40 +6859,82 @@ var init_src3 = __esm(() => {
|
|
|
4817
6859
|
});
|
|
4818
6860
|
|
|
4819
6861
|
// src/create.ts
|
|
4820
|
-
import { spawnSync } from "node:child_process";
|
|
6862
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
4821
6863
|
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
4822
6864
|
import { resolve } from "node:path";
|
|
4823
|
-
function step(label, cmd, args, cwd, stdio = "ignore") {
|
|
6865
|
+
function step(label, cmd, args, cwd, stdio = "ignore", onWarn = warn) {
|
|
4824
6866
|
const res = spawnSync(cmd, args, { cwd, stdio });
|
|
4825
6867
|
if (res.error) {
|
|
4826
|
-
|
|
6868
|
+
onWarn(`${label} skipped: ${res.error.message}`);
|
|
4827
6869
|
return false;
|
|
4828
6870
|
}
|
|
4829
6871
|
if (res.status !== 0) {
|
|
4830
|
-
|
|
6872
|
+
onWarn(`${label} failed (exit ${res.status ?? `signal ${res.signal}`})`);
|
|
4831
6873
|
return false;
|
|
4832
6874
|
}
|
|
4833
6875
|
return true;
|
|
4834
6876
|
}
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
6877
|
+
function streamStep(label, cmd, args, cwd, onOutput, onWarn) {
|
|
6878
|
+
return new Promise((done) => {
|
|
6879
|
+
const child = spawn(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
6880
|
+
const emit = (chunk) => {
|
|
6881
|
+
for (const line of chunk.toString().split(/\r?\n|\r/)) {
|
|
6882
|
+
if (line.trim())
|
|
6883
|
+
onOutput(line);
|
|
6884
|
+
}
|
|
6885
|
+
};
|
|
6886
|
+
child.stdout?.on("data", emit);
|
|
6887
|
+
child.stderr?.on("data", emit);
|
|
6888
|
+
child.on("error", (error) => {
|
|
6889
|
+
onWarn(`${label} skipped: ${error.message}`);
|
|
6890
|
+
done(false);
|
|
6891
|
+
});
|
|
6892
|
+
child.on("close", (code, signal) => {
|
|
6893
|
+
if (code === 0)
|
|
6894
|
+
return done(true);
|
|
6895
|
+
onWarn(`${label} failed (exit ${code ?? `signal ${signal}`})`);
|
|
6896
|
+
done(false);
|
|
6897
|
+
});
|
|
6898
|
+
});
|
|
6899
|
+
}
|
|
6900
|
+
function planProject(config2) {
|
|
6901
|
+
return generate(config2, buildRegistry(config2));
|
|
6902
|
+
}
|
|
6903
|
+
async function writeProject(tree, config2, opts = {}) {
|
|
6904
|
+
const { onWarn = warn, onOutput } = opts;
|
|
4839
6905
|
const dir = resolve(opts.targetDir ?? config2.name);
|
|
4840
6906
|
await mkdir2(dir, { recursive: true });
|
|
4841
|
-
await writeTree(
|
|
6907
|
+
await writeTree(tree, dir);
|
|
4842
6908
|
if (opts.git) {
|
|
4843
|
-
const inited = step("git init", "git", ["init", "-q"], dir);
|
|
4844
|
-
const added = inited && step("git add", "git", ["add", "-A"], dir);
|
|
6909
|
+
const inited = step("git init", "git", ["init", "-q"], dir, "ignore", onWarn);
|
|
6910
|
+
const added = inited && step("git add", "git", ["add", "-A"], dir, "ignore", onWarn);
|
|
4845
6911
|
if (added) {
|
|
4846
|
-
|
|
6912
|
+
const args = ["commit", "-qm", "chore: scaffold with create-turbo-wizard"];
|
|
6913
|
+
step("git commit", "git", args, dir, "ignore", onWarn);
|
|
4847
6914
|
}
|
|
4848
6915
|
}
|
|
6916
|
+
let installError;
|
|
4849
6917
|
if (opts.install) {
|
|
4850
|
-
|
|
6918
|
+
const label = `${config2.packageManager} install`;
|
|
6919
|
+
const capture = (message) => {
|
|
6920
|
+
installError = message;
|
|
6921
|
+
};
|
|
6922
|
+
const ok2 = onOutput ? await streamStep(label, config2.packageManager, ["install"], dir, onOutput, capture) : step(label, config2.packageManager, ["install"], dir, "inherit", capture);
|
|
6923
|
+
if (ok2)
|
|
6924
|
+
installError = null;
|
|
4851
6925
|
}
|
|
4852
|
-
return {
|
|
6926
|
+
return { dir, fileCount: tree.size, installError };
|
|
6927
|
+
}
|
|
6928
|
+
async function createProject(config2, opts = {}) {
|
|
6929
|
+
const res = planProject(config2);
|
|
6930
|
+
if (!res.ok)
|
|
6931
|
+
return res;
|
|
6932
|
+
const value = await writeProject(res.value, config2, opts);
|
|
6933
|
+
if (value.installError)
|
|
6934
|
+
(opts.onWarn ?? warn)(value.installError);
|
|
6935
|
+
return { ok: true, value };
|
|
4853
6936
|
}
|
|
6937
|
+
var warn = (message) => console.error(`! ${message}`);
|
|
4854
6938
|
var init_create = __esm(() => {
|
|
4855
6939
|
init_src2();
|
|
4856
6940
|
init_src3();
|
|
@@ -4860,7 +6944,7 @@ var init_create = __esm(() => {
|
|
|
4860
6944
|
var exports_mcp = {};
|
|
4861
6945
|
__export(exports_mcp, {
|
|
4862
6946
|
startMcpServer: () => startMcpServer,
|
|
4863
|
-
planProject: () =>
|
|
6947
|
+
planProject: () => planProject2,
|
|
4864
6948
|
guidanceText: () => guidanceText,
|
|
4865
6949
|
createProjectFromInput: () => createProjectFromInput,
|
|
4866
6950
|
buildMcpServer: () => buildMcpServer
|
|
@@ -4868,7 +6952,7 @@ __export(exports_mcp, {
|
|
|
4868
6952
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4869
6953
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4870
6954
|
import { z as z3 } from "zod";
|
|
4871
|
-
function
|
|
6955
|
+
function planProject2(input) {
|
|
4872
6956
|
const config2 = toConfig(input);
|
|
4873
6957
|
const res = generate(config2, buildRegistry(config2));
|
|
4874
6958
|
if (!res.ok)
|
|
@@ -4886,7 +6970,7 @@ function buildMcpServer() {
|
|
|
4886
6970
|
description: "Preview the files a configuration would generate (does NOT write to disk).",
|
|
4887
6971
|
inputSchema: PlanInput
|
|
4888
6972
|
}, (input) => {
|
|
4889
|
-
const res =
|
|
6973
|
+
const res = planProject2(input);
|
|
4890
6974
|
return res.ok ? text2(res.paths.join(`
|
|
4891
6975
|
`)) : { ...text2(`Generation failed: ${res.error}`), isError: true };
|
|
4892
6976
|
});
|
|
@@ -4943,59 +7027,259 @@ var init_mcp = __esm(() => {
|
|
|
4943
7027
|
init_src();
|
|
4944
7028
|
init_package();
|
|
4945
7029
|
init_create();
|
|
7030
|
+
import { resolve as resolve2 } from "node:path";
|
|
7031
|
+
import { cancel as cancel3, confirm, isCancel, log as log3 } from "@clack/prompts";
|
|
4946
7032
|
import { initTRPC } from "@trpc/server";
|
|
4947
7033
|
import { createCli } from "trpc-cli";
|
|
4948
7034
|
import { z as z2 } from "zod";
|
|
4949
7035
|
|
|
4950
7036
|
// src/prompts.ts
|
|
7037
|
+
init_src3();
|
|
4951
7038
|
init_src();
|
|
4952
|
-
import { cancel,
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
7039
|
+
import { cancel, group, groupMultiselect, log, select, text } from "@clack/prompts";
|
|
7040
|
+
|
|
7041
|
+
// src/theme.ts
|
|
7042
|
+
import { WriteStream } from "node:tty";
|
|
7043
|
+
import { isCI, unicode } from "@clack/prompts";
|
|
7044
|
+
var resolveDepth = () => {
|
|
7045
|
+
const stream = process.stdout;
|
|
7046
|
+
if (typeof stream.getColorDepth === "function")
|
|
7047
|
+
return stream.getColorDepth();
|
|
7048
|
+
return process.env.FORCE_COLOR ? WriteStream.prototype.getColorDepth() : 1;
|
|
7049
|
+
};
|
|
7050
|
+
var depth = resolveDepth();
|
|
7051
|
+
var wrap = (open, close) => (value) => `\x1B[${open}m${value}\x1B[${close}m`;
|
|
7052
|
+
var plain = (value) => value;
|
|
7053
|
+
var scale = (rgb, x256, basic) => {
|
|
7054
|
+
if (depth >= 24)
|
|
7055
|
+
return wrap(`38;2;${rgb[0]};${rgb[1]};${rgb[2]}`, "39");
|
|
7056
|
+
if (depth >= 8)
|
|
7057
|
+
return wrap(`38;5;${x256}`, "39");
|
|
7058
|
+
if (depth >= 4)
|
|
7059
|
+
return wrap(String(basic), "39");
|
|
7060
|
+
return plain;
|
|
7061
|
+
};
|
|
7062
|
+
var effect = (open, close) => depth >= 4 ? wrap(open, close) : plain;
|
|
7063
|
+
var accent = scale([124, 140, 255], 105, 36);
|
|
7064
|
+
var accentDim = scale([74, 85, 168], 61, 34);
|
|
7065
|
+
var muted = scale([153, 161, 179], 109, 90);
|
|
7066
|
+
var success = scale([74, 222, 128], 78, 32);
|
|
7067
|
+
var warn2 = scale([251, 191, 36], 214, 33);
|
|
7068
|
+
var danger = scale([248, 113, 113], 203, 31);
|
|
7069
|
+
var bold = effect("1", "22");
|
|
7070
|
+
var dim = effect("2", "22");
|
|
7071
|
+
var badge = (value) => depth >= 24 ? `\x1B[48;2;124;140;255m\x1B[30m ${value} \x1B[39m\x1B[49m` : depth >= 8 ? `\x1B[48;5;105m\x1B[30m ${value} \x1B[39m\x1B[49m` : depth >= 4 ? `\x1B[46m\x1B[30m ${value} \x1B[39m\x1B[49m` : ` ${value} `;
|
|
7072
|
+
var WORDMARK = [
|
|
7073
|
+
"████████╗██╗ ██╗██╗███████╗",
|
|
7074
|
+
"╚══██╔══╝██║ ██║██║╚══███╔╝",
|
|
7075
|
+
" ██║ ██║ █╗ ██║██║ ███╔╝ ",
|
|
7076
|
+
" ██║ ██║███╗██║██║ ███╔╝ ",
|
|
7077
|
+
" ██║ ╚███╔███╔╝██║███████╗",
|
|
7078
|
+
" ╚═╝ ╚══╝╚══╝ ╚═╝╚══════╝"
|
|
7079
|
+
];
|
|
7080
|
+
var WORDMARK_WIDTH = 30;
|
|
7081
|
+
var TAGLINE = "scaffold a Turborepo monorepo";
|
|
7082
|
+
var shade = (row) => (row.match(/█+|[^█]+/g) ?? []).map((run) => run.startsWith("█") ? accent(run) : accentDim(run)).join("");
|
|
7083
|
+
function banner(version) {
|
|
7084
|
+
if (!process.stdout.isTTY || isCI() || !unicode)
|
|
7085
|
+
return null;
|
|
7086
|
+
const label = `v${version}`;
|
|
7087
|
+
const gap = Math.max(2, WORDMARK_WIDTH - 2 - TAGLINE.length - label.length);
|
|
7088
|
+
const width = Math.max(WORDMARK_WIDTH, 2 + TAGLINE.length + gap + label.length);
|
|
7089
|
+
if ((process.stdout.columns ?? 80) < width + 2)
|
|
7090
|
+
return null;
|
|
7091
|
+
const footer = ` ${muted(TAGLINE)}${" ".repeat(gap)}${dim(label)}`;
|
|
7092
|
+
return `
|
|
7093
|
+
${WORDMARK.map(shade).join(`
|
|
7094
|
+
`)}
|
|
7095
|
+
|
|
7096
|
+
${footer}
|
|
7097
|
+
`;
|
|
7098
|
+
}
|
|
7099
|
+
|
|
7100
|
+
// src/prompts.ts
|
|
7101
|
+
var toOption = (entry2) => ({
|
|
7102
|
+
value: entry2.id,
|
|
7103
|
+
label: entry2.label,
|
|
7104
|
+
...entry2.hint ? { hint: entry2.hint } : {}
|
|
7105
|
+
});
|
|
7106
|
+
var PACKAGE_MANAGER_HINTS = {
|
|
7107
|
+
pnpm: "Strict node_modules, workspace: protocol",
|
|
7108
|
+
bun: "Fastest install, workspace: protocol",
|
|
7109
|
+
npm: "No workspace: protocol — deps are rewritten to *"
|
|
7110
|
+
};
|
|
7111
|
+
var DEPLOY_HINTS = {
|
|
7112
|
+
node: "@astrojs/node — runs anywhere, and what `astro preview` serves",
|
|
7113
|
+
vercel: "@astrojs/vercel — required once a server route deploys to Vercel"
|
|
7114
|
+
};
|
|
7115
|
+
var CATEGORY_LABELS = {
|
|
7116
|
+
ui: "UI",
|
|
7117
|
+
integration: "Integrations",
|
|
7118
|
+
devtools: "Developer tooling",
|
|
7119
|
+
"cross-cutting": "Cross-cutting"
|
|
7120
|
+
};
|
|
7121
|
+
var moduleGroups = () => {
|
|
7122
|
+
const catalog = moduleCatalog();
|
|
7123
|
+
const groups = {};
|
|
7124
|
+
for (const category of MODULE_CATEGORIES) {
|
|
7125
|
+
const entries = catalog.filter((entry2) => entry2.category === category);
|
|
7126
|
+
if (entries.length)
|
|
7127
|
+
groups[CATEGORY_LABELS[category]] = entries.map(toOption);
|
|
4957
7128
|
}
|
|
4958
|
-
return
|
|
7129
|
+
return groups;
|
|
7130
|
+
};
|
|
7131
|
+
var bail = () => {
|
|
7132
|
+
cancel("Cancelled.");
|
|
7133
|
+
process.exit(0);
|
|
4959
7134
|
};
|
|
4960
7135
|
async function gatherConfig(flags) {
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
7136
|
+
const answers = await group({
|
|
7137
|
+
name: () => flags.name ? Promise.resolve(flags.name) : text({
|
|
7138
|
+
message: "Project name",
|
|
7139
|
+
placeholder: "my-app",
|
|
7140
|
+
defaultValue: "my-app",
|
|
7141
|
+
validate: (value) => {
|
|
7142
|
+
const parsed = ProjectNameSchema.safeParse(value || "my-app");
|
|
7143
|
+
return parsed.success ? undefined : parsed.error.issues[0]?.message;
|
|
7144
|
+
}
|
|
7145
|
+
}),
|
|
7146
|
+
frontend: () => flags.frontend ? Promise.resolve(flags.frontend) : select({
|
|
7147
|
+
message: "Frontend framework",
|
|
7148
|
+
options: frontendCatalog().map(toOption),
|
|
7149
|
+
initialValue: "next"
|
|
7150
|
+
}),
|
|
7151
|
+
content: () => flags.content ? Promise.resolve(flags.content) : select({
|
|
7152
|
+
message: "Content source",
|
|
7153
|
+
options: contentCatalog().map(toOption)
|
|
7154
|
+
}),
|
|
7155
|
+
packageManager: () => flags.packageManager ? Promise.resolve(flags.packageManager) : select({
|
|
7156
|
+
message: "Package manager",
|
|
7157
|
+
options: PACKAGE_MANAGERS.map((value) => ({
|
|
7158
|
+
value,
|
|
7159
|
+
label: value,
|
|
7160
|
+
hint: PACKAGE_MANAGER_HINTS[value]
|
|
7161
|
+
}))
|
|
7162
|
+
}),
|
|
7163
|
+
deploy: ({ results }) => flags.deploy || results.frontend !== "astro" ? Promise.resolve(flags.deploy ?? "node") : select({
|
|
7164
|
+
message: "Deploy target",
|
|
7165
|
+
options: DEPLOY_TARGETS.map((value) => ({
|
|
7166
|
+
value,
|
|
7167
|
+
label: value,
|
|
7168
|
+
hint: DEPLOY_HINTS[value]
|
|
7169
|
+
}))
|
|
7170
|
+
}),
|
|
7171
|
+
modules: () => flags.modules ? Promise.resolve(flags.modules) : groupMultiselect({
|
|
7172
|
+
message: `Modules ${muted("(space to toggle, enter to confirm)")}`,
|
|
7173
|
+
options: moduleGroups(),
|
|
7174
|
+
required: false
|
|
7175
|
+
})
|
|
7176
|
+
}, { onCancel: bail });
|
|
7177
|
+
const picked = (answers.modules ?? []).filter((id) => MODULES.includes(id));
|
|
7178
|
+
const resolved = flags.modules ? { modules: picked, added: [] } : withPrerequisites(picked);
|
|
7179
|
+
for (const [module, requiredBy] of resolved.added) {
|
|
7180
|
+
log.info(`Added ${accent(module)} — required by ${accent(requiredBy)}`);
|
|
7181
|
+
}
|
|
4989
7182
|
return WizardConfigSchema.parse({
|
|
4990
|
-
name,
|
|
4991
|
-
frontend,
|
|
4992
|
-
content,
|
|
4993
|
-
packageManager,
|
|
4994
|
-
|
|
7183
|
+
name: answers.name,
|
|
7184
|
+
frontend: answers.frontend,
|
|
7185
|
+
content: answers.content,
|
|
7186
|
+
packageManager: answers.packageManager,
|
|
7187
|
+
deploy: answers.deploy,
|
|
7188
|
+
modules: resolved.modules,
|
|
4995
7189
|
experimental: { cacheComponents: flags.cacheComponents, reactCompiler: flags.reactCompiler }
|
|
4996
7190
|
});
|
|
4997
7191
|
}
|
|
4998
7192
|
|
|
7193
|
+
// src/ui.ts
|
|
7194
|
+
init_src3();
|
|
7195
|
+
init_src();
|
|
7196
|
+
import { relative, sep } from "node:path";
|
|
7197
|
+
import { cancel as cancel2, intro, log as log2, note, outro, taskLog } from "@clack/prompts";
|
|
7198
|
+
var RAW = { format: (line) => line };
|
|
7199
|
+
function printBanner(version) {
|
|
7200
|
+
const art = banner(version);
|
|
7201
|
+
if (art)
|
|
7202
|
+
process.stdout.write(`${art}
|
|
7203
|
+
`);
|
|
7204
|
+
}
|
|
7205
|
+
function start() {
|
|
7206
|
+
intro(badge("create-turbo-wizard"));
|
|
7207
|
+
}
|
|
7208
|
+
function displayPath(dir) {
|
|
7209
|
+
const rel = relative(process.cwd(), dir);
|
|
7210
|
+
return rel && !rel.startsWith("..") && !rel.startsWith(sep) ? `./${rel}` : dir;
|
|
7211
|
+
}
|
|
7212
|
+
function shape(tree) {
|
|
7213
|
+
const nested = new Map;
|
|
7214
|
+
let rootFiles = 0;
|
|
7215
|
+
for (const path of tree.keys()) {
|
|
7216
|
+
const [top, second] = path.split("/");
|
|
7217
|
+
if (!top)
|
|
7218
|
+
continue;
|
|
7219
|
+
if (second && (top === "apps" || top === "packages")) {
|
|
7220
|
+
const names = nested.get(top) ?? new Set;
|
|
7221
|
+
names.add(second);
|
|
7222
|
+
nested.set(top, names);
|
|
7223
|
+
} else if (!second) {
|
|
7224
|
+
rootFiles += 1;
|
|
7225
|
+
}
|
|
7226
|
+
}
|
|
7227
|
+
const parts = [...nested].sort(([a], [b]) => a.localeCompare(b)).map(([top, names]) => names.size > 3 ? `${names.size} ${top}` : `${top}/{${[...names].sort().join(", ")}}`);
|
|
7228
|
+
if (rootFiles)
|
|
7229
|
+
parts.push(`${rootFiles} root files`);
|
|
7230
|
+
return parts.join(" ");
|
|
7231
|
+
}
|
|
7232
|
+
var labelOf = (entries, id) => entries.find((entry2) => entry2.id === id)?.label ?? id;
|
|
7233
|
+
var row = (label, value) => `${muted(label.padEnd(10))}${value}`;
|
|
7234
|
+
function summary(config2, tree, dir) {
|
|
7235
|
+
const stack = [
|
|
7236
|
+
labelOf(frontendCatalog(), config2.frontend),
|
|
7237
|
+
labelOf(contentCatalog(), config2.content),
|
|
7238
|
+
config2.packageManager
|
|
7239
|
+
].join(" · ");
|
|
7240
|
+
note([
|
|
7241
|
+
row("Directory", accent(displayPath(dir))),
|
|
7242
|
+
row("Stack", stack),
|
|
7243
|
+
row("Modules", config2.modules.length ? config2.modules.join(", ") : muted("none")),
|
|
7244
|
+
row("Writing", `${tree.size} files ${muted(shape(tree))}`),
|
|
7245
|
+
"",
|
|
7246
|
+
muted("Reproduce this with"),
|
|
7247
|
+
dim(buildCommand(config2))
|
|
7248
|
+
].join(`
|
|
7249
|
+
`), "Summary", RAW);
|
|
7250
|
+
}
|
|
7251
|
+
function installLog(config2) {
|
|
7252
|
+
const task = taskLog({
|
|
7253
|
+
title: `Installing dependencies with ${config2.packageManager}`,
|
|
7254
|
+
limit: 8
|
|
7255
|
+
});
|
|
7256
|
+
return {
|
|
7257
|
+
onOutput: (line) => task.message(line, { raw: true }),
|
|
7258
|
+
finish: (error) => error ? task.error(error, { showLog: true }) : task.success(`${config2.packageManager} install`)
|
|
7259
|
+
};
|
|
7260
|
+
}
|
|
7261
|
+
var runScript = (config2, script) => config2.packageManager === "npm" ? `npm run ${script}` : `${config2.packageManager} ${script}`;
|
|
7262
|
+
function nextSteps(config2, tree, dir, installed) {
|
|
7263
|
+
const commands = [`cd ${displayPath(dir)}`];
|
|
7264
|
+
if (!installed)
|
|
7265
|
+
commands.push(`${config2.packageManager} install`);
|
|
7266
|
+
commands.push(runScript(config2, "dev"));
|
|
7267
|
+
if (config2.content === "sanity")
|
|
7268
|
+
commands.push(runScript(config2, "dev:studio"));
|
|
7269
|
+
const extras = [];
|
|
7270
|
+
if (tree.has(".env.example"))
|
|
7271
|
+
extras.push(row("Env", "copy .env.example to .env and fill it in"));
|
|
7272
|
+
extras.push(row("Docs", "https://turbo-wizard.vercel.app/docs"));
|
|
7273
|
+
note([...commands.map(accent), "", ...extras].join(`
|
|
7274
|
+
`), "Next steps", RAW);
|
|
7275
|
+
outro(success(`Created ${config2.name} — ${tree.size} files`));
|
|
7276
|
+
}
|
|
7277
|
+
function fail(message) {
|
|
7278
|
+
log2.error(message);
|
|
7279
|
+
cancel2("Nothing was written.");
|
|
7280
|
+
process.exit(1);
|
|
7281
|
+
}
|
|
7282
|
+
|
|
4999
7283
|
// src/router.ts
|
|
5000
7284
|
var t = initTRPC.create();
|
|
5001
7285
|
var CreateInput = z2.object({
|
|
@@ -5003,6 +7287,7 @@ var CreateInput = z2.object({
|
|
|
5003
7287
|
frontend: z2.enum(FRONTENDS).optional(),
|
|
5004
7288
|
content: z2.enum(CONTENT_SOURCES).optional(),
|
|
5005
7289
|
packageManager: z2.enum(PACKAGE_MANAGERS).optional(),
|
|
7290
|
+
deploy: z2.enum(DEPLOY_TARGETS).optional(),
|
|
5006
7291
|
modules: z2.array(z2.enum(MODULES)).optional(),
|
|
5007
7292
|
preset: z2.enum(PRESETS).optional(),
|
|
5008
7293
|
cacheComponents: z2.boolean().optional(),
|
|
@@ -5020,33 +7305,48 @@ var router = t.router({
|
|
|
5020
7305
|
frontend: input.frontend,
|
|
5021
7306
|
content: input.content ?? base.content,
|
|
5022
7307
|
packageManager: input.packageManager ?? base.packageManager,
|
|
7308
|
+
deploy: input.deploy ?? base.deploy,
|
|
5023
7309
|
modules: input.modules ?? base.modules,
|
|
5024
7310
|
cacheComponents: input.cacheComponents ?? base.experimental?.cacheComponents,
|
|
5025
7311
|
reactCompiler: input.reactCompiler ?? base.experimental?.reactCompiler
|
|
5026
7312
|
};
|
|
7313
|
+
printBanner(package_default.version);
|
|
7314
|
+
start();
|
|
5027
7315
|
const interactive = !input.yes && !input.preset && Boolean(process.stdout.isTTY) && (!input.frontend || !input.name);
|
|
5028
7316
|
const config2 = interactive ? await gatherConfig(flags) : WizardConfigSchema.parse({
|
|
5029
7317
|
name: flags.name ?? "my-app",
|
|
5030
7318
|
frontend: flags.frontend ?? "next",
|
|
5031
7319
|
content: flags.content,
|
|
5032
7320
|
packageManager: flags.packageManager,
|
|
7321
|
+
deploy: flags.deploy,
|
|
5033
7322
|
modules: flags.modules,
|
|
5034
7323
|
experimental: {
|
|
5035
7324
|
cacheComponents: flags.cacheComponents,
|
|
5036
7325
|
reactCompiler: flags.reactCompiler
|
|
5037
7326
|
}
|
|
5038
7327
|
});
|
|
5039
|
-
const
|
|
5040
|
-
|
|
7328
|
+
const plan = planProject(config2);
|
|
7329
|
+
if (!plan.ok)
|
|
7330
|
+
fail(plan.error.message);
|
|
7331
|
+
const dir = resolve2(input.dir ?? config2.name);
|
|
7332
|
+
summary(config2, plan.value, dir);
|
|
7333
|
+
if (interactive) {
|
|
7334
|
+
const proceed = await confirm({ message: "Proceed with scaffolding?" });
|
|
7335
|
+
if (isCancel(proceed) || !proceed) {
|
|
7336
|
+
cancel3("Nothing was written.");
|
|
7337
|
+
process.exit(0);
|
|
7338
|
+
}
|
|
7339
|
+
}
|
|
7340
|
+
const installer = input.install ? installLog(config2) : undefined;
|
|
7341
|
+
const result = await writeProject(plan.value, config2, {
|
|
7342
|
+
targetDir: dir,
|
|
5041
7343
|
install: input.install,
|
|
5042
|
-
git: input.git
|
|
7344
|
+
git: input.git,
|
|
7345
|
+
onWarn: (message) => log3.warn(message),
|
|
7346
|
+
onOutput: installer?.onOutput
|
|
5043
7347
|
});
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
process.exit(1);
|
|
5047
|
-
}
|
|
5048
|
-
console.error(`✓ created ${config2.name} → ${res.value.dir} (${res.value.fileCount} files)`);
|
|
5049
|
-
console.error(` next: cd ${res.value.dir} && ${config2.packageManager} install`);
|
|
7348
|
+
installer?.finish(result.installError);
|
|
7349
|
+
nextSteps(config2, plan.value, result.dir, result.installError === null);
|
|
5050
7350
|
})
|
|
5051
7351
|
});
|
|
5052
7352
|
var cli = createCli({ router, name: "create-turbo-wizard", version: package_default.version });
|