Skip to content

Commit 0a5470c

Browse files
committed
Merge branch 'release' into deploy-web
2 parents e1cf337 + ae59f57 commit 0a5470c

File tree

28 files changed

+3728
-2
lines changed

28 files changed

+3728
-2
lines changed

web/.claude/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"permissions": {
3+
"allow": ["Bash(npx tsc:*)", "Bash(npx tsx:*)", "mcp__chrome-devtools__*"]
4+
}
5+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Crossposting Blog Articles to Dev.to and Medium
2+
3+
This skill allows you to crosspost Wasp blog articles (MDX) to DEV.to and Medium.
4+
If your article has local .mp4 videos, the skill can optionally upload them to YouTube for use on these platforms.
5+
6+
## API Keys and Secrets
7+
8+
The Dev.to API key, as well as the Youtube Client ID and Secret, are saved in the Wasp shared 1password account.
9+
10+
For Medium, allow Claude to start a Chrome browser session, then use email login (not google oauth) to log in with `info@wasp-lang.dev` using the OTP sent to your email.
11+
12+
## How To Crosspost
13+
14+
- Finish your MDX blogpost for wasp.sh
15+
- Invoke the skill: `/crossposting`
16+
17+
If you need more info, just ask Claude how it works.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
---
2+
name: crossposting
3+
description: Crosspost Wasp blog articles (MDX) to DEV.to and Medium.
4+
---
5+
6+
# MDX Conversion Script
7+
8+
Converts a Wasp blog MDX file to clean markdown, HTML, and Medium-ready chunks. Output is saved to `.claude/skills/crossposting/scripts/output/`.
9+
10+
```bash
11+
npx tsx .claude/skills/crossposting/scripts/convert-mdx.ts <path-to-mdx-file> [--publish-devto] [--update-devto <id>] [--upload-videos]
12+
```
13+
14+
**Output files (always written):**
15+
- `<slug>.md` — clean markdown (for DEV.to)
16+
- `<slug>.html` — HTML (for Medium preview/reference)
17+
- `<slug>-medium-chunks.json``{ title: string, chunks: string[] }` pre-split at `<h2>` boundaries, pre-escaped for JS template literals
18+
19+
**Flags:**
20+
- `--publish-devto` — POST as draft to DEV.to (requires `DEVTO_API_KEY`)
21+
- `--update-devto <id>` — PUT to existing DEV.to article (requires `DEVTO_API_KEY`)
22+
- `--upload-videos` — upload local .mp4s to YouTube (unlisted), embed as `{% youtube URL %}` liquid tags
23+
24+
**Env vars:** `DEVTO_API_KEY` (from https://dev.to/settings/extensions), `YOUTUBE_CLIENT_ID` + `YOUTUBE_CLIENT_SECRET` (saved in 1password for the users).
25+
26+
**Notes:**
27+
- `canonical_url` is auto-generated from MDX filename → `wasp.sh/blog/...`
28+
- Without `--upload-videos`, local .mp4 videos become plain links
29+
30+
### YouTube Setup (one-time, human steps)
31+
32+
1. Create Google Cloud project → enable YouTube Data API v3 → create OAuth 2.0 Desktop credentials
33+
2. Add `YOUTUBE_CLIENT_ID` and `YOUTUBE_CLIENT_SECRET` to `~/.zshrc`
34+
3. Run `npx tsx .claude/skills/crossposting/scripts/upload-youtube.ts --auth` → authorize in browser (select @wasplang channel if prompted)
35+
4. Refresh token stored in `~/.youtube-upload-tokens.json`
36+
5. Check channel: `npx tsx .claude/skills/crossposting/scripts/upload-youtube.ts --whoami`. Wrong channel? Delete token file and re-auth.
37+
38+
---
39+
40+
# Crossposting to Dev.to
41+
42+
Use `--publish-devto` or `--update-devto <id>` flags. Requires `DEVTO_API_KEY`.
43+
44+
---
45+
46+
# Crossposting to Medium
47+
48+
Paste HTML into Medium's editor via Chrome DevTools MCP. The user must be logged in to Medium using email login (not google oauth) with `info@wasp-lang.dev`.
49+
50+
## Steps
51+
52+
### 1. Convert the MDX article
53+
54+
Run the conversion script, if it hasn't been run yet (see usage above).
55+
56+
**Images:** Medium does not support webp. Convert webp images to jpg for later manual upload:
57+
```bash
58+
for f in static/img/<slug>/*.webp; do sips -s format jpeg "$f" --out ".claude/skills/crossposting/scripts/output/$(basename "${f%.webp}.jpg")"; done
59+
```
60+
61+
### 2. Navigate to `https://medium.com/new-story`
62+
63+
The human user must login to Medium with the `info@wasp-lang.dev` email and get the OTP sent to your email.
64+
65+
### 3. Set the article title
66+
67+
**Do NOT use the `fill` tool** — it puts text into the body paragraph instead. Use `evaluate_script` with the `setMediumTitle` function from `scripts/medium-helpers.js`.
68+
69+
1. Read `setMediumTitle` from `scripts/medium-helpers.js`.
70+
2. Replace `TITLE_PLACEHOLDER` with the actual title (escape backticks and `${` sequences).
71+
3. Call `evaluate_script` with the resulting function string.
72+
73+
Then press `Enter` to move cursor to the body area.
74+
75+
### 4. Paste the article body
76+
77+
**IMPORTANT:** Do NOT use `document.execCommand` — it bypasses Medium's internal state and causes save errors. Always use synthetic `ClipboardEvent` paste.
78+
79+
**IMPORTANT:** Chunks are already pre-escaped for JS template literals and pre-processed for Medium (YouTube URLs, image placeholders). Inline each chunk string directly into the `pasteMediumChunk` function body where `HTML_CHUNK_PLACEHOLDER` appears. Do NOT base64-encode, store chunks in page variables, or add any intermediate encoding steps.
80+
81+
1. Read `output/<slug>-medium-chunks.json` — chunks are ready to paste as-is.
82+
2. Click the body paragraph to focus it.
83+
3. For each chunk, use `evaluate_script` with the `pasteMediumChunk` function from `scripts/medium-helpers.js`. Replace `HTML_CHUNK_PLACEHOLDER` directly with the chunk string content.
84+
4. Wait for "Saved" status after all chunks are pasted.
85+
86+
### 5. Verify
87+
88+
- Snapshot the article to confirm it looks correct and shows "Draft" / "Saved"
89+
- Scroll through to confirm section order matches the original and make any necessary adjustments.
90+
91+
### 6. Before Publishing (human steps)
92+
93+
- Replace `[IMAGE: filename.jpg]` markers with actual image uploads in Medium's editor
94+
- Add any YouTube embeds that didn't auto-embed (Medium auto-embeds standalone YouTube URLs in `<p>` tags)
95+
- Add the banner image and canonical URL
96+
- Review and publish from Medium's UI

0 commit comments

Comments
 (0)