From fba369a36d850ad4a01230c6ddc9b277fbc6e31c Mon Sep 17 00:00:00 2001 From: Jonathan Hvid Date: Mon, 11 May 2026 14:44:09 +0200 Subject: [PATCH] chore(seed): one-time roadmap seed from content/roadmap.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parses the H2 sections (In progress / Next / Later) into roadmap_items rows. Maps In-progress → beta (actively built, tested with pilots) and Next/Later → exploring with a target hint. Idempotent: skips entirely if the table is already populated, so admin edits are never overwritten. content/roadmap.md stays in the repo as the seed source. Once admin starts editing via /admin, the DB is the source of truth. Co-Authored-By: Claude Opus 4.7 (1M context) --- package.json | 3 +- scripts/seed-roadmap.js | 84 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 scripts/seed-roadmap.js diff --git a/package.json b/package.json index ad2b273..0c4ab95 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "test": "vitest run", "db:migrate": "node scripts/migrate.js", "db:seed": "node scripts/seed.js", - "db:setup": "node scripts/migrate.js && node scripts/seed.js" + "db:seed:roadmap": "node scripts/seed-roadmap.js", + "db:setup": "node scripts/migrate.js && node scripts/seed.js && node scripts/seed-roadmap.js" }, "dependencies": { "@astrojs/node": "^8.3.0", diff --git a/scripts/seed-roadmap.js b/scripts/seed-roadmap.js new file mode 100644 index 0000000..06abe6f --- /dev/null +++ b/scripts/seed-roadmap.js @@ -0,0 +1,84 @@ +#!/usr/bin/env node +// Parse content/roadmap.md → roadmap_items rows. Idempotent: only seeds if +// the table is empty. Once admin starts managing roadmap via /admin, the +// markdown is no longer the source of truth — but it stays in the repo as +// the initial-seed reference. + +import Database from 'better-sqlite3'; +import { readFileSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const dbPath = process.env.BIFROST_DB_PATH ?? join(__dirname, '..', 'bifrost.db'); +const mdPath = join(__dirname, '..', 'content', 'roadmap.md'); + +const db = new Database(dbPath); +db.pragma('foreign_keys = ON'); + +const existing = db.prepare('SELECT COUNT(*) AS n FROM roadmap_items').get().n; +if (existing > 0) { + console.log(` roadmap_items already populated (${existing} rows) — skipping seed.`); + db.close(); + process.exit(0); +} + +const md = readFileSync(mdPath, 'utf8'); + +// Section → status mapping. +// Markdown sections "In progress" / "Next" / "Later" map to the council-portal +// schema's three statuses. In-progress items are actively being built and +// tested with pilots → beta. Next/Later are roadmap intent, not started → exploring. +const SECTION_STATUS = { + 'In progress': { status: 'beta', target: null }, + 'Next': { status: 'exploring', target: 'Next quarter' }, + 'Later': { status: 'exploring', target: 'Later this year' }, +}; + +const items = []; +let currentSection = null; +let sectionDisplayOrder = 0; + +for (const rawLine of md.split('\n')) { + const line = rawLine.trim(); + const h2 = line.match(/^##\s+(.+)$/); + if (h2) { + currentSection = h2[1].trim(); + sectionDisplayOrder = 0; + continue; + } + if (!currentSection || !SECTION_STATUS[currentSection]) continue; + + // Format: **Title** — description text. Optional `pilot-only` flag at end. + const m = line.match(/^\*\*([^*]+)\*\*\s*[—-]\s*(.+)$/); + if (!m) continue; + + sectionDisplayOrder += 10; + const title = m[1].trim(); + let description = m[2].trim(); + // strip trailing ` `pilot-only` ` flag; admin can re-add via UI if wanted + description = description.replace(/`pilot-only`\s*$/, '').trim(); + + items.push({ + title, + description, + status: SECTION_STATUS[currentSection].status, + target: SECTION_STATUS[currentSection].target, + display_order: sectionDisplayOrder, + }); +} + +const insert = db.prepare(` + INSERT INTO roadmap_items (title, description, status, target, display_order) + VALUES (?,?,?,?,?) +`); + +const tx = db.transaction(() => { + for (const it of items) { + insert.run(it.title, it.description, it.status, it.target, it.display_order); + } +}); +tx(); + +console.log(` seeded ${items.length} roadmap items from content/roadmap.md`); +db.close();