Skip to content

Commit bf050b3

Browse files
authored
Merge pull request #751 from CodingCatDev/dev
chore(release): merge dev into main (Sanity schema & Studio refactor)
2 parents 08fc815 + 28d7896 commit bf050b3

56 files changed

Lines changed: 4267 additions & 11935 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
---
2+
name: sanity-editorial-workflow
3+
description: End-to-end editorial workflow for CodingCat.dev using Sanity MCP, Sanity CLI, Gemini writing, cover photo generation/upload, and Sanity Content Releases readiness checks. Use when drafting blog posts or podcast show notes, creating or uploading cover images, checking draft/release status, or preparing Sanity Content Releases.
4+
---
5+
6+
# Sanity Editorial Workflow (MCP, CLI, Gemini Writing, Photos & Content Releases)
7+
8+
Use this skill whenever the user asks to:
9+
- Check the status of drafts, upcoming episodes/posts, or **Sanity Content Releases**
10+
- Write or update blog posts (`post`) or podcast show notes (`podcast`) using Gemini (from a topic or a YouTube `transcript`)
11+
- Generate or upload cover images (`coverImage`) to Sanity and attach them to documents
12+
- Stage or audit documents in a Sanity Content Release before publishing
13+
14+
Do **not** create custom wrapper scripts when you can execute operations directly via **Sanity MCP** (`codingcat-dev` / Sanity Context MCP) and **Sanity CLI** (`pnpm --filter @codingcatdev/sanity exec sanity ...`).
15+
16+
---
17+
18+
## 1. Checking Content Releases & Publishing Readiness
19+
20+
Sanity stores documents in three ID namespaces depending on their lifecycle state:
21+
- **Published**: `<docId>`
22+
- **Drafts**: `drafts.<docId>`
23+
- **Content Releases**: `versions.<releaseId>.<docId>` (paired with a release metadata document of `_type == "system.release"`)
24+
25+
### A. Inspect Active Content Releases & Versioned Documents
26+
Run via Sanity CLI (`--with-user-token` reads `drafts.**` and `versions.**` using the logged-in Sanity CLI session):
27+
28+
```bash
29+
pnpm --filter @codingcatdev/sanity exec sanity documents query \
30+
'{
31+
"releases": *[_type == "system.release"] | order(_updatedAt desc) {
32+
_id,
33+
name,
34+
state,
35+
publishAt,
36+
"metadata": metadata { title, description, releaseType }
37+
},
38+
"releaseDocs": *[_id in path("versions.**") && _type in ["post", "podcast"]] | order(_updatedAt desc) {
39+
_id,
40+
_type,
41+
title,
42+
"slug": slug.current,
43+
date,
44+
"hasCoverImage": defined(coverImage.asset._ref),
45+
"hasExcerpt": defined(excerpt) && length(excerpt) > 10,
46+
"blockCount": count(content),
47+
"hasYoutube": defined(youtube),
48+
"hasSpotify": defined(spotify),
49+
"hasTranscript": defined(transcript)
50+
},
51+
"drafts": *[_id in path("drafts.**") && _type in ["post", "podcast"]] | order(_updatedAt desc) {
52+
_id,
53+
_type,
54+
title,
55+
"slug": slug.current,
56+
date,
57+
"hasCoverImage": defined(coverImage.asset._ref),
58+
"hasExcerpt": defined(excerpt) && length(excerpt) > 10,
59+
"blockCount": count(content),
60+
"hasYoutube": defined(youtube),
61+
"hasSpotify": defined(spotify),
62+
"hasTranscript": defined(transcript)
63+
}
64+
}' --dataset production --api-version v2025-09-30
65+
```
66+
67+
### B. Publishing Readiness Checklist
68+
Before a `post` or `podcast` is ready to publish (or ready inside a Content Release), verify:
69+
1. **Shared Required Fields (`post` & `podcast`)**:
70+
- `title` (non-empty string)
71+
- `slug.current` (lowercase hyphenated slug)
72+
- `date` (ISO 8601 datetime)
73+
- `coverImage.asset._ref` (16:9 image uploaded to Sanity)
74+
- `excerpt` (1–2 sentence summary, <= 160 chars recommended)
75+
- `content` (non-empty Portable Text array)
76+
- `author` (defaults to Alex Patterson `_ref: "RIgC5r4TJ0hCGOzxHszD1w"`)
77+
2. **Podcast-Specific Fields (`podcast`)**:
78+
- `season` (integer) & `episode` (integer)
79+
- `recordingDate` (ISO 8601 datetime)
80+
- `guest` (array of references to `guest` documents)
81+
- `youtube` (YouTube URL — triggers `sync-youtube-transcript` on publish)
82+
- `spotify` & `listenLinks` (auto-synced by `sync-podcast-links` once published on Anchor/Spotify RSS)
83+
84+
---
85+
86+
## 2. Writing Posts & Podcast Show Notes via Gemini
87+
88+
### Step 1: Retrieve Source Context (Transcript or Existing Draft)
89+
- Use the `codingcat-dev` MCP tool `get_content_by_slug` or `query_content`, or run a Sanity CLI query to fetch the document and its `transcript->fullText`, `transcript->chapters`, and `guest[]->`:
90+
```bash
91+
pnpm --filter @codingcatdev/sanity exec sanity documents query \
92+
'*[_type in ["post", "podcast"] && (slug.current == $slug || _id == $id)][0]{
93+
_id, _type, title, "slug": slug.current, excerpt, youtube,
94+
"transcript": transcript->{ fullText, summary, chapters },
95+
"guests": guest[]->{ title, excerpt, socials, websites }
96+
}' --dataset production
97+
```
98+
99+
### Step 2: Draft Content & Convert Markdown to Portable Text
100+
- Write the article or episode show notes in clean Markdown:
101+
- Use `##` (`h2`) and `###` (`h3`) headings to structure sections.
102+
- Include bulleted takeaways, links mentioned, and fenced code blocks where relevant.
103+
- Do **not** repeat the document `title` as an `# h1` at the top of `content`.
104+
- Write a crisp 1–2 sentence `excerpt` that complements the title without repeating it.
105+
- Convert the Markdown to Sanity Portable Text blocks using `@portabletext/markdown` (`markdownToPortableText`) — see `.agents/skills/portable-text-conversion/SKILL.md`.
106+
107+
### Step 3: Save to Draft (`drafts.<id>`) or Content Release (`versions.<releaseId>.<id>`)
108+
- Target `drafts.<docId>` (or `versions.<releaseId>.<docId>` if staging for a Content Release) so changes can be reviewed in Sanity Studio before going live:
109+
```bash
110+
pnpm --filter @codingcatdev/sanity exec sanity documents create <json-file> --replace --dataset production
111+
```
112+
113+
---
114+
115+
## 3. Creating & Uploading Photos (`coverImage`)
116+
117+
### Step 1: Generate a 16:9 Cover Image (When Needed)
118+
- Invoke the `image-generator` subagent with a clear 16:9 visual brief tailored to the post/podcast topic and CodingCat.dev's modern developer aesthetic (avoiding cluttered text overlays if the title is already rendered on cards).
119+
120+
### Step 2: Upload the Image Asset & Attach `coverImage`
121+
- Upload the local image file to Sanity's Asset API and patch the target draft/release document using `@sanity/client` via `pnpm --filter @codingcatdev/sanity exec sanity exec <script> --with-user-token`:
122+
```ts
123+
import { createReadStream } from "node:fs";
124+
import { basename } from "node:path";
125+
import { getCliClient } from "sanity/cli";
126+
127+
const client = getCliClient({ apiVersion: "2025-09-30" });
128+
const asset = await client.assets.upload("image", createReadStream(filePath), {
129+
filename: basename(filePath),
130+
});
131+
132+
await client
133+
.patch(targetDocumentId) // e.g. "drafts.<id>" or "versions.<releaseId>.<id>"
134+
.set({
135+
coverImage: {
136+
_type: "image",
137+
asset: {
138+
_type: "reference",
139+
_ref: asset._id,
140+
},
141+
},
142+
})
143+
.commit();
144+
```

