Skip to content

feat(cms): integrate TinaCMS for blog and ecosystem editing#69

Open
adamsoffer wants to merge 1 commit into
mainfrom
squash/tinacms-rollup
Open

feat(cms): integrate TinaCMS for blog and ecosystem editing#69
adamsoffer wants to merge 1 commit into
mainfrom
squash/tinacms-rollup

Conversation

@adamsoffer
Copy link
Copy Markdown
Collaborator

@adamsoffer adamsoffer commented May 21, 2026

  • Adds a Git-backed admin UI at /admin for editing content/blog/.md and content/ecosystem/.md. Schema covers the full frontmatter for blog posts (title, description, date, author, category, tags, image, imageAlt, draft, body) and ecosystem apps (name, url, description, categories, logo, order, madeBy, links, body).
  • Image uploads route per-field: blog hero images → public/images/blog/, ecosystem logos → public/ecosystem/. Media manager browses both.
  • Tina Cloud search enabled via NEXT_PUBLIC_TINA_SEARCH_TOKEN.
  • tina/tina-lock.json committed (required for Tina Cloud to index branches).
  • /admin rewrite in next.config.ts so /admin and /admin/index.html both serve the SPA.
  • requireEnv helper gates Node-only throws to avoid crashing the bundled admin SPA in the browser, where non-NEXT_PUBLIC_ env vars are absent.
  • pnpm.onlyBuiltDependencies allows native modules (better-sqlite3 for Tina's search index, esbuild, sharp).
  • lib/ecosystem.ts normalizes logo paths so both legacy filename-only and Tina-written full-path values render correctly.

Summary by CodeRabbit

  • New Features

    • Added a CMS admin editor for managing site content and ecosystem app information, accessible at /admin.
  • Configuration

    • Added required environment variables for CMS configuration.
    • Updated development and build scripts to support the new content management system.
    • Standardized ecosystem app logo asset paths for consistency.

Review Change Stack

- Adds a Git-backed admin UI at /admin for editing content/blog/*.md
  and content/ecosystem/*.md. Schema covers the full frontmatter for
  blog posts (title, description, date, author, category, tags, image,
  imageAlt, draft, body) and ecosystem apps (name, url, description,
  categories, logo, order, madeBy, links, body).
- Image uploads route per-field: blog hero images → public/images/blog/,
  ecosystem logos → public/ecosystem/. Media manager browses both.
- Tina Cloud search enabled via NEXT_PUBLIC_TINA_SEARCH_TOKEN.
- tina/tina-lock.json committed (required for Tina Cloud to index branches).
- /admin rewrite in next.config.ts so /admin and /admin/index.html both serve the SPA.
- requireEnv helper gates Node-only throws to avoid crashing the bundled
  admin SPA in the browser, where non-NEXT_PUBLIC_ env vars are absent.
- pnpm.onlyBuiltDependencies allows native modules (better-sqlite3 for
  Tina's search index, esbuild, sharp).
- lib/ecosystem.ts normalizes logo paths so both legacy filename-only and
  Tina-written full-path values render correctly.

Rolled up from #65, #66, #67, #68.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
website Ready Ready Preview, Comment May 21, 2026 1:42am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

📝 Walkthrough

Walkthrough

This PR integrates TinaCMS as a headless CMS for managing blog posts and ecosystem app content, adds TinaCMS environment variables and build scripts, updates build infrastructure to handle generated artifacts, and normalizes ecosystem app logo paths to use absolute URLs.

Changes

TinaCMS Integration

Layer / File(s) Summary
Environment variables and build dependencies
.env.example, package.json
TinaCMS client ID, token, branch, and search token environment variables are documented. Build and dev scripts are updated to route through TinaCMS CLI before Next.js, and @tinacms/cli and tinacms dependencies are added with pnpm onlyBuiltDependencies configuration.
TinaCMS configuration and schema
tina/config.ts, tina/tina-lock.json
tina/config.ts defines environment variable loading (with browser-safe error handling), filename slug generation, branch resolution, media handling, and CMS schema for post (blog) and app (ecosystem) collections with field definitions and UI hints. Generated tina-lock.json contains the compiled schema version, collection field shapes, and GraphQL definitions for the admin interface.
Admin path routing
next.config.ts
Rewrites /admin requests to /admin/index.html to serve the TinaCMS single-page application built into /public/admin/.

Build and Development Infrastructure

Layer / File(s) Summary
Tool configuration updates
.npmrc, .gitignore, .prettierignore, eslint.config.mjs
npm hoisted node-linker is enabled; .gitignore excludes all TinaCMS-generated files under /tina/__generated__/ except curated schema/query files, and ignores /public/admin/; .prettierignore and eslint.config.mjs are updated to skip formatting and linting of those same generated directories.

Ecosystem Logo Path Normalization

Layer / File(s) Summary
Logo normalization helper
lib/ecosystem.ts
New normalizeLogo() helper preserves absolute paths (starting with /) and converts relative filenames into full /ecosystem/{filename} URLs. getAppBySlug() uses this helper to normalize the logo field.
Component rendering updates
components/ecosystem/EcosystemDetail.tsx, components/ecosystem/EcosystemListingClient.tsx
Logo <img> elements now use app.logo directly instead of constructing /ecosystem/${app.logo}, working with pre-normalized paths from the data layer.
Content logo path updates
content/ecosystem/blueclaw.md, content/ecosystem/daydream.md, content/ecosystem/embody.md, content/ecosystem/flipsuite.md, content/ecosystem/frameworks.md, content/ecosystem/higher.md, content/ecosystem/livepeer-studio.md, content/ecosystem/nytv.md, content/ecosystem/spritz.md, content/ecosystem/streamplace.md, content/ecosystem/thelotradio.md, content/ecosystem/tribesocial.md, content/ecosystem/ufo.md
All ecosystem app markdown files update their frontmatter logo field from relative filenames (e.g., daydream.svg) to absolute ecosystem paths (e.g., /ecosystem/daydream.svg), ensuring consistency with the new path normalization logic.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • livepeer/website#66: Modifies package.json TinaCMS build/dev scripts in tandem with this PR's script changes.
  • livepeer/website#65: Directly overlaps with this PR's TinaCMS setup work including environment variables, ignore rules, build scripts, and tina/config.ts introduction.

Poem

🐰 Tina comes to tend the garden,
Content blooms from /ecosystem/ paths so true,
Logos normalized, components now charmed,
Build scripts flow through CLI gates anew! 🌱

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and accurately describes the main change: integrating TinaCMS for editing blog and ecosystem content, which aligns with the extensive changes adding TinaCMS configuration, admin UI, schemas, and supporting infrastructure.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch squash/tinacms-rollup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tina/config.ts`:
- Line 58: The Tina search indexer token is currently read from the
browser-exposed NEXT_PUBLIC_NAMED env var; update the code that sets
search.tina.indexerToken (the indexerToken assignment in tina/config.ts) to read
from the server-only env var process.env.TINA_SEARCH_TOKEN instead of
process.env.NEXT_PUBLIC_TINA_SEARCH_TOKEN, and update the project .env.example
to remove NEXT_PUBLIC_TINA_SEARCH_TOKEN and add TINA_SEARCH_TOKEN so the secret
is not exposed to the client; ensure tina/config.ts remains used only on the
server or in server-side builds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6e0e175e-47f7-4745-893f-ebc43e1619c0

📥 Commits

Reviewing files that changed from the base of the PR and between 1d08cd1 and e51ae83.

⛔ Files ignored due to path filters (7)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • tina/__generated__/_graphql.json is excluded by !**/__generated__/**
  • tina/__generated__/_lookup.json is excluded by !**/__generated__/**
  • tina/__generated__/_schema.json is excluded by !**/__generated__/**
  • tina/__generated__/frags.gql is excluded by !**/__generated__/**
  • tina/__generated__/queries.gql is excluded by !**/__generated__/**
  • tina/__generated__/schema.gql is excluded by !**/__generated__/**
📒 Files selected for processing (25)
  • .env.example
  • .gitignore
  • .npmrc
  • .prettierignore
  • components/ecosystem/EcosystemDetail.tsx
  • components/ecosystem/EcosystemListingClient.tsx
  • content/ecosystem/blueclaw.md
  • content/ecosystem/daydream.md
  • content/ecosystem/embody.md
  • content/ecosystem/flipsuite.md
  • content/ecosystem/frameworks.md
  • content/ecosystem/higher.md
  • content/ecosystem/livepeer-studio.md
  • content/ecosystem/nytv.md
  • content/ecosystem/spritz.md
  • content/ecosystem/streamplace.md
  • content/ecosystem/thelotradio.md
  • content/ecosystem/tribesocial.md
  • content/ecosystem/ufo.md
  • eslint.config.mjs
  • lib/ecosystem.ts
  • next.config.ts
  • package.json
  • tina/config.ts
  • tina/tina-lock.json

Comment thread tina/config.ts

search: {
tina: {
indexerToken: process.env.NEXT_PUBLIC_TINA_SEARCH_TOKEN || "",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In TinaCMS config (v3.x), is search.tina.indexerTokenexpected to useprocess.env.TINA_SEARCH_TOKEN(server/build-only) rather than aNEXT_PUBLIC_* variable?

💡 Result:

Yes—TinaCMS v3.x expects search.tina.indexerToken to be provided from process.env.TINA_SEARCH_TOKEN (i.e., a server/build-only env var), not from a NEXT_PUBLIC_* variable. Tina’s official config examples wire the search token like this: search: { tina: { indexerToken: process.env.TINA_SEARCH_TOKEN } } [1][2]. The TinaCloud docs also explicitly classify TINA_SEARCH_TOKEN as “Site build only” and “Token for TinaCloud search indexing” [1]. Additionally, Tina’s TinaCloud dashboard documentation warns that “API tokens should be considered sensitive and not be committed” and instructs using environment variables for these tokens in your Tina configuration [3]. So if your tina/config.(ts|js|tsx|jsx) is using TinaCMS v3.x, you should keep TINA_SEARCH_TOKEN non-public and avoid NEXT_PUBLIC_TINA_SEARCH_TOKEN for search.tina.indexerToken.

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate tina/config.ts and show around the referenced line
if [ -f tina/config.ts ]; then
  echo "== tina/config.ts (around line 45-80) =="
  nl -ba tina/config.ts | sed -n '45,80p'
else
  echo "tina/config.ts not found at repo root"
fi

# Search for env var names across repo (keep output small)
echo "== Search for NEXT_PUBLIC_TINA_SEARCH_TOKEN =="
rg -n "NEXT_PUBLIC_TINA_SEARCH_TOKEN" . || true

echo "== Search for TINA_SEARCH_TOKEN =="
rg -n "TINA_SEARCH_TOKEN" . || true

# Find an env example file and show any relevant lines
echo "== Candidate env example files =="
ls -1 .env* 2>/dev/null || true

for f in .env.example .env.example.* .env*example* 2>/dev/null; do
  if [ -f "$f" ]; then
    echo "== $f (matching token lines) =="
    nl -ba "$f" | rg -n "TINA_SEARCH_TOKEN|NEXT_PUBLIC_TINA_SEARCH_TOKEN" || true
  fi
done

Repository: livepeer/website

Length of output: 162


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate tina/config.ts =="
fd -t f "tina/config.ts" . || true
fd -t f "config.ts" . | rg -n "tina/config\.ts$" || true

# Determine actual file path(s)
FILES="$(fd -t f "tina/config.ts" . || true)"
if [ -z "${FILES}" ]; then
  echo "No exact tina/config.ts found via fd; attempting broader search for 'indexerToken'..."
  FILES="$(fd -t f "config.ts" . | xargs -r rg -l "indexerToken" || true)"
fi

echo "== Candidate files containing indexerToken =="
echo "$FILES"

# For each candidate file, print around first occurrence of NEXT_PUBLIC_TINA_SEARCH_TOKEN or indexerToken
for f in $FILES; do
  if [ -f "$f" ]; then
    echo "== File: $f =="
    echo "NEXT_PUBLIC_TINA_SEARCH_TOKEN occurrences:"
    rg -n "NEXT_PUBLIC_TINA_SEARCH_TOKEN" "$f" || true
    echo "TINA_SEARCH_TOKEN occurrences:"
    rg -n "TINA_SEARCH_TOKEN" "$f" || true

    echo "Snippet around 'indexerToken' in $f:"
    line="$(rg -n -m1 "indexerToken" "$f" | cut -d: -f1 || true)"
    if [ -n "${line}" ]; then
      start=$((line-8))
      end=$((line+8))
      if [ "$start" -lt 1 ]; then start=1; fi
      sed -n "${start},${end}p" "$f"
    fi
  fi
done

echo "== Repo-wide env var name search =="
echo "NEXT_PUBLIC_TINA_SEARCH_TOKEN:"
rg -n "NEXT_PUBLIC_TINA_SEARCH_TOKEN" . || true
echo "TINA_SEARCH_TOKEN:"
rg -n "TINA_SEARCH_TOKEN" . || true

echo "== Candidate env example files =="
ls -1 .env* 2>/dev/null || true
for f in .env.example .env.example.* .env*example* 2>/dev/null; do
  [ -f "$f" ] || continue
  echo "== $f (token lines) =="
  rg -n "NEXT_PUBLIC_TINA_SEARCH_TOKEN|TINA_SEARCH_TOKEN" "$f" || true
done

Repository: livepeer/website

Length of output: 1323


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== .env.example token occurrences =="
if [ -f .env.example ]; then
  rg -n "NEXT_PUBLIC_TINA_SEARCH_TOKEN|TINA_SEARCH_TOKEN" .env.example || true
  echo "== .env.example surrounding lines (if token present) =="
  # Print a small window around each match
  for v in NEXT_PUBLIC_TINA_SEARCH_TOKEN TINA_SEARCH_TOKEN; do
    line="$(rg -n -m1 "$v" .env.example | cut -d: -f1 || true)"
    if [ -n "$line" ]; then
      start=$((line-5)); end=$((line+5))
      [ "$start" -lt 1 ] && start=1
      sed -n "${start},${end}p" .env.example
    fi
  done
else
  echo ".env.example not found"
fi

echo "== tina package version (if available) =="
if [ -f package.json ]; then
  cat package.json | rg -n "\"tina\"" || true
  # More robust: search for "tinacms" too
  cat package.json | rg -n "tinacms|tina" || true
fi

echo "== Any other NEXT_PUBLIC_TINA_SEARCH_TOKEN usage in repo =="
rg -n "NEXT_PUBLIC_TINA_SEARCH_TOKEN" . || true

Repository: livepeer/website

Length of output: 1095


Keep the Tina search indexer token server-only.

tina/config.ts reads search.tina.indexerToken from process.env.NEXT_PUBLIC_TINA_SEARCH_TOKEN, and .env.example defines NEXT_PUBLIC_TINA_SEARCH_TOKEN. In Next.js, NEXT_PUBLIC_* values are exposed to the browser, so this can leak a sensitive search indexing token—TinaCMS v3.x expects process.env.TINA_SEARCH_TOKEN instead.

🔧 Proposed fix
-      indexerToken: process.env.NEXT_PUBLIC_TINA_SEARCH_TOKEN || "",
+      indexerToken: process.env.TINA_SEARCH_TOKEN || "",
-# Search indexer token (different token type from TINA_TOKEN). Create via
+# Search indexer token (different token type from TINA_TOKEN). Create via
 # Tokens → New Token → Search in the Tina dashboard.
-NEXT_PUBLIC_TINA_SEARCH_TOKEN=
+TINA_SEARCH_TOKEN=
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tina/config.ts` at line 58, The Tina search indexer token is currently read
from the browser-exposed NEXT_PUBLIC_NAMED env var; update the code that sets
search.tina.indexerToken (the indexerToken assignment in tina/config.ts) to read
from the server-only env var process.env.TINA_SEARCH_TOKEN instead of
process.env.NEXT_PUBLIC_TINA_SEARCH_TOKEN, and update the project .env.example
to remove NEXT_PUBLIC_TINA_SEARCH_TOKEN and add TINA_SEARCH_TOKEN so the secret
is not exposed to the client; ensure tina/config.ts remains used only on the
server or in server-side builds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant