Skip to content

Commit ebbbfb9

Browse files
publish mdx artifacts to preview kv with pr cache lifecycle
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
1 parent 50e3562 commit ebbbfb9

File tree

9 files changed

+256
-135
lines changed

9 files changed

+256
-135
lines changed

.dockerignore

Lines changed: 0 additions & 2 deletions
This file was deleted.

.github/workflows/deploy.yml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,18 +116,26 @@ jobs:
116116
env:
117117
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
118118
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false }}
119-
MDX_REMOTE_R2_BUCKET: ${{ secrets.MDX_REMOTE_R2_BUCKET || secrets.R2_BUCKET }}
120119
run: |
121120
set -euo pipefail
122121
dry_run_flag=()
123-
mdx_remote_r2_flag=()
124122
if [ "$DRY_RUN" = "true" ]; then
125123
dry_run_flag+=(--dry-run)
126124
fi
127-
if [ -n "${MDX_REMOTE_R2_BUCKET:-}" ]; then
128-
mdx_remote_r2_flag+=(--mdx-remote-r2-bucket "$MDX_REMOTE_R2_BUCKET")
125+
bun tools/ci/preview-resources.ts ensure --worker-name kentcdodds-com --environment production --wrangler-config wrangler.jsonc --out-config wrangler-production.generated.jsonc "${dry_run_flag[@]}" | tee -a "$GITHUB_OUTPUT"
126+
127+
- name: 📚 Publish mdx-remote artifacts to production KV
128+
env:
129+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
130+
WRANGLER_CONFIG: ${{ steps.resources.outputs.wrangler_config }}
131+
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false }}
132+
run: |
133+
set -euo pipefail
134+
publish_args=(--kv-binding MDX_REMOTE_KV --wrangler-config "$WRANGLER_CONFIG" --wrangler-env production)
135+
if [ "$DRY_RUN" = "true" ]; then
136+
publish_args+=(--dry-run)
129137
fi
130-
bun tools/ci/preview-resources.ts ensure --worker-name kentcdodds-com --environment production --wrangler-config wrangler.jsonc --out-config wrangler-production.generated.jsonc "${dry_run_flag[@]}" "${mdx_remote_r2_flag[@]}" | tee -a "$GITHUB_OUTPUT"
138+
bun run content:publish-mdx-remote -- "${publish_args[@]}"
131139
132140
- name: 🗄️ Apply D1 migrations
133141
if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.dry_run) }}

.github/workflows/preview.yml

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ on:
3232
default: false
3333

3434
permissions:
35+
actions: write
3536
contents: read
3637
pull-requests: write
3738

