Skip to content

Commit 84fe5c2

Browse files
authored
feat: Sanity Workspaces (production + dev) + Studio CI deploy (#683)
- Two workspaces: production (dataset: production, preview: codingcat.dev) + dev (dataset: dev, preview: dev.codingcat.dev) - Shared config via buildPlugins(previewUrl), schemaTypes, documentActions — zero duplication - sanity.cli.ts uses env vars with hardcoded fallbacks - Studio deploy step in deploy.yml on main branch only (SANITY_AUTH_TOKEN) - Added package-lock.json to .gitignore (pnpm workspace)
1 parent 74f0c80 commit 84fe5c2

File tree

4 files changed

+89
-55
lines changed

4 files changed

+89
-55
lines changed

.github/workflows/deploy.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,10 @@ jobs:
5353
env:
5454
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
5555
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
56+
57+
- name: Deploy Sanity Studio
58+
if: github.ref == 'refs/heads/main'
59+
run: npx sanity deploy --no-login
60+
working-directory: apps/sanity
61+
env:
62+
SANITY_AUTH_TOKEN: ${{ secrets.SANITY_AUTH_TOKEN }}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ yarn-error.log*
3535
.vscode/*
3636
!.vscode/launch.json
3737
!.vscode/mcp.json
38+
# pnpm workspace — root pnpm-lock.yaml is the lockfile
39+
package-lock.json

apps/sanity/sanity.cli.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { defineCliConfig } from "sanity/cli";
22

33
export default defineCliConfig({
44
api: {
5-
projectId: "hfh83o0w",
6-
dataset: "production",
5+
projectId: process.env.SANITY_STUDIO_PROJECT_ID || "hfh83o0w",
6+
dataset: process.env.SANITY_STUDIO_DATASET || "production",
77
},
88
});

apps/sanity/sanity.config.ts

Lines changed: 78 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
/**
2-
* Sanity Studio configuration for standalone deployment
3-
* Migrated from Next.js embedded studio to standalone Sanity Studio app
2+
* Sanity Studio configuration with dev/production workspaces.
3+
*
4+
* Both workspaces share the same schemas, plugins, and structure.
5+
* They differ only in dataset and presentation tool preview URL.
6+
*
7+
* Workspace switcher appears in the Studio top-left corner.
48
*/
59
import { visionTool } from "@sanity/vision";
610
import { type PluginOptions, defineConfig } from "sanity";
@@ -46,15 +50,15 @@ import podcastSeries from "./schemas/documents/podcastSeries";
4650
import category from "./schemas/documents/category";
4751
import short from "./schemas/documents/short";
4852

49-
// Sanity Studio env vars (SANITY_STUDIO_ prefix is auto-exposed by Sanity CLI)
53+
// ── Shared constants ─────────────────────────────────────────────────
5054
const projectId = process.env.SANITY_STUDIO_PROJECT_ID || "hfh83o0w";
51-
const dataset = process.env.SANITY_STUDIO_DATASET || "production";
5255
const apiVersion = process.env.SANITY_STUDIO_API_VERSION || "2025-09-30";
53-
const studioUrl = "/";
56+
5457
// Set SANITY_STUDIO_DISABLE_PRESENTATION=true if you get network errors to api.sanity.io (e.g. firewall/VPN)
5558
const presentationEnabled =
5659
process.env.SANITY_STUDIO_DISABLE_PRESENTATION !== "true";
5760

61+
// ── Shared helpers ───────────────────────────────────────────────────
5862
function resolveHref(type: string, slug?: string): string | undefined {
5963
switch (type) {
6064
case "post":
@@ -71,6 +75,45 @@ const homeLocation = {
7175
href: "/",
7276
} satisfies DocumentLocation;
7377

78+
// ── Shared schema types ──────────────────────────────────────────────
79+
const schemaTypes = [
80+
// Portable text block types (table)
81+
tableSchema,
82+
rowType,
83+
// Singletons
84+
settings,
85+
engineConfig,
86+
// Documents
87+
author,
88+
guest,
89+
page,
90+
podcast,
91+
podcastType,
92+
post,
93+
sponsor,
94+
previewSession,
95+
sponsorshipRequest,
96+
contentIdea,
97+
automatedVideo,
98+
mediaAsset,
99+
videoAnalytics,
100+
sponsorLead,
101+
sponsorPool,
102+
// New document types
103+
podcastSeries,
104+
category,
105+
short,
106+
];
107+
108+
// ── Shared document actions ──────────────────────────────────────────
109+
const documentActions = (prev: any[], context: { schemaType: string }) => {
110+
if (context.schemaType === "post" || context.schemaType === "podcast") {
111+
return [sharePreviewAction, ...prev];
112+
}
113+
return prev;
114+
};
115+
116+
// ── Shared podcast structure ─────────────────────────────────────────
74117
export const podcastStructure = (): StructureResolver => {
75118
return (S) => {
76119
return S.list()
@@ -138,49 +181,9 @@ export const podcastStructure = (): StructureResolver => {
138181
};
139182
};
140183

141-
export default defineConfig({
142-
basePath: studioUrl,
143-
projectId,
144-
dataset,
145-
schema: {
146-
types: [
147-
// Portable text block types (table)
148-
tableSchema,
149-
rowType,
150-
// Singletons
151-
settings,
152-
engineConfig,
153-
// Documents
154-
author,
155-
guest,
156-
page,
157-
podcast,
158-
podcastType,
159-
post,
160-
sponsor,
161-
previewSession,
162-
sponsorshipRequest,
163-
contentIdea,
164-
automatedVideo,
165-
mediaAsset,
166-
videoAnalytics,
167-
sponsorLead,
168-
sponsorPool,
169-
// New document types
170-
podcastSeries,
171-
category,
172-
short,
173-
],
174-
},
175-
document: {
176-
actions: (prev, context) => {
177-
if (context.schemaType === "post" || context.schemaType === "podcast") {
178-
return [sharePreviewAction, ...prev];
179-
}
180-
return prev;
181-
},
182-
},
183-
plugins: [
184+
// ── Build plugins for a given preview URL ────────────────────────────
185+
function buildPlugins(previewUrl: string): PluginOptions[] {
186+
return [
184187
...(presentationEnabled
185188
? [
186189
presentationTool({
@@ -216,6 +219,7 @@ export default defineConfig({
216219
},
217220
},
218221
previewUrl: {
222+
origin: previewUrl,
219223
previewMode: {
220224
enable: "/api/draft-mode/enable",
221225
disable: "/api/draft-mode/disable",
@@ -225,12 +229,10 @@ export default defineConfig({
225229
]
226230
: []),
227231
structureTool({ structure: podcastStructure() }),
228-
// Configures the global "new document" button, and document actions, to suit the Settings document singleton
229232
singletonPlugin([
230233
settings.name,
231234
engineConfig.name,
232235
]),
233-
// Sets up AI Assist with preset prompts
234236
assistWithPresets(),
235237
media(),
236238
codeInput(),
@@ -242,7 +244,30 @@ export default defineConfig({
242244
},
243245
],
244246
}),
245-
// Vision lets you query your content with GROQ in the studio
246247
visionTool({ defaultApiVersion: apiVersion }),
247-
].filter(Boolean) as PluginOptions[],
248-
});
248+
].filter(Boolean) as PluginOptions[];
249+
}
250+
251+
// ── Workspace definitions ────────────────────────────────────────────
252+
export default defineConfig([
253+
{
254+
name: "production",
255+
title: "CodingCat.dev (Production)",
256+
projectId,
257+
dataset: "production",
258+
basePath: "/production",
259+
schema: { types: schemaTypes },
260+
document: { actions: documentActions },
261+
plugins: buildPlugins("https://codingcat.dev"),
262+
},
263+
{
264+
name: "dev",
265+
title: "CodingCat.dev (Dev)",
266+
projectId,
267+
dataset: "dev",
268+
basePath: "/dev",
269+
schema: { types: schemaTypes },
270+
document: { actions: documentActions },
271+
plugins: buildPlugins("https://dev.codingcat.dev"),
272+
},
273+
]);

0 commit comments

Comments
 (0)