Skip to content

Commit 21303b9

Browse files
author
DOS AI Bot
committed
feat: sync changelog.md to Supabase on push
1 parent 69a276b commit 21303b9

1 file changed

Lines changed: 123 additions & 0 deletions

File tree

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
name: Sync Changelog to Supabase
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'changelog.md'
8+
workflow_dispatch:
9+
10+
env:
11+
SUPABASE_URL: https://gulptwduchsjcsbndmua.supabase.co
12+
13+
jobs:
14+
sync:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Parse changelog.md and sync to Supabase
20+
run: |
21+
node << 'SCRIPT'
22+
const fs = require('fs');
23+
const https = require('https');
24+
25+
const content = fs.readFileSync('changelog.md', 'utf8');
26+
const lines = content.split('\n');
27+
28+
const typeMap = {
29+
'New': 'feature',
30+
'Improved': 'improvement',
31+
'Fixed': 'fix',
32+
'Breaking': 'breaking',
33+
};
34+
const productMap = {
35+
'Dashboard': 'dashboard',
36+
'Agents': 'agents',
37+
'Integrations': 'integrations',
38+
'Platform': 'platform',
39+
'DOSClaw': 'dosclaw',
40+
'API Gateway': 'gateway',
41+
'DOSafe': 'dosafe',
42+
'Inference': 'inference',
43+
};
44+
45+
const entries = [];
46+
let currentDate = null;
47+
let currentType = null;
48+
49+
for (const line of lines) {
50+
// ## March 27, 2026
51+
const dateMatch = line.match(/^## (.+)$/);
52+
if (dateMatch) {
53+
const d = new Date(dateMatch[1].trim());
54+
currentDate = isNaN(d) ? null : d.toISOString().split('T')[0];
55+
currentType = null;
56+
continue;
57+
}
58+
59+
// ### New / Fixed / Improved / Breaking
60+
const typeMatch = line.match(/^### (.+)$/);
61+
if (typeMatch) {
62+
currentType = typeMap[typeMatch[1].trim()] || null;
63+
continue;
64+
}
65+
66+
// - `Product` **Title** — Description
67+
const entryMatch = line.match(/^- `([^`]+)` \*\*([^*]+)\*\* — (.+)$/);
68+
if (entryMatch && currentDate && currentType) {
69+
const product = productMap[entryMatch[1]] || entryMatch[1].toLowerCase();
70+
entries.push({
71+
date: currentDate,
72+
product,
73+
type: currentType,
74+
title: entryMatch[2].trim(),
75+
description: entryMatch[3].trim(),
76+
source: 'auto',
77+
});
78+
}
79+
}
80+
81+
if (entries.length === 0) {
82+
console.log('No entries parsed');
83+
process.exit(0);
84+
}
85+
86+
console.log(`Parsed ${entries.length} entries`);
87+
88+
// Call Supabase RPC
89+
const body = JSON.stringify({
90+
p_entries: entries,
91+
p_write_key: process.env.CHANGELOG_WRITE_KEY,
92+
});
93+
94+
const url = new URL(`${process.env.SUPABASE_URL}/rest/v1/rpc/insert_changelog`);
95+
const options = {
96+
hostname: url.hostname,
97+
path: url.pathname,
98+
method: 'POST',
99+
headers: {
100+
'Content-Type': 'application/json',
101+
'apikey': process.env.SUPABASE_ANON_KEY,
102+
'Authorization': `Bearer ${process.env.SUPABASE_ANON_KEY}`,
103+
'Content-Profile': 'dosai',
104+
'Content-Length': Buffer.byteLength(body),
105+
},
106+
};
107+
108+
const req = https.request(options, (res) => {
109+
let data = '';
110+
res.on('data', (chunk) => data += chunk);
111+
res.on('end', () => {
112+
console.log(`Supabase response (${res.statusCode}):`, data);
113+
process.exit(res.statusCode >= 200 && res.statusCode < 300 ? 0 : 1);
114+
});
115+
});
116+
req.on('error', (e) => { console.error(e); process.exit(1); });
117+
req.write(body);
118+
req.end();
119+
SCRIPT
120+
env:
121+
SUPABASE_URL: ${{ env.SUPABASE_URL }}
122+
SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
123+
CHANGELOG_WRITE_KEY: ${{ secrets.CHANGELOG_WRITE_KEY }}

0 commit comments

Comments
 (0)