@@ -101,18 +102,23 @@ jobs:
101102
env:
102103
WORKER_NAME: ${{ steps.names.outputs.worker_name }}
103104
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false }}
104-
MDX_REMOTE_R2_BUCKET: ${{ secrets.MDX_REMOTE_R2_BUCKET || secrets.R2_BUCKET }}
105105
run: |
106106
set -euo pipefail
107107
dry_run_flag=()
108-
mdx_remote_r2_flag=()
109108
if [ "$DRY_RUN" = "true" ]; then
110109
dry_run_flag+=(--dry-run)
111110
fi
112-
if [ -n "${MDX_REMOTE_R2_BUCKET:-}" ]; then
113-
mdx_remote_r2_flag+=(--mdx-remote-r2-bucket "$MDX_REMOTE_R2_BUCKET")
114-
fi
115-
bun tools/ci/preview-resources.ts ensure --worker-name "$WORKER_NAME" --environment preview --wrangler-config wrangler.jsonc --out-config wrangler-preview.generated.jsonc "${dry_run_flag[@]}" "${mdx_remote_r2_flag[@]}" | tee -a "$GITHUB_OUTPUT"
111+
bun tools/ci/preview-resources.ts ensure --worker-name "$WORKER_NAME" --environment preview --wrangler-config wrangler.jsonc --out-config wrangler-preview.generated.jsonc "${dry_run_flag[@]}" | tee -a "$GITHUB_OUTPUT"
112+
113+
- name: ♻️ Restore mdx-remote cache (PR + main warm baseline)
114+
id: mdx-cache-restore
115+
uses: actions/cache/restore@v4
116+
with:
117+
path: other/content/mdx-remote
118+
key: mdx-remote-${{ runner.os }}-pr-${{ github.event.pull_request.number || github.ref_name }}-${{ github.sha }}
119+
restore-keys: |
120+
mdx-remote-${{ runner.os }}-pr-${{ github.event.pull_request.number || github.ref_name }}-
121+
mdx-remote-${{ runner.os }}-main-
116122
117123
- name: 🧪 Deploy preview mock workers
118124
id: deploy-mocks
@@ -160,6 +166,31 @@ jobs:
160166
preview --from-dotenv .env.example --generate-cookie-secret
161167
--include-empty
162168
169+
- name: 📚 Compile + publish mdx-remote artifacts to preview KV
170+
env:
171+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
172+
WRANGLER_CONFIG: ${{ steps.resources.outputs.wrangler_config }}
173+
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false }}
174+
BEFORE_SHA: ${{ github.event.pull_request.base.sha || github.event.before || '' }}
175+
AFTER_SHA: ${{ github.sha }}
176+
run: |
177+
set -euo pipefail
178+
publish_args=(--kv-binding MDX_REMOTE_KV --wrangler-config "$WRANGLER_CONFIG" --wrangler-env preview)
179+
if [ -n "${BEFORE_SHA:-}" ] && [ "$BEFORE_SHA" != "0000000000000000000000000000000000000000" ]; then
180+
publish_args+=(--before "$BEFORE_SHA" --after "$AFTER_SHA")
181+
fi
182+
if [ "$DRY_RUN" = "true" ]; then
183+
publish_args+=(--dry-run)
184+
fi
185+
bun run content:publish-mdx-remote -- "${publish_args[@]}"
186+
187+
- name: ♻️ Save mdx-remote cache
188+
if: ${{ always() && !(github.event_name == 'workflow_dispatch' && inputs.dry_run) }}
189+
uses: actions/cache/save@v4
190+
with:
191+
path: other/content/mdx-remote
192+
key: mdx-remote-${{ runner.os }}-pr-${{ github.event.pull_request.number || github.ref_name }}-${{ github.sha }}
193+
163194
- name: ☁️ Deploy preview app worker
164195
id: deploy-preview
165196
env:
@@ -326,3 +357,30 @@ jobs:
326357
dry_run_flag+=(--dry-run)
327358
fi
328359
bun tools/ci/preview-resources.ts cleanup --worker-name "$WORKER_NAME" --environment preview --out-config wrangler-preview.generated.jsonc "${dry_run_flag[@]}"
360+
361+
- name: 🗑️ Delete PR mdx-remote caches
362+
if: ${{ github.event_name == 'pull_request' }}
363+
uses: actions/github-script@v7
364+
env:
365+
CACHE_KEY_PREFIX: mdx-remote-${{ runner.os }}-pr-${{ github.event.pull_request.number }}-
366+
with:
367+
script: |
368+
const { owner, repo } = context.repo
369+
const keyPrefix = process.env.CACHE_KEY_PREFIX
370+
const cacheResponse = await github.rest.actions.getActionsCacheList({
371+
owner,
372+
repo,
373+
key: keyPrefix,
374+
per_page: 100,
375+
})
376+
const caches = cacheResponse.data.actions_caches ?? []
377+
const matching = caches.filter((cache) => cache.key?.startsWith(keyPrefix))
378+
core.info(`Found ${matching.length} cache entries with prefix ${keyPrefix}`)
379+
for (const cache of matching) {
380+
await github.rest.actions.deleteActionsCacheById({
381+
owner,
382+
repo,
383+
cache_id: cache.id,
384+
})
385+
core.info(`Deleted cache ${cache.id} (${cache.key})`)
386+
}

