|
| 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