From fc84d095b8c9731e4a18d160ce2e55ed4adab9ed Mon Sep 17 00:00:00 2001
From: Carlos Arias
Date: Sat, 4 Jul 2026 23:06:23 +0000
Subject: [PATCH] feat: config-driven identity (site.config.json) + buildable
sample content
- site.config.json single source of identity (name/url/description/author/social)
- scripts/configure.mjs stamps it into app, content-pipeline, and astroagent configs
- app/src/lib/blog-data.js SITE + author now read from app/src/config/site.json
- astro.config.mjs default site URL from config
- index.astro / Footer.astro neutralized hardcoded title, hero alt, social link
- sample 'welcome' post + placeholder hero/avatar so the site builds out of the box
Verified: cd app && npm ci && npm run build -> 13 pages, Complete.
Remaining comiida demo prose in about/faq/services/contact + Newsletter (sample content).
Co-Authored-By: Claude Opus 4.8 (1M context)
Claude-Session: https://claude.ai/code/session_01SYHWLHihq3v9nxNwoPCKSn
---
app/astro.config.mjs | 7 ++-
app/src/components/Footer.astro | 2 +-
app/src/config/site.json | 20 +++++++
app/src/content/blog/welcome/cover.png | Bin 0 -> 5137 bytes
app/src/content/blog/welcome/index.mdx | 22 +++++++
app/src/lib/blog-data.js | 77 +++++--------------------
app/src/pages/index.astro | 10 ++--
astroagent.config.json | 4 +-
content-pipeline/config.json | 47 ++++++++++-----
scripts/configure.mjs | 59 +++++++++++++++++++
site.config.json | 23 ++++++++
11 files changed, 183 insertions(+), 88 deletions(-)
create mode 100644 app/src/config/site.json
create mode 100644 app/src/content/blog/welcome/cover.png
create mode 100644 app/src/content/blog/welcome/index.mdx
create mode 100644 scripts/configure.mjs
create mode 100644 site.config.json
diff --git a/app/astro.config.mjs b/app/astro.config.mjs
index bfe9230..8247250 100644
--- a/app/astro.config.mjs
+++ b/app/astro.config.mjs
@@ -1,9 +1,14 @@
+import { readFileSync } from "node:fs";
import { defineConfig } from "astro/config";
+
+const siteConfig = JSON.parse(
+ readFileSync(new URL("./src/config/site.json", import.meta.url)),
+);
import mdx from "@astrojs/mdx";
import tailwindcss from "@tailwindcss/vite";
const site =
- process.env.SITE_URL || process.env.PUBLIC_SITE_URL || "https://comiida.com";
+ process.env.SITE_URL || process.env.PUBLIC_SITE_URL || siteConfig.url;
export default defineConfig({
site,
diff --git a/app/src/components/Footer.astro b/app/src/components/Footer.astro
index 3a5d6de..fb7b857 100644
--- a/app/src/components/Footer.astro
+++ b/app/src/components/Footer.astro
@@ -14,7 +14,7 @@ const { flush = false } = Astro.props;
diff --git a/app/src/config/site.json b/app/src/config/site.json
new file mode 100644
index 0000000..0f6ba33
--- /dev/null
+++ b/app/src/config/site.json
@@ -0,0 +1,20 @@
+{
+ "name": "SeedProject Site",
+ "url": "https://example.com",
+ "description": "A short one-line description of this site, used for SEO and social sharing.",
+ "tagline": "A starter site built on SeedProject",
+ "language": "en",
+ "author": {
+ "slug": "site-author",
+ "name": "Site Author",
+ "bio": "One-line author bio — who runs this site.",
+ "longBio": "A longer author biography paragraph describing who runs this site and their background.",
+ "avatar": "/avatar-placeholder.png",
+ "jobTitle": "Founder & Editor",
+ "knowsAbout": [],
+ "sameAs": []
+ },
+ "social": {
+ "twitter": ""
+ }
+}
diff --git a/app/src/content/blog/welcome/cover.png b/app/src/content/blog/welcome/cover.png
new file mode 100644
index 0000000000000000000000000000000000000000..9554827fd0e3398b9a4dc854d069047135c01177
GIT binary patch
literal 5137
zcmeAS@N?(olHy`uVBq!ia0y~yVA;UHz^uT*1QglR#1jgnI14-?iy0XBj({-ZRBb+K
z1_t57o-U3d6?5L+-P;#^U9{oi;|F^tule}7I;B)IW|_xCJ^sQN9s`L9LZ-`{bC2GS
z3UK`JDZN
hcKw!09*fd>l%=)T{-ly
zo3O98o}VcPbWQ`O8G1Oq-8#nw7)Svr&U6bZ4CiLeH?{?(9bp`yRjs>Ui+Ld2cN38ruS+oq-ugvcT>?!nyrz@iKRyu?bDQ)G89*Zaw#f2WTV@*?vXx3-z4`
zPJAo&UYj`&R0c3omN;qTJmjMKTU2@#8!$N8AZ00$c_&N$`chzx;IWC^^oA4%gsmhs
zajvM1X#>?xC&47SnF=|Sh^)}IUbnhz3=B+z1NdzI_EC8;P?0BPX@hXEVGm>6Zd_42
z#|c=9Oc==O2`RGBGtut|P;IYrlip2^Z(kS~{+#{$>(A|@w)<`V*8FYQUVd-K^Iuov
zwSWpe)?NaOOu^aIDNP$C~09-TXz5yVo!noC|jYS~6YudET71^7+5xK6G#W`S-5G
z9xP~u1)>lD?5JA``7&CYO`NCOM~^BVo=%nWY53%#kKeDzFt^-?(%Vd
z@$WHrc6>Zl|LQ`uTkZLU)2rv%)m8la^7HFr+uA=LzD+cb`|$MhKAZ1P%@}N^6mu}n
z`10lB;n(|LM&5h+^YU}|{YSIK?0)q!I0!I(a5z8T?*IR`v!yfk-rHyKrRZ_)<$d?|
z>6e%)GTd(mY6Uu2-#@P9tn>6n^LwxR4A(uckFLt+-}~RW_WXV}hCAOb+t>fU4k_M0
zObrAUpk!o1!j%%ftb^JNS+RGTD5&C^NVVzNsa`94XH>(w*FhxIv;*I
zG}Li-XNXM7S?fz7y~qaYW+KWPz33GrdX0lFj~x62TLjd19>jXw_4lA~9#ZLhCGNG)
zd0@6Xz(7*>=k4QNz#5lM%_EW%=hpty1Jn#5rlEett}aDnAX>hVXvf`|*xDZqs-7lM
zxqC@u5rk2MqGh5id2Z04g9=IMlw7-!atOxg43a}v*fTuep046y*w_Hf4Gf;HelF{r
G5}E)eLgG09
literal 0
HcmV?d00001
diff --git a/app/src/content/blog/welcome/index.mdx b/app/src/content/blog/welcome/index.mdx
new file mode 100644
index 0000000..cf93ca4
--- /dev/null
+++ b/app/src/content/blog/welcome/index.mdx
@@ -0,0 +1,22 @@
+---
+title: "Welcome to your new site"
+excerpt: "A sample post so your blog builds out of the box. Edit or delete it once you add real content."
+date: 2026-01-01
+readingTime: 2
+category: "guides"
+author: "site-author"
+thumbnail: "./cover.png"
+featured: true
+---
+
+This is a sample post included with the **SeedProject base**. It exists so the
+blog, homepage, RSS feed, and sitemap all build before you've written anything.
+
+## Getting started
+
+1. Edit `site.config.json` at the repo root with your site's name, URL, and author.
+2. Run `node scripts/configure.mjs` to stamp that identity across the theme and engines.
+3. Replace this post with your own content under `app/src/content/blog/`.
+
+Each post is a folder with an `index.mdx` file and a `cover` image, using the
+frontmatter fields defined in `app/src/content.config.js`.
diff --git a/app/src/lib/blog-data.js b/app/src/lib/blog-data.js
index 255fff6..ccd1ea7 100644
--- a/app/src/lib/blog-data.js
+++ b/app/src/lib/blog-data.js
@@ -1,33 +1,22 @@
import { getCollection } from "astro:content";
+import siteConfig from "../config/site.json";
const siteUrl = (
import.meta.env.SITE_URL ||
import.meta.env.PUBLIC_SITE_URL ||
- "https://quietpages-eta.vercel.app"
+ siteConfig.url
).replace(/\/$/, "");
export const authors = [
{
- slug: "carlos-arias",
- name: "Carlos Arias",
- bio: "AI engineer and digital strategist with 25+ years building software and AI systems; founder of CarlosArias&Co and engineer behind Medellín.co.",
- longBio:
- "Carlos Arias is an AI engineer and strategist with over two decades across software engineering, digital marketing, and AI. He is the founder of CarlosArias&Co and Snoopi.io, and an engineer and strategist behind the Medellín.co city guide. Based between South Florida and Latin America, he builds AI-driven systems for local visibility and revenue-tracked growth. Comiida's articles are researched from public sources and AI-assisted under his editorial oversight.",
- avatar: "https://carlosarias.com/wp-content/uploads/2023/02/cropped-clos.jpg",
- jobTitle: "Founder & Editor, Comiida",
- knowsAbout: [
- "Medellín restaurants",
- "Colombian cuisine",
- "dining guides",
- "local SEO",
- ],
- sameAs: [
- "https://x.com/hicarlosarias",
- "https://www.linkedin.com/in/hicarlosarias/",
- "https://www.instagram.com/carlosarias.co",
- "https://www.facebook.com/hicarlosarias",
- "https://carlosarias.com",
- ],
+ slug: siteConfig.author.slug,
+ name: siteConfig.author.name,
+ bio: siteConfig.author.bio,
+ longBio: siteConfig.author.longBio,
+ avatar: siteConfig.author.avatar,
+ jobTitle: siteConfig.author.jobTitle,
+ knowsAbout: siteConfig.author.knowsAbout || [],
+ sameAs: siteConfig.author.sameAs || [],
},
];
@@ -38,45 +27,7 @@ export const categories = [
{ slug: "neighborhoods", name: "Neighborhoods" },
];
-export const tags = [
- { slug: "food-guide", name: "Food Guide" },
- { slug: "ciudad-del-rio", name: "Ciudad Del Rio" },
- { slug: "things-to-do", name: "Things To Do" },
- { slug: "terminal-del-sur", name: "Terminal Del Sur" },
- { slug: "specialty-coffee", name: "Specialty Coffee" },
- { slug: "craft-beer", name: "Craft Beer" },
- { slug: "rionegro", name: "Rionegro" },
- { slug: "antioquia", name: "Antioquia" },
- { slug: "day-trip", name: "Day Trip" },
- { slug: "oriente-antioqueno", name: "Oriente Antioqueno" },
- { slug: "food-festival", name: "Food Festival" },
- { slug: "marinilla", name: "Marinilla" },
- { slug: "wake-medellin", name: "Wake Medellin" },
- { slug: "peruvian-cuisine", name: "Peruvian Cuisine" },
- { slug: "steakhouse", name: "Steakhouse" },
- { slug: "gastronomy", name: "Gastronomy" },
- { slug: "time-out", name: "Time Out" },
- { slug: "food-ranking", name: "Food Ranking" },
- { slug: "chef", name: "Chef" },
- { slug: "colombian-cuisine", name: "Colombian Cuisine" },
- { slug: "new-restaurant", name: "New Restaurant" },
- { slug: "legal-news", name: "Legal News" },
- { slug: "fine-dining", name: "Fine Dining" },
- { slug: "hidden-kitchen", name: "Hidden Kitchen" },
- { slug: "chefs-table", name: "Chefs Table" },
- { slug: "tasting-menu", name: "Tasting Menu" },
- { slug: "new-openings", name: "New Openings" },
- { slug: "restaurant-news", name: "Restaurant News" },
- { slug: "restaurants", name: "Restaurants" },
- { slug: "coffee", name: "Coffee" },
- { slug: "el-poblado", name: "El Poblado" },
- { slug: "brunch", name: "Brunch" },
- { slug: "expats", name: "Expats" },
- { slug: "food-safety", name: "Food Safety" },
- { slug: "street-food", name: "Street Food" },
- { slug: "medellin", name: "Medellín" },
- { slug: "travel", name: "Travel" },
-];
+export const tags = [];
export const imageSrc = (image) => (typeof image === "string" ? image : image?.src);
@@ -136,9 +87,9 @@ export const formatDate = (iso) =>
});
export const SITE = {
- name: "Comiida",
- description:
- "Medellín's restaurant scene for English-speaking expats, nomads, and travelers — guides, news, and data-driven dining picks.",
+ name: siteConfig.name,
+ description: siteConfig.description,
+ tagline: siteConfig.tagline,
url: siteUrl,
};
diff --git a/app/src/pages/index.astro b/app/src/pages/index.astro
index 9602e5b..062d053 100644
--- a/app/src/pages/index.astro
+++ b/app/src/pages/index.astro
@@ -28,7 +28,7 @@ const jsonLd = {
---
-
{featuredAuthor.name}
diff --git a/astroagent.config.json b/astroagent.config.json
index abd7098..87bfa83 100644
--- a/astroagent.config.json
+++ b/astroagent.config.json
@@ -1,6 +1,6 @@
{
- "name": "comiida",
- "url": "https://comiida.com",
+ "name": "seedproject-site",
+ "url": "https://example.com",
"appDir": "app",
"outDir": "public",
"buildCommand": "npm run build",
diff --git a/content-pipeline/config.json b/content-pipeline/config.json
index 490130f..fe32e19 100644
--- a/content-pipeline/config.json
+++ b/content-pipeline/config.json
@@ -1,9 +1,9 @@
{
"site": {
- "url": "https://comiida.com",
- "name": "Comiida",
- "topic": "the restaurant industry in Medellin, Colombia",
- "audience": "English-speaking expats, digital nomads, and tourists in Medellin",
+ "url": "https://example.com",
+ "name": "SeedProject Site",
+ "topic": "your subject area",
+ "audience": "your target audience",
"language": "en"
},
"paths": {
@@ -36,8 +36,8 @@
"maxPerScan": 8
},
"author": {
- "slug": "carlos-arias",
- "name": "Carlos Arias"
+ "slug": "site-author",
+ "name": "Site Author"
},
"image": {
"provider": "higgsfield",
@@ -46,27 +46,42 @@
"outputFormat": "jpg",
"credit": {
"caption": "Illustrative cover image. Not a photograph of any specific establishment.",
- "author": "Comiida"
+ "author": "SeedProject Site"
}
},
"editorial": {
"calendarDays": 90,
"refillThreshold": 7,
"refillCount": 14,
- "timezone": "America/Bogota",
+ "timezone": "America/New_York",
"contentMix": {
- "guide": 0.30,
+ "guide": 0.3,
"list": 0.25,
- "news-roundup": 0.20,
+ "news-roundup": 0.2,
"trend": 0.15,
- "review-summary": 0.10
+ "review-summary": 0.1
},
"wordCounts": {
- "news-roundup": [600, 900],
- "trend": [800, 1200],
- "review-summary": [900, 1300],
- "list": [1000, 1500],
- "guide": [1200, 1800]
+ "news-roundup": [
+ 600,
+ 900
+ ],
+ "trend": [
+ 800,
+ 1200
+ ],
+ "review-summary": [
+ 900,
+ 1300
+ ],
+ "list": [
+ 1000,
+ 1500
+ ],
+ "guide": [
+ 1200,
+ 1800
+ ]
}
}
}
diff --git a/scripts/configure.mjs b/scripts/configure.mjs
new file mode 100644
index 0000000..12da949
--- /dev/null
+++ b/scripts/configure.mjs
@@ -0,0 +1,59 @@
+#!/usr/bin/env node
+/**
+ * configure.mjs — stamp site.config.json (the single source of identity) into
+ * every engine config. Run after editing site.config.json:
+ *
+ * node scripts/configure.mjs
+ *
+ * Writes:
+ * - app/src/config/site.json (Astro theme reads this)
+ * - content-pipeline/config.json (site identity + author + timezone)
+ * - astroagent.config.json (name + url)
+ */
+import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
+import { dirname, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
+const read = (p) => JSON.parse(readFileSync(resolve(root, p), "utf8"));
+const write = (p, obj) => {
+ const full = resolve(root, p);
+ mkdirSync(dirname(full), { recursive: true });
+ writeFileSync(full, JSON.stringify(obj, null, 2) + "\n");
+ console.log(" wrote", p);
+};
+const slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
+
+const site = read("site.config.json");
+console.log(`Configuring site: ${site.name} <${site.url}>`);
+
+// 1) Astro theme
+write("app/src/config/site.json", {
+ name: site.name,
+ url: site.url,
+ description: site.description,
+ tagline: site.tagline,
+ language: site.language,
+ author: site.author,
+ social: site.social,
+});
+
+// 2) content-pipeline
+if (existsSync(resolve(root, "content-pipeline/config.json"))) {
+ const cp = read("content-pipeline/config.json");
+ cp.site = { ...cp.site, url: site.url, name: site.name, topic: site.topic, audience: site.audience, language: site.language };
+ cp.author = { ...(cp.author || {}), slug: site.author.slug, name: site.author.name };
+ if (cp.editorial) cp.editorial.timezone = site.timezone;
+ if (cp.image && cp.image.credit) cp.image.credit.author = site.name;
+ write("content-pipeline/config.json", cp);
+}
+
+// 3) astroagent
+if (existsSync(resolve(root, "astroagent.config.json"))) {
+ const aa = read("astroagent.config.json");
+ aa.name = slug(site.name);
+ aa.url = site.url;
+ write("astroagent.config.json", aa);
+}
+
+console.log("Done. Rebuild the site: (cd app && npm run build)");
diff --git a/site.config.json b/site.config.json
new file mode 100644
index 0000000..3d0c86a
--- /dev/null
+++ b/site.config.json
@@ -0,0 +1,23 @@
+{
+ "name": "SeedProject Site",
+ "url": "https://example.com",
+ "description": "A short one-line description of this site, used for SEO and social sharing.",
+ "tagline": "A starter site built on SeedProject",
+ "language": "en",
+ "timezone": "America/New_York",
+ "topic": "your subject area",
+ "audience": "your target audience",
+ "author": {
+ "slug": "site-author",
+ "name": "Site Author",
+ "bio": "One-line author bio — who runs this site.",
+ "longBio": "A longer author biography paragraph describing who runs this site and their background.",
+ "avatar": "/avatar-placeholder.png",
+ "jobTitle": "Founder & Editor",
+ "knowsAbout": [],
+ "sameAs": []
+ },
+ "social": {
+ "twitter": ""
+ }
+}