‎.github/workflows/deploy.yml‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ jobs:
2929
- name: Install dependencies
3030
run: pnpm install --frozen-lockfile
3131

32+
- name: Export Sanity production dataset backup before deploy
33+
run: |
34+
curl -f -sS "https://hfh83o0w.api.sanity.io/v2021-06-07/data/export/production" | gzip > /tmp/sanity-production-backup.ndjson.gz
35+
36+
- name: Upload Sanity production dataset backup artifact
37+
uses: actions/upload-artifact@v4
38+
with:
39+
name: sanity-production-predeploy-backup-dev-${{ github.sha }}
40+
path: /tmp/sanity-production-backup.ndjson.gz
41+
retention-days: 30
42+
3243
- name: Deploy Sanity Studio
3344
run: npx sanity deploy -y
3445
working-directory: apps/sanity
@@ -62,7 +73,7 @@ jobs:
6273
echo "Resolved Organization ID: $ORG_ID"
6374
mkdir -p .sanity
6475
echo "{\"organizationId\":\"$ORG_ID\"}" > .sanity/blueprint.config.json
65-
pnpm exec sanity blueprints deploy --organization-id "$ORG_ID" --stack ${{ vars.SANITY_STUDIO_DATASET }}
76+
pnpm exec sanity blueprints deploy --organization-id "$ORG_ID" --stack ${{ vars.SANITY_STUDIO_DATASET }} --no-wait
6677
env:
6778
SANITY_AUTH_TOKEN: ${{ secrets.SANITY_AUTH_TOKEN }}
6879
SANITY_API_WRITE_TOKEN: ${{ secrets.SANITY_API_WRITE_TOKEN }}
@@ -90,6 +101,19 @@ jobs:
90101
- name: Install dependencies
91102
run: pnpm install --frozen-lockfile
92103

