a reactive (signals based) hypermedia web framework (wip)
stormlightlabs.github.io/volt/
hypermedia
frontend
signals
1import path from "node:path";
2import { diffSections, extractHeadings, extractSections, hashMarkdown } from "./differ.js";
3import type { DocMetadata } from "./storage.js";
4import { getDocMetadata, updateDocMetadata } from "./storage.js";
5
6/**
7 * Track version for a generated documentation file
8 * Compares with previous version, calculates diff, bumps version, adds frontmatter
9 *
10 * @param filePath Absolute path to the documentation file
11 * @param content Generated markdown content (without frontmatter)
12 * @returns Content with frontmatter prepended
13 */
14export async function trackVersion(filePath: string, content: string): Promise<string> {
15 const docsDir = path.join(process.cwd(), "..", "docs");
16 const relPath = path.relative(docsDir, filePath);
17
18 const prevMeta = await getDocMetadata(relPath);
19
20 const newSections = extractSections(content);
21 const newHash = hashMarkdown(content);
22 const today = new Date().toISOString().split("T")[0];
23
24 if (!prevMeta) {
25 const initialMeta: DocMetadata = {
26 version: "1.0",
27 updated: today,
28 hash: newHash,
29 sections: extractHeadings(content),
30 history: [{ version: "1.0", date: today, hash: newHash, added: newSections.length, removed: 0, edited: 0 }],
31 };
32
33 await updateDocMetadata(relPath, initialMeta);
34 return addFrontmatter(content, "1.0", today);
35 }
36
37 if (prevMeta.hash === newHash) {
38 return addFrontmatter(content, prevMeta.version, prevMeta.updated);
39 }
40
41 const oldSections = extractSections(prevMeta.sections.map((h) => `${h}\n\nContent`).join("\n\n"));
42 const diff = diffSections(oldSections, newSections);
43
44 const newVersion = calculateVersionBump(diff, prevMeta.version);
45
46 const newMeta: DocMetadata = {
47 version: newVersion,
48 updated: today,
49 hash: newHash,
50 sections: extractHeadings(content),
51 history: [...prevMeta.history, {
52 version: newVersion,
53 date: today,
54 hash: newHash,
55 added: diff.added,
56 removed: diff.removed,
57 edited: diff.edited,
58 }],
59 };
60
61 await updateDocMetadata(relPath, newMeta);
62 return addFrontmatter(content, newVersion, today);
63}
64
65/**
66 * Calculate version bump based on diff
67 *
68 * Rules:
69 * - Any sections removed → Major bump
70 * - Any sections added → Major bump
71 * - ≥4 sections edited → Major bump
72 * - 1-3 sections edited → Minor bump
73 * - No changes → No bump
74 */
75function calculateVersionBump(diff: { added: number; removed: number; edited: number }, current: string): string {
76 const [major, minor] = current.split(".").map(Number);
77
78 if (diff.removed > 0) {
79 return `${major + 1}.0`;
80 }
81
82 if (diff.added > 0) {
83 return `${major + 1}.0`;
84 }
85
86 if (diff.edited >= 4) {
87 return `${major + 1}.0`;
88 }
89
90 if (diff.edited > 0) {
91 return `${major}.${minor + 1}`;
92 }
93
94 return current;
95}
96
97/**
98 * Add frontmatter to markdown content
99 */
100function addFrontmatter(content: string, version: string, date: string): string {
101 return `---
102version: ${version}
103updated: ${date}
104---
105
106${content}`;
107}