mirror of
https://github.com/waynesutton/markdown-site.git
synced 2026-01-12 04:09:14 +00:00
feat: Multi-model AI chat and image generation in Dashboard
This commit is contained in:
@@ -440,12 +440,14 @@ function updatePostTsx(config: ForkConfig): void {
|
||||
console.log("\nUpdating src/pages/Post.tsx...");
|
||||
|
||||
updateFile("src/pages/Post.tsx", [
|
||||
// Match any existing SITE_URL value (https://...)
|
||||
{
|
||||
search: /const SITE_URL = "https:\/\/markdowncms\.netlify\.app";/,
|
||||
search: /const SITE_URL = "https:\/\/[^"]+";/,
|
||||
replace: `const SITE_URL = "${config.siteUrl}";`,
|
||||
},
|
||||
// Match any existing SITE_NAME value
|
||||
{
|
||||
search: /const SITE_NAME = "markdown sync framework";/,
|
||||
search: /const SITE_NAME = "[^"]+";/,
|
||||
replace: `const SITE_NAME = "${config.siteName}";`,
|
||||
},
|
||||
]);
|
||||
@@ -456,22 +458,31 @@ function updateConvexHttp(config: ForkConfig): void {
|
||||
console.log("\nUpdating convex/http.ts...");
|
||||
|
||||
updateFile("convex/http.ts", [
|
||||
// Match any existing SITE_URL value with process.env fallback
|
||||
{
|
||||
search: /const SITE_URL = process\.env\.SITE_URL \|\| "https:\/\/markdowncms\.netlify\.app";/,
|
||||
search: /const SITE_URL = process\.env\.SITE_URL \|\| "https:\/\/[^"]+";/,
|
||||
replace: `const SITE_URL = process.env.SITE_URL || "${config.siteUrl}";`,
|
||||
},
|
||||
// Match any existing SITE_NAME value (line 10)
|
||||
{
|
||||
search: /const SITE_NAME = "markdown sync framework";/,
|
||||
search: /const SITE_NAME = "[^"]+";/,
|
||||
replace: `const SITE_NAME = "${config.siteName}";`,
|
||||
},
|
||||
// Match any existing siteUrl in generateMetaHtml function
|
||||
{
|
||||
search: /const siteUrl = process\.env\.SITE_URL \|\| "https:\/\/markdowncms\.netlify\.app";/,
|
||||
search: /const siteUrl = process\.env\.SITE_URL \|\| "https:\/\/[^"]+";/,
|
||||
replace: `const siteUrl = process.env.SITE_URL || "${config.siteUrl}";`,
|
||||
},
|
||||
// Match any existing siteName in generateMetaHtml function
|
||||
{
|
||||
search: /const siteName = "markdown sync framework";/,
|
||||
search: /const siteName = "[^"]+";/,
|
||||
replace: `const siteName = "${config.siteName}";`,
|
||||
},
|
||||
// Update the description in API responses
|
||||
{
|
||||
search: /"An open-source publishing framework[^"]*"/g,
|
||||
replace: `"${config.siteDescription}"`,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -480,14 +491,17 @@ function updateConvexRss(config: ForkConfig): void {
|
||||
console.log("\nUpdating convex/rss.ts...");
|
||||
|
||||
updateFile("convex/rss.ts", [
|
||||
// Match any existing SITE_URL value with process.env fallback
|
||||
{
|
||||
search: /const SITE_URL = process\.env\.SITE_URL \|\| "https:\/\/markdowncms\.netlify\.app";/,
|
||||
search: /const SITE_URL = process\.env\.SITE_URL \|\| "https:\/\/[^"]+";/,
|
||||
replace: `const SITE_URL = process.env.SITE_URL || "${config.siteUrl}";`,
|
||||
},
|
||||
// Match any existing SITE_TITLE value
|
||||
{
|
||||
search: /const SITE_TITLE = "markdown sync framework";/,
|
||||
search: /const SITE_TITLE = "[^"]+";/,
|
||||
replace: `const SITE_TITLE = "${config.siteName}";`,
|
||||
},
|
||||
// Match any existing SITE_DESCRIPTION value (multiline)
|
||||
{
|
||||
search: /const SITE_DESCRIPTION =\s*"[^"]+";/,
|
||||
replace: `const SITE_DESCRIPTION =\n "${config.siteDescription}";`,
|
||||
@@ -500,89 +514,94 @@ function updateIndexHtml(config: ForkConfig): void {
|
||||
console.log("\nUpdating index.html...");
|
||||
|
||||
const replacements: Array<{ search: string | RegExp; replace: string }> = [
|
||||
// Meta description
|
||||
// Meta description (match any content)
|
||||
{
|
||||
search: /<meta\s*name="description"\s*content="[^"]*"\s*\/>/,
|
||||
replace: `<meta\n name="description"\n content="${config.siteDescription}"\n />`,
|
||||
},
|
||||
// Meta author
|
||||
// Meta author (match any content)
|
||||
{
|
||||
search: /<meta name="author" content="[^"]*" \/>/,
|
||||
replace: `<meta name="author" content="${config.siteName}" />`,
|
||||
},
|
||||
// Open Graph title
|
||||
// Open Graph title (match any content)
|
||||
{
|
||||
search: /<meta property="og:title" content="[^"]*" \/>/,
|
||||
replace: `<meta property="og:title" content="${config.siteName}" />`,
|
||||
},
|
||||
// Open Graph description
|
||||
// Open Graph description (match any content)
|
||||
{
|
||||
search: /<meta\s*property="og:description"\s*content="[^"]*"\s*\/>/,
|
||||
replace: `<meta\n property="og:description"\n content="${config.siteDescription}"\n />`,
|
||||
},
|
||||
// Open Graph URL
|
||||
// Open Graph URL (match any https URL)
|
||||
{
|
||||
search: /<meta property="og:url" content="https:\/\/markdowncms\.netlify\.app\/" \/>/,
|
||||
search: /<meta property="og:url" content="https:\/\/[^"]*" \/>/,
|
||||
replace: `<meta property="og:url" content="${config.siteUrl}/" />`,
|
||||
},
|
||||
// Open Graph site name
|
||||
// Open Graph site name (match any content)
|
||||
{
|
||||
search: /<meta property="og:site_name" content="[^"]*" \/>/,
|
||||
search: /<meta property="og:site_name" content="[^"]*"\s*\/>/,
|
||||
replace: `<meta property="og:site_name" content="${config.siteName}" />`,
|
||||
},
|
||||
// Open Graph image
|
||||
// Open Graph site name with newline formatting
|
||||
{
|
||||
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 />`,
|
||||
search: /<meta\s*property="og:site_name"\s*content="[^"]*"\s*>/,
|
||||
replace: `<meta\n property="og:site_name"\n content="${config.siteName}"\n >`,
|
||||
},
|
||||
// Twitter domain
|
||||
// Open Graph image (match any https URL)
|
||||
{
|
||||
search: /<meta\s*property="og:image"\s*content="https:\/\/[^"]*"\s*\/>/,
|
||||
replace: `<meta\n property="og:image"\n content="${config.siteUrl}/images/og-default.png"\n />`,
|
||||
},
|
||||
// Twitter domain (match any domain)
|
||||
{
|
||||
search: /<meta property="twitter:domain" content="[^"]*" \/>/,
|
||||
replace: `<meta property="twitter:domain" content="${config.siteDomain}" />`,
|
||||
},
|
||||
// Twitter URL
|
||||
// Twitter URL (match any https URL)
|
||||
{
|
||||
search: /<meta property="twitter:url" content="https:\/\/markdowncms\.netlify\.app\/" \/>/,
|
||||
search: /<meta property="twitter:url" content="https:\/\/[^"]*" \/>/,
|
||||
replace: `<meta property="twitter:url" content="${config.siteUrl}/" />`,
|
||||
},
|
||||
// Twitter title
|
||||
// Twitter title (match any content)
|
||||
{
|
||||
search: /<meta name="twitter:title" content="[^"]*" \/>/,
|
||||
replace: `<meta name="twitter:title" content="${config.siteName}" />`,
|
||||
},
|
||||
// Twitter description
|
||||
// Twitter description (match any content)
|
||||
{
|
||||
search: /<meta\s*name="twitter:description"\s*content="[^"]*"\s*\/>/,
|
||||
replace: `<meta\n name="twitter:description"\n content="${config.siteDescription}"\n />`,
|
||||
},
|
||||
// Twitter image
|
||||
// Twitter image (match any https URL)
|
||||
{
|
||||
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 />`,
|
||||
search: /<meta\s*name="twitter:image"\s*content="https:\/\/[^"]*"\s*\/>/,
|
||||
replace: `<meta\n name="twitter:image"\n content="${config.siteUrl}/images/og-default.png"\n />`,
|
||||
},
|
||||
// JSON-LD name
|
||||
// JSON-LD name (match any value)
|
||||
{
|
||||
search: /"name": "markdown sync framework"/g,
|
||||
replace: `"name": "${config.siteName}"`,
|
||||
search: /"name": "[^"]+",\s*\n\s*"url":/g,
|
||||
replace: `"name": "${config.siteName}",\n "url":`,
|
||||
},
|
||||
// JSON-LD URL
|
||||
// JSON-LD URL (match any https URL)
|
||||
{
|
||||
search: /"url": "https:\/\/markdowncms\.netlify\.app"/g,
|
||||
search: /"url": "https:\/\/[^"]+"/g,
|
||||
replace: `"url": "${config.siteUrl}"`,
|
||||
},
|
||||
// JSON-LD description
|
||||
// JSON-LD description (match any content)
|
||||
{
|
||||
search: /"description": "An open-source publishing framework[^"]*"/,
|
||||
search: /"description": "[^"]+"/,
|
||||
replace: `"description": "${config.siteDescription}"`,
|
||||
},
|
||||
// JSON-LD search target
|
||||
// JSON-LD search target (match any URL)
|
||||
{
|
||||
search: /"target": "https:\/\/markdowncms\.netlify\.app\/\?q=\{search_term_string\}"/,
|
||||
search: /"target": "https:\/\/[^"]+\/\?q=\{search_term_string\}"/,
|
||||
replace: `"target": "${config.siteUrl}/?q={search_term_string}"`,
|
||||
},
|
||||
// Page title
|
||||
// Page title (match any title content)
|
||||
{
|
||||
search: /<title>markdown "sync" framework<\/title>/,
|
||||
search: /<title>[^<]+<\/title>/,
|
||||
replace: `<title>${config.siteTitle}</title>`,
|
||||
},
|
||||
];
|
||||
@@ -733,25 +752,30 @@ function updateOpenApiYaml(config: ForkConfig): void {
|
||||
const githubUrl = `https://github.com/${config.githubUsername}/${config.githubRepo}`;
|
||||
|
||||
updateFile("public/openapi.yaml", [
|
||||
// Match any title ending with API
|
||||
{
|
||||
search: /title: markdown sync framework API/,
|
||||
search: /title: .+ API/,
|
||||
replace: `title: ${config.siteName} API`,
|
||||
},
|
||||
// Match any GitHub contact URL
|
||||
{
|
||||
search: /url: https:\/\/github\.com\/waynesutton\/markdown-site/,
|
||||
search: /url: https:\/\/github\.com\/[^\/]+\/[^\s]+/,
|
||||
replace: `url: ${githubUrl}`,
|
||||
},
|
||||
// Match any server URL (production server line)
|
||||
{
|
||||
search: /- url: https:\/\/markdowncms\.netlify\.app/,
|
||||
replace: `- url: ${config.siteUrl}`,
|
||||
search: /- url: https:\/\/[^\s]+\n\s+description: Production server/,
|
||||
replace: `- url: ${config.siteUrl}\n description: Production server`,
|
||||
},
|
||||
// Match any example site name
|
||||
{
|
||||
search: /example: markdown sync framework/g,
|
||||
replace: `example: ${config.siteName}`,
|
||||
search: /example: .+\n\s+url:/g,
|
||||
replace: `example: ${config.siteName}\n url:`,
|
||||
},
|
||||
// Match any example URL (for site URL)
|
||||
{
|
||||
search: /example: https:\/\/markdowncms\.netlify\.app/g,
|
||||
replace: `example: ${config.siteUrl}`,
|
||||
search: /example: https:\/\/[^\s]+\n\s+posts:/,
|
||||
replace: `example: ${config.siteUrl}\n posts:`,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
/**
|
||||
* Discovery Files Sync Script
|
||||
*
|
||||
* Reads siteConfig.ts and Convex data to update discovery files.
|
||||
* Reads fork-config.json (if available), siteConfig.ts, and Convex data to update discovery files.
|
||||
* Run with: npm run sync:discovery (dev) or npm run sync:discovery:prod (prod)
|
||||
*
|
||||
* This script updates:
|
||||
* - AGENTS.md (project overview and current status sections)
|
||||
* - CLAUDE.md (current status section for Claude Code)
|
||||
* - public/llms.txt (site info, API endpoints, GitHub links)
|
||||
*
|
||||
* IMPORTANT: If fork-config.json exists, it will be used as the source of truth.
|
||||
* This ensures that after running `npm run configure`, subsequent sync:discovery
|
||||
* commands will use your configured values.
|
||||
*/
|
||||
|
||||
import fs from "fs";
|
||||
@@ -33,12 +37,48 @@ const PROJECT_ROOT = process.cwd();
|
||||
const PUBLIC_DIR = path.join(PROJECT_ROOT, "public");
|
||||
const ROOT_DIR = PROJECT_ROOT;
|
||||
|
||||
// Fork config interface (matches fork-config.json structure)
|
||||
interface ForkConfig {
|
||||
siteName: string;
|
||||
siteTitle: string;
|
||||
siteDescription: string;
|
||||
siteUrl: string;
|
||||
siteDomain: string;
|
||||
githubUsername: string;
|
||||
githubRepo: string;
|
||||
contactEmail?: string;
|
||||
bio?: string;
|
||||
gitHubRepoConfig?: {
|
||||
owner: string;
|
||||
repo: string;
|
||||
branch: string;
|
||||
contentPath: string;
|
||||
};
|
||||
}
|
||||
|
||||
// Load fork-config.json if it exists
|
||||
function loadForkConfig(): ForkConfig | null {
|
||||
try {
|
||||
const configPath = path.join(PROJECT_ROOT, "fork-config.json");
|
||||
if (fs.existsSync(configPath)) {
|
||||
const content = fs.readFileSync(configPath, "utf-8");
|
||||
const config = JSON.parse(content) as ForkConfig;
|
||||
console.log("Using configuration from fork-config.json");
|
||||
return config;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Could not load fork-config.json, falling back to siteConfig.ts");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Site config data structure
|
||||
interface SiteConfigData {
|
||||
name: string;
|
||||
title: string;
|
||||
bio: string;
|
||||
description?: string;
|
||||
siteUrl?: string; // Added to pass URL from fork-config.json
|
||||
gitHubRepo?: {
|
||||
owner: string;
|
||||
repo: string;
|
||||
@@ -47,8 +87,39 @@ interface SiteConfigData {
|
||||
};
|
||||
}
|
||||
|
||||
// Load site config from siteConfig.ts using regex
|
||||
// Cached fork config
|
||||
let cachedForkConfig: ForkConfig | null | undefined = undefined;
|
||||
|
||||
// Get fork config (cached)
|
||||
function getForkConfig(): ForkConfig | null {
|
||||
if (cachedForkConfig === undefined) {
|
||||
cachedForkConfig = loadForkConfig();
|
||||
}
|
||||
return cachedForkConfig;
|
||||
}
|
||||
|
||||
// Load site config - prioritizes fork-config.json over siteConfig.ts
|
||||
function loadSiteConfig(): SiteConfigData {
|
||||
// First try fork-config.json
|
||||
const forkConfig = getForkConfig();
|
||||
if (forkConfig) {
|
||||
return {
|
||||
name: forkConfig.siteName,
|
||||
title: forkConfig.siteTitle,
|
||||
bio: forkConfig.bio || forkConfig.siteDescription,
|
||||
description: forkConfig.siteDescription,
|
||||
siteUrl: forkConfig.siteUrl,
|
||||
gitHubRepo: forkConfig.gitHubRepoConfig || {
|
||||
owner: forkConfig.githubUsername,
|
||||
repo: forkConfig.githubRepo,
|
||||
branch: "main",
|
||||
contentPath: "public/raw",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Fall back to siteConfig.ts
|
||||
console.log("No fork-config.json found, reading from siteConfig.ts");
|
||||
try {
|
||||
const configPath = path.join(
|
||||
PROJECT_ROOT,
|
||||
@@ -94,14 +165,14 @@ function loadSiteConfig(): SiteConfigData {
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
name: nameMatch?.[1] || "markdown sync framework",
|
||||
title: titleMatch?.[1] || "markdown sync framework",
|
||||
name: nameMatch?.[1] || "Your Site Name",
|
||||
title: titleMatch?.[1] || "Your Site Title",
|
||||
bio:
|
||||
bioMatch?.[1] ||
|
||||
"An open-source publishing framework built for AI agents and developers to ship websites, docs, or blogs..",
|
||||
"Your site description here.",
|
||||
description:
|
||||
bioMatch?.[1] ||
|
||||
"An open-source publishing framework built for AI agents and developers to ship websites, docs, or blogs..",
|
||||
"Your site description here.",
|
||||
gitHubRepo,
|
||||
};
|
||||
}
|
||||
@@ -110,30 +181,51 @@ function loadSiteConfig(): SiteConfigData {
|
||||
}
|
||||
|
||||
return {
|
||||
name: "markdown sync framework",
|
||||
title: "markdown sync framework",
|
||||
bio: "An open-source publishing framework built for AI agents and developers to ship websites, docs, or blogs..",
|
||||
description:
|
||||
"An open-source publishing framework built for AI agents and developers to ship websites, docs, or blogs..",
|
||||
name: "Your Site Name",
|
||||
title: "Your Site Title",
|
||||
bio: "Your site description here.",
|
||||
description: "Your site description here.",
|
||||
};
|
||||
}
|
||||
|
||||
// Get site URL from environment or config
|
||||
function getSiteUrl(): string {
|
||||
return (
|
||||
process.env.SITE_URL || process.env.VITE_SITE_URL || "https://markdown.fast"
|
||||
);
|
||||
// Get site URL from fork-config.json, environment, or siteConfig
|
||||
function getSiteUrl(siteConfig?: SiteConfigData): string {
|
||||
// 1. Check fork-config.json (via siteConfig)
|
||||
if (siteConfig?.siteUrl) {
|
||||
return siteConfig.siteUrl;
|
||||
}
|
||||
// 2. Check fork-config.json directly
|
||||
const forkConfig = getForkConfig();
|
||||
if (forkConfig?.siteUrl) {
|
||||
return forkConfig.siteUrl;
|
||||
}
|
||||
// 3. Check environment variables
|
||||
if (process.env.SITE_URL) {
|
||||
return process.env.SITE_URL;
|
||||
}
|
||||
if (process.env.VITE_SITE_URL) {
|
||||
return process.env.VITE_SITE_URL;
|
||||
}
|
||||
// 4. Return placeholder (user should configure)
|
||||
return "https://yoursite.example.com";
|
||||
}
|
||||
|
||||
// Build GitHub URL from repo config or fallback
|
||||
// Build GitHub URL from repo config or fork-config.json
|
||||
function getGitHubUrl(siteConfig: SiteConfigData): string {
|
||||
if (siteConfig.gitHubRepo) {
|
||||
return `https://github.com/${siteConfig.gitHubRepo.owner}/${siteConfig.gitHubRepo.repo}`;
|
||||
}
|
||||
return (
|
||||
process.env.GITHUB_REPO_URL ||
|
||||
"https://github.com/waynesutton/markdown-site"
|
||||
);
|
||||
// Check fork-config.json directly
|
||||
const forkConfig = getForkConfig();
|
||||
if (forkConfig) {
|
||||
return `https://github.com/${forkConfig.githubUsername}/${forkConfig.githubRepo}`;
|
||||
}
|
||||
// Check environment variable
|
||||
if (process.env.GITHUB_REPO_URL) {
|
||||
return process.env.GITHUB_REPO_URL;
|
||||
}
|
||||
// Return placeholder
|
||||
return "https://github.com/yourusername/your-repo";
|
||||
}
|
||||
|
||||
// Update CLAUDE.md with current status
|
||||
@@ -326,9 +418,9 @@ async function syncDiscoveryFiles() {
|
||||
// Initialize Convex client
|
||||
const client = new ConvexHttpClient(convexUrl);
|
||||
|
||||
// Load site configuration
|
||||
// Load site configuration (uses fork-config.json if available)
|
||||
const siteConfig = loadSiteConfig();
|
||||
const siteUrl = getSiteUrl();
|
||||
const siteUrl = getSiteUrl(siteConfig);
|
||||
|
||||
console.log(`Site: ${siteConfig.name}`);
|
||||
console.log(`Title: ${siteConfig.title}`);
|
||||
|
||||
Reference in New Issue
Block a user