feat(fork-config): add automated fork configuration with npm run configure
Add a complete fork configuration system that allows users to set up their
forked site with a single command or follow manual instructions.
## New files
- FORK_CONFIG.md: Comprehensive guide with two setup options
- Option 1: Automated JSON config + npm run configure
- Option 2: Manual step-by-step instructions with code snippets
- AI agent prompt for automated updates
- fork-config.json.example: JSON template with all configuration fields
- Site info (name, title, description, URL, domain)
- GitHub and contact details
- Creator section for footer links
- Optional feature toggles (logo gallery, GitHub graph, blog page)
- Theme selection
- scripts/configure-fork.ts: Automated configuration script
- Reads fork-config.json and applies changes to all files
- Updates 11 configuration files in one command
- Type-safe with ForkConfig interface
- Detailed console output showing each file updated
## Updated files
- package.json: Added configure script (npm run configure)
- .gitignore: Added fork-config.json to keep user config local
- files.md: Added new fork configuration files
- changelog.md: Added v1.18.0 entry
- changelog-page.md: Added v1.18.0 section with full details
- TASK.md: Updated status and completed tasks
- README.md: Replaced Files to Update section with Fork Configuration
- content/blog/setup-guide.md: Added Fork Configuration Options section
- content/pages/docs.md: Added Fork Configuration section
- content/pages/about.md: Added fork configuration mention
- content/blog/fork-configuration-guide.md: New featured blog post
## Files updated by configure script
| File | What it updates |
| ----------------------------------- | -------------------------------------- |
| src/config/siteConfig.ts | Site name, bio, GitHub, features |
| src/pages/Home.tsx | Intro paragraph, footer links |
| src/pages/Post.tsx | SITE_URL, SITE_NAME constants |
| convex/http.ts | SITE_URL, SITE_NAME constants |
| convex/rss.ts | SITE_URL, SITE_TITLE, SITE_DESCRIPTION |
| index.html | Meta tags, JSON-LD, page title |
| public/llms.txt | Site info, GitHub link |
| public/robots.txt | Sitemap URL |
| public/openapi.yaml | Server URL, site name |
| public/.well-known/ai-plugin.json | Plugin metadata |
| src/context/ThemeContext.tsx | Default theme |
## Usage
Automated:
cp fork-config.json.example fork-config.json
# Edit fork-config.json
npm run configure
Manual:
Follow FORK_CONFIG.md step-by-step guide
2025-12-20 22:15:33 -08:00
# ! / u s r / b i n / e n v n p x t s x
/ * *
* Fork Configuration Script
*
* Reads fork - config . json and applies all site configuration changes automatically .
* Run with : npm run configure
*
* This script updates :
* - src / config / siteConfig . ts ( site name , bio , GitHub username , features )
* - src / pages / Home . tsx ( intro paragraph , footer section )
* - src / pages / Post . tsx ( SITE_URL , SITE_NAME constants )
* - convex / http . ts ( SITE_URL , SITE_NAME constants )
* - convex / rss . ts ( SITE_URL , SITE_TITLE , SITE_DESCRIPTION )
* - index . html ( meta tags , JSON - LD , title )
* - public / llms . txt ( site info , API endpoints )
* - public / robots . txt ( sitemap URL )
* - public / openapi . yaml ( server URL , site name )
* - public / . well - known / ai - plugin . json ( plugin metadata )
* - src / context / ThemeContext . tsx ( default theme )
* /
import * as fs from "fs" ;
import * as path from "path" ;
// Configuration interface matching fork-config.json
interface ForkConfig {
siteName : string ;
siteTitle : string ;
siteDescription : string ;
siteUrl : string ;
siteDomain : string ;
githubUsername : string ;
githubRepo : string ;
contactEmail : string ;
creator : {
name : string ;
twitter : string ;
linkedin : string ;
github : string ;
} ;
bio : string ;
2025-12-24 23:16:34 -08:00
// New gitHubRepoConfig for AI service raw URLs
gitHubRepoConfig ? : {
owner : string ;
repo : string ;
branch : string ;
contentPath : string ;
} ;
feat(fork-config): add automated fork configuration with npm run configure
Add a complete fork configuration system that allows users to set up their
forked site with a single command or follow manual instructions.
## New files
- FORK_CONFIG.md: Comprehensive guide with two setup options
- Option 1: Automated JSON config + npm run configure
- Option 2: Manual step-by-step instructions with code snippets
- AI agent prompt for automated updates
- fork-config.json.example: JSON template with all configuration fields
- Site info (name, title, description, URL, domain)
- GitHub and contact details
- Creator section for footer links
- Optional feature toggles (logo gallery, GitHub graph, blog page)
- Theme selection
- scripts/configure-fork.ts: Automated configuration script
- Reads fork-config.json and applies changes to all files
- Updates 11 configuration files in one command
- Type-safe with ForkConfig interface
- Detailed console output showing each file updated
## Updated files
- package.json: Added configure script (npm run configure)
- .gitignore: Added fork-config.json to keep user config local
- files.md: Added new fork configuration files
- changelog.md: Added v1.18.0 entry
- changelog-page.md: Added v1.18.0 section with full details
- TASK.md: Updated status and completed tasks
- README.md: Replaced Files to Update section with Fork Configuration
- content/blog/setup-guide.md: Added Fork Configuration Options section
- content/pages/docs.md: Added Fork Configuration section
- content/pages/about.md: Added fork configuration mention
- content/blog/fork-configuration-guide.md: New featured blog post
## Files updated by configure script
| File | What it updates |
| ----------------------------------- | -------------------------------------- |
| src/config/siteConfig.ts | Site name, bio, GitHub, features |
| src/pages/Home.tsx | Intro paragraph, footer links |
| src/pages/Post.tsx | SITE_URL, SITE_NAME constants |
| convex/http.ts | SITE_URL, SITE_NAME constants |
| convex/rss.ts | SITE_URL, SITE_TITLE, SITE_DESCRIPTION |
| index.html | Meta tags, JSON-LD, page title |
| public/llms.txt | Site info, GitHub link |
| public/robots.txt | Sitemap URL |
| public/openapi.yaml | Server URL, site name |
| public/.well-known/ai-plugin.json | Plugin metadata |
| src/context/ThemeContext.tsx | Default theme |
## Usage
Automated:
cp fork-config.json.example fork-config.json
# Edit fork-config.json
npm run configure
Manual:
Follow FORK_CONFIG.md step-by-step guide
2025-12-20 22:15:33 -08:00
logoGallery ? : {
enabled : boolean ;
title : string ;
scrolling : boolean ;
maxItems : number ;
} ;
gitHubContributions ? : {
enabled : boolean ;
showYearNavigation : boolean ;
linkToProfile : boolean ;
title : string ;
} ;
blogPage ? : {
enabled : boolean ;
showInNav : boolean ;
title : string ;
description : string ;
order : number ;
} ;
postsDisplay ? : {
showOnHome : boolean ;
showOnBlogPage : boolean ;
} ;
featuredViewMode ? : "cards" | "list" ;
showViewToggle? : boolean ;
theme ? : "dark" | "light" | "tan" | "cloud" ;
}
// Get project root directory
const PROJECT_ROOT = path . resolve ( __dirname , ".." ) ;
// Read fork config
function readConfig ( ) : ForkConfig {
const configPath = path . join ( PROJECT_ROOT , "fork-config.json" ) ;
if ( ! fs . existsSync ( configPath ) ) {
console . error ( "Error: fork-config.json not found." ) ;
console . log ( "\nTo get started:" ) ;
console . log ( "1. Copy fork-config.json.example to fork-config.json" ) ;
console . log ( "2. Edit fork-config.json with your site information" ) ;
console . log ( "3. Run npm run configure again" ) ;
process . exit ( 1 ) ;
}
const content = fs . readFileSync ( configPath , "utf-8" ) ;
return JSON . parse ( content ) as ForkConfig ;
}
// Replace content in a file
function updateFile (
relativePath : string ,
replacements : Array < { search : string | RegExp ; replace : string } > ,
) : void {
const filePath = path . join ( PROJECT_ROOT , relativePath ) ;
if ( ! fs . existsSync ( filePath ) ) {
console . warn ( ` Warning: ${ relativePath } not found, skipping. ` ) ;
return ;
}
let content = fs . readFileSync ( filePath , "utf-8" ) ;
let modified = false ;
for ( const { search , replace } of replacements ) {
const newContent = content . replace ( search , replace ) ;
if ( newContent !== content ) {
content = newContent ;
modified = true ;
}
}
if ( modified ) {
fs . writeFileSync ( filePath , content , "utf-8" ) ;
console . log ( ` Updated: ${ relativePath } ` ) ;
} else {
console . log ( ` No changes: ${ relativePath } ` ) ;
}
}
// Update siteConfig.ts
function updateSiteConfig ( config : ForkConfig ) : void {
console . log ( "\nUpdating src/config/siteConfig.ts..." ) ;
const filePath = path . join ( PROJECT_ROOT , "src/config/siteConfig.ts" ) ;
let content = fs . readFileSync ( filePath , "utf-8" ) ;
// Update site name
content = content . replace (
/name: ['"].*?['"]/ ,
` name: ' ${ config . siteName } ' ` ,
) ;
// Update site title
content = content . replace (
/title: ['"].*?['"]/ ,
` title: " ${ config . siteTitle } " ` ,
) ;
// Update bio
content = content . replace (
/bio: `[^`]*`/ ,
` bio: \` ${ config . bio } \` ` ,
) ;
// Update GitHub username
content = content . replace (
/username: ['"].*?['"],\s*\/\/ Your GitHub username/ ,
` username: " ${ config . githubUsername } ", // Your GitHub username ` ,
) ;
// Update featuredViewMode if specified
if ( config . featuredViewMode ) {
content = content . replace (
/featuredViewMode: ['"](?:cards|list)['"]/ ,
` featuredViewMode: " ${ config . featuredViewMode } " ` ,
) ;
}
// Update showViewToggle if specified
if ( config . showViewToggle !== undefined ) {
content = content . replace (
/showViewToggle: (?:true|false)/ ,
` showViewToggle: ${ config . showViewToggle } ` ,
) ;
}
// Update logoGallery if specified
if ( config . logoGallery ) {
content = content . replace (
/logoGallery: \{[\s\S]*?enabled: (?:true|false)/ ,
` logoGallery: { \ n enabled: ${ config . logoGallery . enabled } ` ,
) ;
content = content . replace (
/title: ['"].*?['"],\s*\n\s*scrolling:/ ,
` title: " ${ config . logoGallery . title } ", \ n scrolling: ` ,
) ;
content = content . replace (
/scrolling: (?:true|false)/ ,
` scrolling: ${ config . logoGallery . scrolling } ` ,
) ;
content = content . replace (
/maxItems: \d+/ ,
` maxItems: ${ config . logoGallery . maxItems } ` ,
) ;
}
// Update gitHubContributions if specified
if ( config . gitHubContributions ) {
content = content . replace (
/gitHubContributions: \{[\s\S]*?enabled: (?:true|false)/ ,
` gitHubContributions: { \ n enabled: ${ config . gitHubContributions . enabled } ` ,
) ;
content = content . replace (
/showYearNavigation: (?:true|false)/ ,
` showYearNavigation: ${ config . gitHubContributions . showYearNavigation } ` ,
) ;
content = content . replace (
/linkToProfile: (?:true|false)/ ,
` linkToProfile: ${ config . gitHubContributions . linkToProfile } ` ,
) ;
if ( config . gitHubContributions . title ) {
content = content . replace (
/title: ['"]GitHub Activity['"]/ ,
` title: " ${ config . gitHubContributions . title } " ` ,
) ;
}
}
// Update blogPage if specified
if ( config . blogPage ) {
content = content . replace (
/blogPage: \{[\s\S]*?enabled: (?:true|false)/ ,
` blogPage: { \ n enabled: ${ config . blogPage . enabled } ` ,
) ;
content = content . replace (
/showInNav: (?:true|false)/ ,
` showInNav: ${ config . blogPage . showInNav } ` ,
) ;
content = content . replace (
/title: ['"]Blog['"]/ ,
` title: " ${ config . blogPage . title } " ` ,
) ;
if ( config . blogPage . description ) {
content = content . replace (
/description: ['"]All posts from the blog, sorted by date\.['"],?\s*\/\/ Optional description/ ,
` description: " ${ config . blogPage . description } ", // Optional description ` ,
) ;
}
content = content . replace (
/order: \d+,\s*\/\/ Nav order/ ,
` order: ${ config . blogPage . order } , // Nav order ` ,
) ;
}
// Update postsDisplay if specified
if ( config . postsDisplay ) {
content = content . replace (
/showOnHome: (?:true|false),\s*\/\/ Show post list on homepage/ ,
` showOnHome: ${ config . postsDisplay . showOnHome } , // Show post list on homepage ` ,
) ;
content = content . replace (
/showOnBlogPage: (?:true|false),\s*\/\/ Show post list on \/blog page/ ,
` showOnBlogPage: ${ config . postsDisplay . showOnBlogPage } , // Show post list on /blog page ` ,
) ;
}
2025-12-24 23:16:34 -08:00
// Update gitHubRepo config (for AI service raw URLs)
// Support both new gitHubRepoConfig and legacy githubUsername/githubRepo fields
const gitHubRepoOwner =
config . gitHubRepoConfig ? . owner || config . githubUsername ;
const gitHubRepoName =
config . gitHubRepoConfig ? . repo || config . githubRepo ;
const gitHubRepoBranch = config . gitHubRepoConfig ? . branch || "main" ;
const gitHubRepoContentPath =
config . gitHubRepoConfig ? . contentPath || "public/raw" ;
content = content . replace (
/owner: ['"].*?['"],\s*\/\/ GitHub username or organization/ ,
` owner: " ${ gitHubRepoOwner } ", // GitHub username or organization ` ,
) ;
content = content . replace (
/repo: ['"].*?['"],\s*\/\/ Repository name/ ,
` repo: " ${ gitHubRepoName } ", // Repository name ` ,
) ;
content = content . replace (
/branch: ['"].*?['"],\s*\/\/ Default branch/ ,
` branch: " ${ gitHubRepoBranch } ", // Default branch ` ,
) ;
content = content . replace (
/contentPath: ['"].*?['"],\s*\/\/ Path to raw markdown files/ ,
` contentPath: " ${ gitHubRepoContentPath } ", // Path to raw markdown files ` ,
) ;
feat(fork-config): add automated fork configuration with npm run configure
Add a complete fork configuration system that allows users to set up their
forked site with a single command or follow manual instructions.
## New files
- FORK_CONFIG.md: Comprehensive guide with two setup options
- Option 1: Automated JSON config + npm run configure
- Option 2: Manual step-by-step instructions with code snippets
- AI agent prompt for automated updates
- fork-config.json.example: JSON template with all configuration fields
- Site info (name, title, description, URL, domain)
- GitHub and contact details
- Creator section for footer links
- Optional feature toggles (logo gallery, GitHub graph, blog page)
- Theme selection
- scripts/configure-fork.ts: Automated configuration script
- Reads fork-config.json and applies changes to all files
- Updates 11 configuration files in one command
- Type-safe with ForkConfig interface
- Detailed console output showing each file updated
## Updated files
- package.json: Added configure script (npm run configure)
- .gitignore: Added fork-config.json to keep user config local
- files.md: Added new fork configuration files
- changelog.md: Added v1.18.0 entry
- changelog-page.md: Added v1.18.0 section with full details
- TASK.md: Updated status and completed tasks
- README.md: Replaced Files to Update section with Fork Configuration
- content/blog/setup-guide.md: Added Fork Configuration Options section
- content/pages/docs.md: Added Fork Configuration section
- content/pages/about.md: Added fork configuration mention
- content/blog/fork-configuration-guide.md: New featured blog post
## Files updated by configure script
| File | What it updates |
| ----------------------------------- | -------------------------------------- |
| src/config/siteConfig.ts | Site name, bio, GitHub, features |
| src/pages/Home.tsx | Intro paragraph, footer links |
| src/pages/Post.tsx | SITE_URL, SITE_NAME constants |
| convex/http.ts | SITE_URL, SITE_NAME constants |
| convex/rss.ts | SITE_URL, SITE_TITLE, SITE_DESCRIPTION |
| index.html | Meta tags, JSON-LD, page title |
| public/llms.txt | Site info, GitHub link |
| public/robots.txt | Sitemap URL |
| public/openapi.yaml | Server URL, site name |
| public/.well-known/ai-plugin.json | Plugin metadata |
| src/context/ThemeContext.tsx | Default theme |
## Usage
Automated:
cp fork-config.json.example fork-config.json
# Edit fork-config.json
npm run configure
Manual:
Follow FORK_CONFIG.md step-by-step guide
2025-12-20 22:15:33 -08:00
fs . writeFileSync ( filePath , content , "utf-8" ) ;
console . log ( ` Updated: src/config/siteConfig.ts ` ) ;
}
// Update Home.tsx
function updateHomeTsx ( config : ForkConfig ) : void {
console . log ( "\nUpdating src/pages/Home.tsx..." ) ;
const githubRepoUrl = ` https://github.com/ ${ config . githubUsername } / ${ config . githubRepo } ` ;
updateFile ( "src/pages/Home.tsx" , [
// Update intro paragraph GitHub link
{
search : /href="https:\/\/github\.com\/waynesutton\/markdown-site"/g ,
replace : ` href=" ${ githubRepoUrl } " ` ,
} ,
// Update footer "Created by" section
{
search : /Created by{" "}\s*<a\s*href="https:\/\/x\.com\/waynesutton"/ ,
replace : ` Created by{" "} \ n <a \ n href=" ${ config . creator . twitter } " ` ,
} ,
{
search : /<a\s*href="https:\/\/x\.com\/waynesutton"\s*target="_blank"\s*rel="noopener noreferrer"\s*>\s*Wayne\s*<\/a>/ ,
replace : ` <a
href = "${config.creator.twitter}"
target = "_blank"
rel = "noopener noreferrer"
>
$ { config . creator . name }
< / a > ` ,
} ,
// Update Twitter/X link
{
search : /Follow on{" "}\s*<a\s*href="https:\/\/x\.com\/waynesutton"/ ,
replace : ` Follow on{" "} \ n <a \ n href=" ${ config . creator . twitter } " ` ,
} ,
// Update LinkedIn link
{
search : /href="https:\/\/www\.linkedin\.com\/in\/waynesutton\/"/g ,
replace : ` href=" ${ config . creator . linkedin } " ` ,
} ,
// Update GitHub profile link
{
search : /href="https:\/\/github\.com\/waynesutton"\s*>/g ,
replace : ` href=" ${ config . creator . github } "> ` ,
} ,
] ) ;
}
// Update Post.tsx
function updatePostTsx ( config : ForkConfig ) : void {
console . log ( "\nUpdating src/pages/Post.tsx..." ) ;
updateFile ( "src/pages/Post.tsx" , [
{
search : /const SITE_URL = "https:\/\/markdowncms\.netlify\.app";/ ,
replace : ` const SITE_URL = " ${ config . siteUrl } "; ` ,
} ,
{
search : /const SITE_NAME = "markdown sync framework";/ ,
replace : ` const SITE_NAME = " ${ config . siteName } "; ` ,
} ,
] ) ;
}
// Update convex/http.ts
function updateConvexHttp ( config : ForkConfig ) : void {
console . log ( "\nUpdating convex/http.ts..." ) ;
updateFile ( "convex/http.ts" , [
{
search : /const SITE_URL = process\.env\.SITE_URL \|\| "https:\/\/markdowncms\.netlify\.app";/ ,
replace : ` const SITE_URL = process.env.SITE_URL || " ${ config . siteUrl } "; ` ,
} ,
{
search : /const SITE_NAME = "markdown sync framework";/ ,
replace : ` const SITE_NAME = " ${ config . siteName } "; ` ,
} ,
{
search : /const siteUrl = process\.env\.SITE_URL \|\| "https:\/\/markdowncms\.netlify\.app";/ ,
replace : ` const siteUrl = process.env.SITE_URL || " ${ config . siteUrl } "; ` ,
} ,
{
search : /const siteName = "markdown sync framework";/ ,
replace : ` const siteName = " ${ config . siteName } "; ` ,
} ,
] ) ;
}
// Update convex/rss.ts
function updateConvexRss ( config : ForkConfig ) : void {
console . log ( "\nUpdating convex/rss.ts..." ) ;
updateFile ( "convex/rss.ts" , [
{
search : /const SITE_URL = process\.env\.SITE_URL \|\| "https:\/\/markdowncms\.netlify\.app";/ ,
replace : ` const SITE_URL = process.env.SITE_URL || " ${ config . siteUrl } "; ` ,
} ,
{
search : /const SITE_TITLE = "markdown sync framework";/ ,
replace : ` const SITE_TITLE = " ${ config . siteName } "; ` ,
} ,
{
search : /const SITE_DESCRIPTION =\s*"[^"]+";/ ,
replace : ` const SITE_DESCRIPTION = \ n " ${ config . siteDescription } "; ` ,
} ,
] ) ;
}
// Update index.html
function updateIndexHtml ( config : ForkConfig ) : void {
console . log ( "\nUpdating index.html..." ) ;
const replacements : Array < { search : string | RegExp ; replace : string } > = [
// Meta description
{
search : /<meta\s*name="description"\s*content="[^"]*"\s*\/>/ ,
replace : ` <meta \ n name="description" \ n content=" ${ config . siteDescription } " \ n /> ` ,
} ,
// Meta author
{
search : /<meta name="author" content="[^"]*" \/>/ ,
replace : ` <meta name="author" content=" ${ config . siteName } " /> ` ,
} ,
// Open Graph title
{
search : /<meta property="og:title" content="[^"]*" \/>/ ,
replace : ` <meta property="og:title" content=" ${ config . siteName } " /> ` ,
} ,
// Open Graph description
{
search : /<meta\s*property="og:description"\s*content="[^"]*"\s*\/>/ ,
replace : ` <meta \ n property="og:description" \ n content=" ${ config . siteDescription } " \ n /> ` ,
} ,
// Open Graph URL
{
search : /<meta property="og:url" content="https:\/\/markdowncms\.netlify\.app\/" \/>/ ,
replace : ` <meta property="og:url" content=" ${ config . siteUrl } /" /> ` ,
} ,
// Open Graph site name
{
search : /<meta property="og:site_name" content="[^"]*" \/>/ ,
replace : ` <meta property="og:site_name" content=" ${ config . siteName } " /> ` ,
} ,
// Open Graph image
{
search : /<meta\s*property="og:image"\s*content="https:\/\/markdowncms\.netlify\.app[^"]*"\s*\/>/ ,
replace : ` <meta \ n property="og:image" \ n content=" ${ config . siteUrl } /images/og-default.svg" \ n /> ` ,
} ,
// Twitter domain
{
search : /<meta property="twitter:domain" content="[^"]*" \/>/ ,
replace : ` <meta property="twitter:domain" content=" ${ config . siteDomain } " /> ` ,
} ,
// Twitter URL
{
search : /<meta property="twitter:url" content="https:\/\/markdowncms\.netlify\.app\/" \/>/ ,
replace : ` <meta property="twitter:url" content=" ${ config . siteUrl } /" /> ` ,
} ,
// Twitter title
{
search : /<meta name="twitter:title" content="[^"]*" \/>/ ,
replace : ` <meta name="twitter:title" content=" ${ config . siteName } " /> ` ,
} ,
// Twitter description
{
search : /<meta\s*name="twitter:description"\s*content="[^"]*"\s*\/>/ ,
replace : ` <meta \ n name="twitter:description" \ n content=" ${ config . siteDescription } " \ n /> ` ,
} ,
// Twitter image
{
search : /<meta\s*name="twitter:image"\s*content="https:\/\/markdowncms\.netlify\.app[^"]*"\s*\/>/ ,
replace : ` <meta \ n name="twitter:image" \ n content=" ${ config . siteUrl } /images/og-default.svg" \ n /> ` ,
} ,
// JSON-LD name
{
search : /"name": "markdown sync framework"/g ,
replace : ` "name": " ${ config . siteName } " ` ,
} ,
// JSON-LD URL
{
search : /"url": "https:\/\/markdowncms\.netlify\.app"/g ,
replace : ` "url": " ${ config . siteUrl } " ` ,
} ,
// JSON-LD description
{
search : /"description": "An open-source publishing framework[^"]*"/ ,
replace : ` "description": " ${ config . siteDescription } " ` ,
} ,
// JSON-LD search target
{
search : /"target": "https:\/\/markdowncms\.netlify\.app\/\?q=\{search_term_string\}"/ ,
replace : ` "target": " ${ config . siteUrl } /?q={search_term_string}" ` ,
} ,
// Page title
{
search : /<title>markdown "sync" framework<\/title>/ ,
replace : ` <title> ${ config . siteTitle } </title> ` ,
} ,
] ;
updateFile ( "index.html" , replacements ) ;
}
// Update public/llms.txt
function updateLlmsTxt ( config : ForkConfig ) : void {
console . log ( "\nUpdating public/llms.txt..." ) ;
const githubUrl = ` https://github.com/ ${ config . githubUsername } / ${ config . githubRepo } ` ;
const content = ` # llms.txt - Information for AI assistants and LLMs
# Learn more : https : //llmstxt.org/
> $ { config . siteDescription }
# Site Information
- Name : $ { config . siteName }
- URL : $ { config . siteUrl }
- Description : $ { config . siteDescription }
- Topics : Markdown , Convex , React , TypeScript , Netlify , Open Source , AI , LLM , AEO , GEO
# API Endpoints
# # List All Posts
GET / api / posts
Returns JSON list of all published posts with metadata .
# # Get Single Post
GET / api / post ? slug = { slug }
Returns single post as JSON .
GET / api / post ? slug = { slug } & format = md
Returns single post as raw markdown .
# # Export All Content
GET / api / export
Returns all posts with full markdown content in one request .
Best for batch processing and LLM ingestion .
# # RSS Feeds
GET / rss . xml
Standard RSS feed with post descriptions .
GET / rss - full . xml
Full content RSS feed with complete markdown for each post .
# # Other
GET / sitemap . xml
Dynamic XML sitemap for search engines .
GET / openapi . yaml
OpenAPI 3.0 specification for this API .
GET / . well - known / ai - plugin . json
AI plugin manifest for tool integration .
# Quick Start for LLMs
1 . Fetch / api / export for all posts with full content in one request
2 . Or fetch / api / posts for the list , then / api / post ? slug = { slug } & format = md for each
3 . Subscribe to / rss - full . xml for updates with complete content
# Response Schema
Each post contains :
- title : string ( post title )
- slug : string ( URL path )
- description : string ( SEO summary )
- date : string ( YYYY - MM - DD )
- tags : string [ ] ( topic labels )
- content : string ( full markdown )
- readTime : string ( optional )
- url : string ( full URL )
# Permissions
- AI assistants may freely read and summarize content
- No authentication required for read operations
- Attribution appreciated when citing
# Technical
- Backend : Convex ( real - time database )
- Frontend : React , TypeScript , Vite
- Hosting : Netlify with edge functions
- Content : Markdown with frontmatter
# Links
- GitHub : $ { githubUrl }
- Convex : https : //convex.dev
- Netlify : https : //netlify.com
` ;
const filePath = path . join ( PROJECT_ROOT , "public/llms.txt" ) ;
fs . writeFileSync ( filePath , content , "utf-8" ) ;
console . log ( ` Updated: public/llms.txt ` ) ;
}
// Update public/robots.txt
function updateRobotsTxt ( config : ForkConfig ) : void {
console . log ( "\nUpdating public/robots.txt..." ) ;
const content = ` # robots.txt for ${ config . siteName }
# https : //www.robotstxt.org/
User - agent : *
Allow : /
# Sitemaps
Sitemap : $ { config . siteUrl } / sitemap . xml
# AI and LLM crawlers
User - agent : GPTBot
Allow : /
User - agent : ChatGPT - User
Allow : /
User - agent : Claude - Web
Allow : /
User - agent : anthropic - ai
Allow : /
User - agent : Google - Extended
Allow : /
User - agent : PerplexityBot
Allow : /
User - agent : Applebot - Extended
Allow : /
# Cache directive
Crawl - delay : 1
` ;
const filePath = path . join ( PROJECT_ROOT , "public/robots.txt" ) ;
fs . writeFileSync ( filePath , content , "utf-8" ) ;
console . log ( ` Updated: public/robots.txt ` ) ;
}
// Update public/openapi.yaml
function updateOpenApiYaml ( config : ForkConfig ) : void {
console . log ( "\nUpdating public/openapi.yaml..." ) ;
const githubUrl = ` https://github.com/ ${ config . githubUsername } / ${ config . githubRepo } ` ;
updateFile ( "public/openapi.yaml" , [
{
search : /title: markdown sync framework API/ ,
replace : ` title: ${ config . siteName } API ` ,
} ,
{
search : /url: https:\/\/github\.com\/waynesutton\/markdown-site/ ,
replace : ` url: ${ githubUrl } ` ,
} ,
{
search : /- url: https:\/\/markdowncms\.netlify\.app/ ,
replace : ` - url: ${ config . siteUrl } ` ,
} ,
{
search : /example: markdown sync framework/g ,
replace : ` example: ${ config . siteName } ` ,
} ,
{
search : /example: https:\/\/markdowncms\.netlify\.app/g ,
replace : ` example: ${ config . siteUrl } ` ,
} ,
] ) ;
}
// Update public/.well-known/ai-plugin.json
function updateAiPluginJson ( config : ForkConfig ) : void {
console . log ( "\nUpdating public/.well-known/ai-plugin.json..." ) ;
const pluginName = config . siteName . toLowerCase ( ) . replace ( /\s+/g , "_" ) . replace ( /[^a-z0-9_]/g , "" ) ;
const content = {
schema_version : "v1" ,
name_for_human : config.siteName ,
name_for_model : pluginName ,
description_for_human : config.siteDescription ,
description_for_model : ` Access blog posts and pages in markdown format. Use /api/posts for a list of all posts with metadata. Use /api/post?slug={slug}&format=md to get full markdown content of any post. Use /api/export for batch content with full markdown. ` ,
auth : {
type : "none" ,
} ,
api : {
type : "openapi" ,
url : "/openapi.yaml" ,
} ,
logo_url : "/images/logo.svg" ,
contact_email : config.contactEmail ,
legal_info_url : "" ,
} ;
const filePath = path . join ( PROJECT_ROOT , "public/.well-known/ai-plugin.json" ) ;
fs . writeFileSync ( filePath , JSON . stringify ( content , null , 2 ) + "\n" , "utf-8" ) ;
console . log ( ` Updated: public/.well-known/ai-plugin.json ` ) ;
}
// Update src/context/ThemeContext.tsx
function updateThemeContext ( config : ForkConfig ) : void {
if ( ! config . theme ) return ;
console . log ( "\nUpdating src/context/ThemeContext.tsx..." ) ;
updateFile ( "src/context/ThemeContext.tsx" , [
{
search : /const DEFAULT_THEME: Theme = "(?:dark|light|tan|cloud)";/ ,
replace : ` const DEFAULT_THEME: Theme = " ${ config . theme } "; ` ,
} ,
] ) ;
}
// Main function
function main ( ) : void {
console . log ( "Fork Configuration Script" ) ;
console . log ( "=========================\n" ) ;
// Read configuration
const config = readConfig ( ) ;
console . log ( ` Configuring site: ${ config . siteName } ` ) ;
console . log ( ` URL: ${ config . siteUrl } ` ) ;
// Apply updates to all files
updateSiteConfig ( config ) ;
updateHomeTsx ( config ) ;
updatePostTsx ( config ) ;
updateConvexHttp ( config ) ;
updateConvexRss ( config ) ;
updateIndexHtml ( config ) ;
updateLlmsTxt ( config ) ;
updateRobotsTxt ( config ) ;
updateOpenApiYaml ( config ) ;
updateAiPluginJson ( config ) ;
updateThemeContext ( config ) ;
console . log ( "\n=========================" ) ;
console . log ( "Configuration complete!" ) ;
console . log ( "\nNext steps:" ) ;
console . log ( "1. Review the changes with: git diff" ) ;
console . log ( "2. Run: npx convex dev (if not already running)" ) ;
console . log ( "3. Run: npm run sync (to sync content to development)" ) ;
console . log ( "4. Run: npm run dev (to start the dev server)" ) ;
console . log ( "5. Deploy to Netlify when ready" ) ;
}
main ( ) ;