104+
- name: Export Sanity production dataset backup before deploy
105+
run: |
106+
curl -f -sS "https://hfh83o0w.api.sanity.io/v2021-06-07/data/export/production" | gzip > /tmp/sanity-production-backup.ndjson.gz
107+
env:
108+
SANITY_AUTH_TOKEN: ${{ secrets.SANITY_AUTH_TOKEN }}
109+
110+
- name: Upload Sanity production dataset backup artifact
111+
uses: actions/upload-artifact@v4
112+
with:
113+
name: sanity-production-predeploy-backup-${{ github.sha }}
114+
path: /tmp/sanity-production-backup.ndjson.gz
115+
retention-days: 30
116+
93117
- name: Deploy Sanity Studio
94118
run: npx sanity deploy -y
95119
working-directory: apps/sanity
@@ -123,7 +147,7 @@ jobs:
123147
echo "Resolved Organization ID: $ORG_ID"
124148
mkdir -p .sanity
125149
echo "{\"organizationId\":\"$ORG_ID\"}" > .sanity/blueprint.config.json
126-
pnpm exec sanity blueprints deploy --organization-id "$ORG_ID" --stack ${{ vars.SANITY_STUDIO_DATASET }}
150+
pnpm exec sanity blueprints deploy --organization-id "$ORG_ID" --stack ${{ vars.SANITY_STUDIO_DATASET }} --no-wait
127151
env:
128152
SANITY_AUTH_TOKEN: ${{ secrets.SANITY_AUTH_TOKEN }}
129153
SANITY_API_WRITE_TOKEN: ${{ secrets.SANITY_API_WRITE_TOKEN }}

0 commit comments

Comments
 (0)