Files
wiki/scripts/configure-fork.ts

764 lines
23 KiB
TypeScript
Raw Normal View History

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
#!/usr/bin/env npx tsx
/**
* 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;
// 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";
fontFamily?: "serif" | "sans" | "monospace";
feat: add AI Agent chat integration with Anthropic Claude API Add AI writing assistant (Agent) powered by Anthropic Claude API. Agent can be enabled on Write page (replaces textarea) and optionally in RightSidebar on posts/pages via frontmatter. Features: - AIChatView component with per-page chat history - Page content context support for AI responses - Markdown rendering for AI responses - User-friendly error handling for missing API keys - System prompt configurable via Convex environment variables - Anonymous session authentication using localStorage Environment variables required: - ANTHROPIC_API_KEY (required) - CLAUDE_PROMPT_STYLE, CLAUDE_PROMPT_COMMUNITY, CLAUDE_PROMPT_RULES (optional split prompts) - CLAUDE_SYSTEM_PROMPT (optional single prompt fallback) Configuration: - siteConfig.aiChat.enabledOnWritePage: Enable Agent toggle on /write page - siteConfig.aiChat.enabledOnContent: Allow Agent on posts/pages via frontmatter - Frontmatter aiChat: true (requires rightSidebar: true) Updated files: - src/components/AIChatView.tsx: AI chat interface component - src/components/RightSidebar.tsx: Conditional Agent rendering - src/pages/Write.tsx: Agent mode toggle (title changes to Agent) - convex/aiChats.ts: Chat history queries and mutations - convex/aiChatActions.ts: Claude API integration with error handling - convex/schema.ts: aiChats table with indexes - src/config/siteConfig.ts: AIChatConfig interface - Documentation updated across all files Documentation: - files.md: Updated component descriptions - changelog.md: Added v1.33.0 entry - TASK.md: Marked AI chat tasks as completed - README.md: Added AI Agent Chat section - content/pages/docs.md: Added AI Agent chat documentation - content/blog/setup-guide.md: Added AI Agent chat setup instructions - public/raw/changelog.md: Added v1.33.0 entry
2025-12-26 12:31:33 -08:00
homepage?: {
type: "default" | "page" | "post";
slug?: string;
originalHomeRoute?: 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
}
// 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`,
);
}
// Update fontFamily if specified
if (config.fontFamily) {
content = content.replace(
/fontFamily: ['"](?:serif|sans|monospace)['"],\s*\/\/ Options: "serif", "sans", or "monospace"/,
`fontFamily: "${config.fontFamily}", // Options: "serif", "sans", or "monospace"`,
);
}
feat: add AI Agent chat integration with Anthropic Claude API Add AI writing assistant (Agent) powered by Anthropic Claude API. Agent can be enabled on Write page (replaces textarea) and optionally in RightSidebar on posts/pages via frontmatter. Features: - AIChatView component with per-page chat history - Page content context support for AI responses - Markdown rendering for AI responses - User-friendly error handling for missing API keys - System prompt configurable via Convex environment variables - Anonymous session authentication using localStorage Environment variables required: - ANTHROPIC_API_KEY (required) - CLAUDE_PROMPT_STYLE, CLAUDE_PROMPT_COMMUNITY, CLAUDE_PROMPT_RULES (optional split prompts) - CLAUDE_SYSTEM_PROMPT (optional single prompt fallback) Configuration: - siteConfig.aiChat.enabledOnWritePage: Enable Agent toggle on /write page - siteConfig.aiChat.enabledOnContent: Allow Agent on posts/pages via frontmatter - Frontmatter aiChat: true (requires rightSidebar: true) Updated files: - src/components/AIChatView.tsx: AI chat interface component - src/components/RightSidebar.tsx: Conditional Agent rendering - src/pages/Write.tsx: Agent mode toggle (title changes to Agent) - convex/aiChats.ts: Chat history queries and mutations - convex/aiChatActions.ts: Claude API integration with error handling - convex/schema.ts: aiChats table with indexes - src/config/siteConfig.ts: AIChatConfig interface - Documentation updated across all files Documentation: - files.md: Updated component descriptions - changelog.md: Added v1.33.0 entry - TASK.md: Marked AI chat tasks as completed - README.md: Added AI Agent Chat section - content/pages/docs.md: Added AI Agent chat documentation - content/blog/setup-guide.md: Added AI Agent chat setup instructions - public/raw/changelog.md: Added v1.33.0 entry
2025-12-26 12:31:33 -08:00
// Update homepage configuration if specified
if (config.homepage) {
content = content.replace(
/type: ['"](?:default|page|post)['"],\s*\/\/ Options: "default" \(standard Home component\), "page" \(use a static page\), or "post" \(use a blog post\)/,
`type: "${config.homepage.type}", // Options: "default" (standard Home component), "page" (use a static page), or "post" (use a blog post)`,
);
if (config.homepage.slug !== undefined) {
content = content.replace(
/slug: (?:undefined|['"].*?['"]),\s*\/\/ Required if type is "page" or "post" - the slug of the page\/post to use/,
`slug: ${config.homepage.slug ? `"${config.homepage.slug}"` : "undefined"}, // Required if type is "page" or "post" - the slug of the page/post to use`,
);
}
if (config.homepage.originalHomeRoute !== undefined) {
content = content.replace(
/originalHomeRoute: ['"].*?['"],\s*\/\/ Route to access the original homepage when custom homepage is set/,
`originalHomeRoute: "${config.homepage.originalHomeRoute}", // Route to access the original homepage when custom homepage is set`,
);
}
}
// 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();