Source for solberg.is, my personal blog and projects page.
The site includes the public blog, an authenticated Markdown editor and admin area, a CLI for managing posts, and the Kitty terminal-theme editor.
- TanStack Start with React Server Components and React 19
- Vite and the Cloudflare Vite plugin
- Cloudflare Workers and Cloudflare D1
- Kysely for typed queries, with
db-resultclassifying failures into Result tags - Markdown and MDX rendered with
safe-mdx, with server-side Shiki highlighting - Monaco for post editing
- Tailwind CSS v4
- Hono for the authenticated blog API
- Bun, TypeScript, oxlint, and oxfmt
Install dependencies and start Vite:
bun install
bun run devLocal configuration lives in .env, which is ignored by Git. The application expects:
NODE_ENV=development
SITE_URL=http://localhost:5173
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
ONEDOLLARSTATS_API_KEY=...The development app bypasses GitHub OAuth through /api/dev-auth, but still needs a sufficiently
long GITHUB_CLIENT_SECRET for the encrypted admin session. Use the URL printed by Vite if it
chooses a port other than 5173.
To initialize a fresh local D1 database, apply the checked-in migrations in timestamp order:
for migration in migrations/*/migration.sql; do
bunx wrangler d1 execute solberg-blog --local --file "$migration"
doneAfter changing schema.ts, generate and inspect a new migration:
bun run generate:migrationApply one generated migration locally:
bunx wrangler d1 execute solberg-blog \
--local \
--file migrations/<timestamp_name>/migration.sqlApply it to production explicitly:
bunx wrangler d1 execute solberg-blog \
--remote \
--file migrations/<timestamp_name>/migration.sql \
--yesProduction schema changes must land before application code that reads the new schema. D1 schema work is not applied automatically by a Worker deployment.
The Bun CLI in cli/blog.ts manages posts and notes through the authenticated
Hono API.
It targets the Vite development server at http://localhost:5173 by default. Set BLOG_API_URL
if Vite chooses another port, or use the production shortcut:
bun run blog:prod login
bun run blog:prod list
bun run blog:prod get how-i-use-claude-codeLogin uses GitHub's device flow and stores the resulting app token in ~/.blog-cli-session.
New posts are drafts unless --publish is supplied. The body can come from a file or stdin:
bun run blog:prod create \
--slug my-post \
--title "My Post" \
--body-file - \
--publish < post.mdAdd --dry-run to inspect the post without creating it, or --diff to print the content before
creation.
get --json returns the post and its ETag. Pass that ETag back with --if-match to avoid
overwriting an edit made after the post was fetched:
bun run blog:prod get my-post --json > /tmp/my-post.json
jq -r '.post.markdown' /tmp/my-post.json > /tmp/my-post.md
# Edit /tmp/my-post.md, then update only if the original revision is still current.
bun run blog:prod update my-post \
--body-file /tmp/my-post.md \
--if-match "$(jq -r '.etag' /tmp/my-post.json)" \
--diffOther useful forms:
# Print only Markdown, suitable for a pipe or redirect.
bun run blog:prod get my-post --body-only
# Preview metadata and Markdown changes without writing.
bun run blog:prod update my-post --body-file post.md --dry-run
# Publish or return a post to draft state.
bun run blog:prod update my-post --publish
bun run blog:prod update my-post --unpublish
# Back up every post to iCloud Documents on macOS.
bun run blog:prod backupRun bun run blog --help for the complete command and option list.
bun run test
bun run lint
bun run format:check
bun run buildbun run build runs the production Vite build and tsc --noEmit. Lefthook runs formatting and
type-aware linting before each commit.
Pushing main triggers Cloudflare Workers Builds. A successful
Workers Builds: solberg-blog check is the production deployment signal.
git push origin main
gh api "repos/jokull/blog/commits/$(git rev-parse HEAD)/check-runs" \
--jq '.check_runs[] | {name, status, conclusion, details_url}'A failed build leaves the previous production deployment active. Manual deployment is also available:
bun run deploysrc/routes— TanStack Start file routes and API endpointsapp— shared page, layout, editor, and admin components rendered by the current routescli— authenticated blog CLI and GitHub device-flow loginlib/api.ts— Hono post, note, category, and CLI-auth APIschema.tsandmigrations— D1 schema and forward migrationssrc/kitty— Kitty theme editor, parser, gallery, and RPC implementationcomponents— shared UI and data visualizations