-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathsplit-changelog.js
More file actions
86 lines (71 loc) · 2.5 KB
/
split-changelog.js
File metadata and controls
86 lines (71 loc) · 2.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
const fs = require('fs');
const path = require('path');
const changelogPath = path.resolve(__dirname, '../../CHANGELOG.md');
const blogDir = path.resolve(__dirname, '../src/content/blog');
// Ensure blog directory exists
fs.mkdirSync(blogDir, { recursive: true });
// Clear existing generated blog posts
const existing = fs.readdirSync(blogDir).filter(f => f.endsWith('.md'));
for (const file of existing) {
fs.unlinkSync(path.join(blogDir, file));
}
const changelog = fs.readFileSync(changelogPath, 'utf-8');
// Split on ## [X.Y.Z] headers
// Matches: ## [1.10.4] - 2026-03-14 — AST-based LSP Formatting
const versionRegex = /^## \[(\d+\.\d+\.\d+)\]\s*-?\s*(\d{4}-\d{2}-\d{2})?\s*(?:—\s*(.*))?$/;
const lines = changelog.split('\n');
const releases = [];
let current = null;
for (const line of lines) {
const match = line.match(versionRegex);
if (match) {
if (current) {
releases.push(current);
}
current = {
version: match[1],
date: match[2] || '',
subtitle: (match[3] || '').trim(),
body: [],
};
} else if (line.match(/^## \[Unreleased\]/i)) {
// End any current section, skip Unreleased
if (current) {
releases.push(current);
}
current = null;
} else if (current) {
current.body.push(line);
}
}
if (current) {
releases.push(current);
}
let count = 0;
for (const release of releases) {
const { version, date, subtitle } = release;
const body = release.body.join('\n').trim();
// Skip empty sections
if (!body) continue;
const slug = `v${version.replace(/\./g, '-')}`;
const titleParts = [`v${version}`];
if (subtitle) titleParts.push(`\u2014 ${subtitle}`);
const title = titleParts.join(' ');
// Extract first non-empty, non-heading, non-list line as description
const descLine = release.body.find(l => l.trim() && !l.startsWith('#') && !l.startsWith('---') && !l.startsWith('- '));
const rawDesc = (descLine || subtitle || `GoSQLX v${version} release`).replace(/\*\*/g, '').replace(/`/g, '').trim();
// YAML-safe: escape quotes, strip trailing truncated quotes, limit length
const description = rawDesc.slice(0, 160).replace(/"/g, "'").replace(/'$/, '');
const frontmatter = [
'---',
`title: "${title}"`,
`date: "${date}"`,
`version: "${version}"`,
`description: "${description}"`,
'---',
].join('\n');
const content = `${frontmatter}\n\n${body}\n`;
fs.writeFileSync(path.join(blogDir, `${slug}.md`), content);
count++;
}
console.log(`Generated ${count} blog posts from CHANGELOG.md`);