.github/workflows/refresh-content.yml

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,65 @@ jobs:
2828
- name: 📥 Download deps
2929
run: bun install --frozen-lockfile
3030

31+
- name: 🧾 Resolve worker/environment for branch
32+
id: target
33+
shell: bash
34+
env:
35+
REF_NAME: ${{ github.ref_name }}
36+
run: |
37+
set -euo pipefail
38+
if [ "$REF_NAME" = "main" ]; then
39+
echo "worker_name=kentcdodds-com" >> "$GITHUB_OUTPUT"
40+
echo "environment=production" >> "$GITHUB_OUTPUT"
41+
else
42+
echo "worker_name=kentcdodds-com-development" >> "$GITHUB_OUTPUT"
43+
echo "environment=development" >> "$GITHUB_OUTPUT"
44+
fi
45+
46+
- name: ♻️ Restore mdx-remote cache
47+
id: mdx-cache-restore
48+
uses: actions/cache/restore@v4
49+
with:
50+
path: other/content/mdx-remote
51+
key: mdx-remote-${{ runner.os }}-${{ github.ref_name }}-${{ github.sha }}
52+
restore-keys: |
53+
mdx-remote-${{ runner.os }}-${{ github.ref_name }}-
54+
mdx-remote-${{ runner.os }}-main-
55+
56+
- name: 🧱 Ensure branch resources
57+
id: resources
58+
env:
59+
WORKER_NAME: ${{ steps.target.outputs.worker_name }}
60+
TARGET_ENV: ${{ steps.target.outputs.environment }}
61+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
62+
run: |
63+
set -euo pipefail
64+
bun tools/ci/preview-resources.ts ensure --worker-name "$WORKER_NAME" --environment "$TARGET_ENV" --wrangler-config wrangler.jsonc --out-config wrangler-content.generated.jsonc | tee -a "$GITHUB_OUTPUT"
65+
3166
- name: 📚 Publish mdx-remote artifacts
3267
run: |
3368
set -euo pipefail
3469
bun run content:publish-mdx-remote -- \
35-
--bucket "${{ secrets.MDX_REMOTE_R2_BUCKET || secrets.R2_BUCKET }}" \
70+
--kv-binding MDX_REMOTE_KV \
71+
--wrangler-config "${{ steps.resources.outputs.wrangler_config }}" \
72+
--wrangler-env "${{ steps.target.outputs.environment }}" \
3673
--before "${{ github.event.before }}" \
3774
--after "${{ github.sha }}"
3875
env:
39-
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
4076
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
4177

78+
- name: ♻️ Save mdx-remote cache
79+
if: ${{ always() }}
80+
uses: actions/cache/save@v4
81+
with:
82+
path: other/content/mdx-remote
83+
key: mdx-remote-${{ runner.os }}-${{ github.ref_name }}-${{ github.sha }}
84+
4285
- name: 🥬 Refresh Content
4386
run: bun ./other/refresh-changed-content.js ${{ github.sha }}
4487
env:
4588
REFRESH_CACHE_SECRET: ${{ secrets.REFRESH_CACHE_SECRET }}
89+
90+
- name: 🧹 Cleanup generated content config
91+
if: always()
92+
run: rm -f wrangler-content.generated.jsonc

