Initial Commit
This commit is contained in:
236
scripts/delib-monorepo-refs.ts
Normal file
236
scripts/delib-monorepo-refs.ts
Normal file
@@ -0,0 +1,236 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
/**
|
||||
* delib-monorepo-refs — scrub genesis / monorepo / stage / OpenSpec metadata
|
||||
* from individual library README.md files so they read as standalone packages.
|
||||
*
|
||||
* Usage:
|
||||
* bun scripts/delib-monorepo-refs.ts # rewrite
|
||||
* bun scripts/delib-monorepo-refs.ts --dry-run # preview
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { CONFIG } from "./lib/config.js";
|
||||
import { walkMd } from "./lib/doc-tree.js";
|
||||
|
||||
const ROOT = CONFIG.paths.root;
|
||||
const PACKAGES_ROOT = join(ROOT, CONFIG.paths.packagesRoot);
|
||||
|
||||
const REPLACEMENTS: { pattern: RegExp; replacement: string; note?: string }[] = [
|
||||
// 1. Strip OpenSpec link blocks (won't resolve on npm)
|
||||
{
|
||||
pattern: /\n?<!-- begin:auto-spec-link -->[\s\S]*?<!-- end:auto-spec-link -->\n?/g,
|
||||
replacement: "\n",
|
||||
note: "OpenSpec block",
|
||||
},
|
||||
|
||||
// 2. Stand-alone "## OpenSpec" heading + following spec-link line
|
||||
{
|
||||
pattern: /\n?## OpenSpec\n\nCanonical capability spec: `?[^`\n]+`?\.[ \t]*\n?/g,
|
||||
replacement: "\n",
|
||||
note: "OpenSpec heading",
|
||||
},
|
||||
|
||||
// 3. Remove Stage-N.N prefix from H1 title lines
|
||||
{
|
||||
pattern:
|
||||
/^(# @suaveplan\/[\w-]+)\s+-\s+Stage-?\d+(?:\.\d+)?\s+/gim,
|
||||
replacement: "$1 — ",
|
||||
note: "Stage in H1",
|
||||
},
|
||||
|
||||
// 4. Remove Stage-N.N at start of sentences / lines (common prose prefix)
|
||||
{
|
||||
pattern:
|
||||
/([.\n]|^)\s*Stage-?\d+(?:\.\d+)?\s+(concrete|abstract|trivial|bridge|rolled|Better-Auth|contract|package|audit|fixed-principal|session resolver|JWT|service-to-service|in-memory|reference|HTTP|WebSocket|in-process|fixed-Principal|concrete Hono|concrete GraphQL|concrete WebAssembly|concrete Apollo|concrete static|concrete tenant|concrete database|concrete event|concrete job|concrete logger|concrete manifest|concrete migration|concrete OpenAPI|concrete rate-limit|concrete router|concrete saga|concrete sandbox|concrete secrets|concrete SSE|concrete WS|concrete worker|concrete WS broker|concrete WS Bun)\b/gi,
|
||||
replacement: "$1$2",
|
||||
note: "Stage prefix",
|
||||
},
|
||||
|
||||
// 5. Remove orphaned "Stage-N.N" that stands alone mid-sentence
|
||||
{
|
||||
pattern: /\bStage-?\d+(?:\.\d+)?\b/gi,
|
||||
replacement: "",
|
||||
note: "orphaned Stage",
|
||||
},
|
||||
|
||||
// 6. "Genesis Stage N — …" sentences
|
||||
{
|
||||
pattern:
|
||||
/\bGenesis Stage \d+(?:\.\d+)?[^.]*\.\s*/gi,
|
||||
replacement: "",
|
||||
note: "Genesis Stage sentence",
|
||||
},
|
||||
|
||||
// 7. "Genesis-wave package …" sentences
|
||||
{
|
||||
pattern: /\bGenesis-wave package[^.]*\.\s*/gi,
|
||||
replacement: "",
|
||||
note: "Genesis-wave sentence",
|
||||
},
|
||||
|
||||
// 8. "Per design doc §N.N, genesis ships …" sentences
|
||||
{
|
||||
pattern:
|
||||
/\bPer design doc §\d+(?:\.\d+)*,\s+genesis\s+[^.]*\.\s*/gi,
|
||||
replacement: "",
|
||||
note: "design-doc genesis sentence",
|
||||
},
|
||||
|
||||
// 9. "the Suaveplan monorepo" / "for the Suaveplan monorepo"
|
||||
{
|
||||
pattern: /\bfor the Suaveplan monorepo\b/gi,
|
||||
replacement: "for Suaveplan",
|
||||
note: "monorepo ref",
|
||||
},
|
||||
{
|
||||
pattern: /\bthe Suaveplan monorepo\b/gi,
|
||||
replacement: "Suaveplan",
|
||||
note: "monorepo ref",
|
||||
},
|
||||
|
||||
// 10. "the genesis monorepo"
|
||||
{
|
||||
pattern: /\bthe genesis monorepo\b/gi,
|
||||
replacement: "Suaveplan",
|
||||
note: "genesis monorepo",
|
||||
},
|
||||
|
||||
// 11. "genesis `@suaveplan/...`" -> just the package name
|
||||
{
|
||||
pattern: /\bgenesis\s+(`@suaveplan\/[^`]+`)/gi,
|
||||
replacement: "$1",
|
||||
note: "genesis pkg prefix",
|
||||
},
|
||||
|
||||
// 12. "for the `@suaveplan/api` plugin foundation"
|
||||
{
|
||||
pattern:
|
||||
/\bfor the `@suaveplan\/api` plugin foundation\b/gi,
|
||||
replacement: "for `@suaveplan/api`",
|
||||
note: "plugin foundation",
|
||||
},
|
||||
{
|
||||
pattern:
|
||||
/\bthe `@suaveplan\/api` plugin foundation\b/gi,
|
||||
replacement: "`@suaveplan/api`",
|
||||
note: "plugin foundation",
|
||||
},
|
||||
{
|
||||
pattern: /\bplugin foundation\b/gi,
|
||||
replacement: "plugin system",
|
||||
note: "plugin foundation",
|
||||
},
|
||||
|
||||
// 13. Loose "genesis" when clearly referring to the project (not a name)
|
||||
{
|
||||
pattern: /\bgenesis (fix|package|design|crypto|wave|monorepo)\b/gi,
|
||||
replacement: "$1",
|
||||
note: "genesis adjective",
|
||||
},
|
||||
|
||||
// 14. "openspec/changes/..." paths in prose
|
||||
{
|
||||
pattern: /`?\.\.\/\.\.\/\.\.\/\.\.\/openspec\/[^`\n]+`?/g,
|
||||
replacement: "the spec",
|
||||
note: "openspec path",
|
||||
},
|
||||
|
||||
// 15. "OpenSpec change" / "OpenSpec-tracked"
|
||||
{
|
||||
pattern: /\bOpenSpec change\b/gi,
|
||||
replacement: "spec change",
|
||||
note: "OpenSpec change",
|
||||
},
|
||||
{
|
||||
pattern: /\bOpenSpec-tracked\b/gi,
|
||||
replacement: "tracked",
|
||||
note: "OpenSpec-tracked",
|
||||
},
|
||||
|
||||
// 16. "of the umbrella plan (openspec/changes/...)"
|
||||
{
|
||||
pattern:
|
||||
/\bof the umbrella plan\s*\([^)]+openspec[^)]+\)\b/gi,
|
||||
replacement: "of the design",
|
||||
note: "umbrella plan",
|
||||
},
|
||||
|
||||
// 17. "Stage N of the umbrella plan"
|
||||
{
|
||||
pattern:
|
||||
/\bStage \d+(?:\.\d+)?\s+of the umbrella plan\b/gi,
|
||||
replacement: "the design",
|
||||
note: "Stage umbrella plan",
|
||||
},
|
||||
];
|
||||
|
||||
function scrub(content: string): { next: string; hits: string[] } {
|
||||
let next = content;
|
||||
const hits: string[] = [];
|
||||
|
||||
for (const { pattern, replacement, note } of REPLACEMENTS) {
|
||||
const before = next;
|
||||
next = next.replace(pattern, replacement);
|
||||
if (next !== before && note) {
|
||||
hits.push(note);
|
||||
}
|
||||
}
|
||||
|
||||
// Collapse multiple blank lines left behind
|
||||
next = next.replace(/\n{3,}/g, "\n\n");
|
||||
|
||||
return { next, hits };
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
const dryRun = process.argv.slice(2).includes("--dry-run");
|
||||
|
||||
const readmes = walkMd(PACKAGES_ROOT, {
|
||||
ignoreDirs: ["node_modules", "dist", "build", "coverage", ".turbo"],
|
||||
}).filter((p) => basename(p) === "README.md");
|
||||
|
||||
let changed = 0;
|
||||
let unchanged = 0;
|
||||
const report: string[] = [];
|
||||
|
||||
for (const readmePath of readmes) {
|
||||
const content = readFileSync(readmePath, "utf-8");
|
||||
const { next, hits } = scrub(content);
|
||||
if (next === content) {
|
||||
unchanged++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const rel = readmePath.replace(ROOT + "/", "");
|
||||
const uniqueHits = [...new Set(hits)];
|
||||
report.push(`${rel} (${uniqueHits.join(", ")})`);
|
||||
|
||||
if (!dryRun) {
|
||||
writeFileSync(readmePath, next);
|
||||
}
|
||||
changed++;
|
||||
}
|
||||
|
||||
console.log("");
|
||||
console.log(`✏️ Changed: ${changed}`);
|
||||
console.log(`⏭️ Unchanged: ${unchanged}`);
|
||||
|
||||
if (report.length > 0) {
|
||||
console.log("");
|
||||
console.log("Files touched:");
|
||||
for (const line of report) console.log(` ${line}`);
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
console.log("");
|
||||
console.log("(Dry-run — no files were written.)");
|
||||
}
|
||||
}
|
||||
|
||||
function basename(p: string): string {
|
||||
return p.slice(p.lastIndexOf("/") + 1);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user