mirror of
https://github.com/prdlk/cv.git
synced 2026-08-02 17:31:41 +00:00
init(app): Setup client package for web api specific logic
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* Synchronize the YAML corpus under ../docs into the local D1 database.
|
||||
*
|
||||
* Usage: bun scripts/sync.ts [--remote]
|
||||
*
|
||||
* Applies migrations, regenerates seed.generated.sql from the YAML files,
|
||||
* and executes it against D1 (local by default).
|
||||
*/
|
||||
import { execSync } from "node:child_process";
|
||||
import { readFileSync, readdirSync, writeFileSync } from "node:fs";
|
||||
import { basename, join } from "node:path";
|
||||
import yaml from "js-yaml";
|
||||
|
||||
const APP = join(import.meta.dirname, "..");
|
||||
const DOCS = join(APP, "..", "docs");
|
||||
const TARGET = process.argv.includes("--remote") ? "--remote" : "--local";
|
||||
|
||||
// ---------- helpers ----------
|
||||
|
||||
const sql = (v: string | number | null | undefined): string =>
|
||||
v == null ? "NULL" : typeof v === "number" ? String(v) : `'${v.replace(/'/g, "''")}'`;
|
||||
|
||||
/** js-yaml parses ISO dates into Date objects; normalize everything to strings. */
|
||||
function iso(v: unknown): string | null {
|
||||
if (v == null) return null;
|
||||
if (v instanceof Date) return v.toISOString().slice(0, 10);
|
||||
return String(v);
|
||||
}
|
||||
|
||||
/** Split a `description` block scalar into its "- " bullets (with wrapped-line continuation). */
|
||||
function bulletsOf(desc: unknown): string[] {
|
||||
if (typeof desc !== "string") return [];
|
||||
const out: string[] = [];
|
||||
for (const line of desc.split("\n")) {
|
||||
const t = line.trim();
|
||||
if (t.startsWith("- ")) out.push(t.slice(2).trim());
|
||||
else if (t && out.length > 0) out[out.length - 1] += ` ${t}`;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function loadYaml(path: string): Record<string, unknown> {
|
||||
return yaml.load(readFileSync(path, "utf8")) as Record<string, unknown>;
|
||||
}
|
||||
|
||||
// ---------- build statements ----------
|
||||
|
||||
const stmts: string[] = [
|
||||
"DELETE FROM profile;",
|
||||
"DELETE FROM summaries;",
|
||||
"DELETE FROM experience;",
|
||||
"DELETE FROM projects;",
|
||||
"DELETE FROM bullets;",
|
||||
"DELETE FROM item_skills;",
|
||||
"DELETE FROM skills;",
|
||||
"DELETE FROM education;",
|
||||
"DELETE FROM honors;",
|
||||
"DELETE FROM organizations;",
|
||||
];
|
||||
|
||||
// about/profile.yml
|
||||
{
|
||||
const p = loadYaml(join(DOCS, "about/profile.yml"));
|
||||
stmts.push(
|
||||
`INSERT INTO profile (id, full_name, email, phone, website, linkedin, github, twitter) VALUES (1, ${sql(
|
||||
String(p["full-name"]),
|
||||
)}, ${sql(iso(p.email))}, ${sql(iso(p.phone))}, ${sql(iso(p.website))}, ${sql(
|
||||
iso(p.linkedin),
|
||||
)}, ${sql(iso(p.github))}, ${sql(iso(p.twitter))});`,
|
||||
);
|
||||
for (const s of (p.summary as { role: string; summary: string }[] | undefined) ?? []) {
|
||||
stmts.push(
|
||||
`INSERT INTO summaries (role, summary) VALUES (${sql(s.role)}, ${sql(s.summary.trim())});`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// about/education.yml
|
||||
{
|
||||
const e = loadYaml(join(DOCS, "about/education.yml"));
|
||||
stmts.push(
|
||||
`INSERT INTO education (id, school, degree, field, start_date, end_date, activities) VALUES (1, ${sql(
|
||||
iso(e.school),
|
||||
)}, ${sql(iso(e.degree))}, ${sql(iso(e.field))}, ${sql(iso(e.start))}, ${sql(
|
||||
iso(e.end),
|
||||
)}, ${sql(typeof e.activities === "string" ? e.activities.trim() : null)});`,
|
||||
);
|
||||
for (const skill of (e.skills as string[] | undefined) ?? []) {
|
||||
stmts.push(
|
||||
`INSERT INTO item_skills (parent_type, parent_slug, skill) VALUES ('education', 'education', ${sql(skill)});`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// about/honors.yml + organizations.yml (top-level lists)
|
||||
for (const h of yaml.load(readFileSync(join(DOCS, "about/honors.yml"), "utf8")) as Record<
|
||||
string,
|
||||
unknown
|
||||
>[]) {
|
||||
stmts.push(
|
||||
`INSERT INTO honors (title, date, institution) VALUES (${sql(iso(h.title))}, ${sql(
|
||||
iso(h.date),
|
||||
)}, ${sql(iso(h.institution))});`,
|
||||
);
|
||||
}
|
||||
for (const o of yaml.load(readFileSync(join(DOCS, "about/organizations.yml"), "utf8")) as Record<
|
||||
string,
|
||||
unknown
|
||||
>[]) {
|
||||
stmts.push(
|
||||
`INSERT INTO organizations (name, url, title) VALUES (${sql(iso(o.name))}, ${sql(
|
||||
iso(o.url),
|
||||
)}, ${sql(iso(o.title))});`,
|
||||
);
|
||||
}
|
||||
|
||||
// skills/languages.yml (tiered map)
|
||||
{
|
||||
const tiers = loadYaml(join(DOCS, "skills/languages.yml")) as Record<string, string[]>;
|
||||
let pos = 0;
|
||||
for (const tier of ["expert", "strong", "familiar"]) {
|
||||
for (const name of tiers[tier] ?? []) {
|
||||
stmts.push(
|
||||
`INSERT INTO skills (name, category, tier, position) VALUES (${sql(name)}, 'language', ${sql(tier)}, ${pos++});`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// skills/frameworks.yml (flat list; group headers live in YAML comments)
|
||||
{
|
||||
let grp: string | null = null;
|
||||
let pos = 0;
|
||||
for (const line of readFileSync(join(DOCS, "skills/frameworks.yml"), "utf8").split("\n")) {
|
||||
const t = line.trim();
|
||||
if (t.startsWith("#")) grp = t.replace(/^#\s*/, "");
|
||||
else if (t.startsWith("- "))
|
||||
stmts.push(
|
||||
`INSERT INTO skills (name, category, grp, position) VALUES (${sql(t.slice(2).trim())}, 'framework', ${sql(grp)}, ${pos++});`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// skills/methodology.yml (flat list)
|
||||
{
|
||||
const items = yaml.load(readFileSync(join(DOCS, "skills/methodology.yml"), "utf8")) as string[];
|
||||
items.forEach((name, pos) =>
|
||||
stmts.push(
|
||||
`INSERT INTO skills (name, category, position) VALUES (${sql(name)}, 'methodology', ${pos});`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// experience/*.yml
|
||||
for (const file of readdirSync(join(DOCS, "experience")).filter((f) => /\.ya?ml$/.test(f)).sort()) {
|
||||
const slug = file.replace(/\.ya?ml$/, "");
|
||||
const e = loadYaml(join(DOCS, "experience", file));
|
||||
stmts.push(
|
||||
`INSERT INTO experience (slug, title, company, start_date, end_date, location, profile_heading) VALUES (${sql(
|
||||
slug,
|
||||
)}, ${sql(iso(e.title))}, ${sql(iso(e.company))}, ${sql(iso(e.start))}, ${sql(
|
||||
iso(e.end),
|
||||
)}, ${sql(iso(e.location))}, ${sql(iso(e["profile-heading"]))});`,
|
||||
);
|
||||
bulletsOf(e.description).forEach((text, position) =>
|
||||
stmts.push(
|
||||
`INSERT INTO bullets (parent_type, parent_slug, position, text) VALUES ('experience', ${sql(slug)}, ${position}, ${sql(text)});`,
|
||||
),
|
||||
);
|
||||
for (const skill of (e.skills as string[] | undefined) ?? []) {
|
||||
stmts.push(
|
||||
`INSERT INTO item_skills (parent_type, parent_slug, skill) VALUES ('experience', ${sql(slug)}, ${sql(skill)});`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// projects/*.yml
|
||||
for (const file of readdirSync(join(DOCS, "projects")).filter((f) => /\.ya?ml$/.test(f)).sort()) {
|
||||
const slug = file.replace(/\.ya?ml$/, "");
|
||||
const p = loadYaml(join(DOCS, "projects", file));
|
||||
stmts.push(
|
||||
`INSERT INTO projects (slug, title, start_date, end_date, url, associated_with) VALUES (${sql(
|
||||
slug,
|
||||
)}, ${sql(iso(p.title))}, ${sql(iso(p.start))}, ${sql(iso(p.end))}, ${sql(
|
||||
iso(p["project-url"]),
|
||||
)}, ${sql(typeof p["associated-with"] === "string" ? basename(p["associated-with"]).replace(/\.ya?ml$/, "") : null)});`,
|
||||
);
|
||||
bulletsOf(p.description).forEach((text, position) =>
|
||||
stmts.push(
|
||||
`INSERT INTO bullets (parent_type, parent_slug, position, text) VALUES ('project', ${sql(slug)}, ${position}, ${sql(text)});`,
|
||||
),
|
||||
);
|
||||
for (const skill of (p.skills as string[] | undefined) ?? []) {
|
||||
stmts.push(
|
||||
`INSERT INTO item_skills (parent_type, parent_slug, skill) VALUES ('project', ${sql(slug)}, ${sql(skill)});`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- execute ----------
|
||||
|
||||
const seedPath = join(APP, "seed.generated.sql");
|
||||
writeFileSync(seedPath, `${stmts.join("\n")}\n`);
|
||||
console.log(`Wrote ${stmts.length} statements to seed.generated.sql`);
|
||||
|
||||
execSync(`bunx wrangler d1 migrations apply cv-tailor ${TARGET}`, { cwd: APP, stdio: "inherit" });
|
||||
execSync(`bunx wrangler d1 execute cv-tailor ${TARGET} --file seed.generated.sql`, {
|
||||
cwd: APP,
|
||||
stdio: "inherit",
|
||||
});
|
||||
console.log("Sync complete.");
|
||||
Reference in New Issue
Block a user