docs/agents/project-context.md

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,11 @@ reference:
8787
`${MEDIA_BASE_URL}/stream`).
8888
- current deployment default media base is `https://media.kcd.dev`
8989
(planned production cutover target: `https://media.kentcdodds.com`).
90-
- `ENABLE_MDX_REMOTE=true` enables runtime rendering via the new mdx-remote
91-
JSON-tree interpreter path.
92-
- Runtime mdx-remote document loading order (when `ENABLE_MDX_REMOTE=true`):
93-
1) KV binding (`MDX_REMOTE_KV`)
94-
2) R2 binding (`MDX_REMOTE_R2`)
95-
3) HTTP artifact host (`MDX_REMOTE_BASE_URL`)
96-
4) local artifact files in mocks mode (`MDX_REMOTE_LOCAL_ARTIFACT_DIRECTORY`,
97-
defaults to `other/content/mdx-remote`)
98-
- GitHub content fetches in mocks mode use local filesystem fallback in
99-
`app/utils/github.server.ts` (no network call required for content paths).
90+
- Runtime MDX rendering now uses a single path:
91+
- `MDX_REMOTE_KV` binding only (no runtime fallback chain).
92+
- GitHub content fetches in mocks mode still use local filesystem fallback in
93+
`app/utils/github.server.ts` for tools/routes that explicitly read source
94+
files, but page rendering no longer uses GitHub fallback compilation.
10095
- Media workflow:
10196
- canonical manifests live in `content/data/media-manifests/{images,videos}.json`
10297
- one-time Cloudinary bootstrap to local `content/**`:
@@ -159,15 +154,14 @@ reference:
159154
- `bun run content:compile-mdx-remote:dry-run`
160155
- `bun run content:compile-mdx-remote:strict-report` (strict validation report
161156
for component/expression compatibility; exits non-zero on failures)
162-
- `bun run content:publish-mdx-remote -- --bucket <r2-bucket> [--before <sha> --after <sha>]`
163-
publishes strict-validated mdx-remote artifacts to R2 (changed-entry upload +
157+
- `bun run content:publish-mdx-remote -- --kv-binding MDX_REMOTE_KV --wrangler-config <config-path> --wrangler-env <env> [--before <sha> --after <sha>]`
158+
publishes strict-validated mdx-remote artifacts to KV (changed-entry upload +
164159
deleted-entry cleanup when SHAs are provided).
165160
- `bun run content:watch-mdx-remote` watches content collections locally and
166161
continuously rebuilds strict mdx-remote artifacts into
167162
`other/content/mdx-remote/` (use `--once` for a single local rebuild pass).
168-
- Preview/production generated Wrangler configs can attach an R2 binding for
169-
runtime mdx docs via `--mdx-remote-r2-bucket <bucket-name>` on
170-
`tools/ci/preview-resources.ts`.
163+
- Preview/production generated Wrangler configs attach dedicated KV bindings
164+
for runtime mdx docs (`MDX_REMOTE_KV`) via `tools/ci/preview-resources.ts`.
171165
- generates serialized mdx-remote JSON docs + manifest under
172166
`other/content/mdx-remote/`.
173167

other/content/__tests__/publish-mdx-remote-artifacts.test.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,19 @@ import {
88
toEntryKey,
99
} from '../publish-mdx-remote-artifacts.ts'
1010

11-
test('parseArgs requires bucket and supports optional flags', () => {
11+
test('parseArgs requires kv binding and supports optional flags', () => {
1212
expect(() =>
1313
parseArgs(['--before', 'abc', '--after', 'def']),
14-
).toThrow(/missing required --bucket/i)
14+
).toThrow(/missing required --kv-binding/i)
1515

1616
expect(
1717
parseArgs([
18-
'--bucket',
19-
'mdx-artifacts',
18+
'--kv-binding',
19+
'MDX_REMOTE_KV',
20+
'--wrangler-config',
21+
'wrangler-preview.generated.jsonc',
22+
'--wrangler-env',
23+
'preview',
2024
'--before',
2125
'abc',
2226
'--after',
@@ -26,7 +30,9 @@ test('parseArgs requires bucket and supports optional flags', () => {
2630
'--dry-run',
2731
]),
2832
).toEqual({
29-
bucket: 'mdx-artifacts',
33+
kvBinding: 'MDX_REMOTE_KV',
34+
wranglerConfigPath: 'wrangler-preview.generated.jsonc',
35+
wranglerEnv: 'preview',
3036
beforeSha: 'abc',
3137
afterSha: 'def',
3238
outputDirectory: 'tmp/mdx',

0 commit comments

Comments
 (0)