304 lines
14 KiB
YAML
304 lines
14 KiB
YAML
name: Discord — Release Merge Notification
|
|
|
|
on:
|
|
pull_request:
|
|
types:
|
|
- closed
|
|
branches:
|
|
- release
|
|
|
|
jobs:
|
|
notify:
|
|
# Solo cuando el PR fue efectivamente mergeado (no cerrado sin merge)
|
|
if: github.event.pull_request.merged == true
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write # necesario para pushear CHANGELOG.md
|
|
|
|
steps:
|
|
- name: Checkout full history
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
ref: release
|
|
|
|
- name: Configure git identity
|
|
run: |
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
|
|
- name: Generate CHANGELOG entry, commit, then send Discord announcement
|
|
uses: actions/github-script@v7
|
|
env:
|
|
DAVEAGENT_API_KEY: ${{ secrets.DAVEAGENT_API_KEY }}
|
|
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}
|
|
DISCORD_CHANNEL_ID: ${{ secrets.DISCORD_CHANNEL_ID }}
|
|
with:
|
|
script: |
|
|
const { execSync } = require('child_process');
|
|
const fs = require('fs');
|
|
const today = new Date().toISOString().slice(0, 10);
|
|
|
|
// ── Helpers ──────────────────────────────────────────────────────
|
|
function git(cmd) {
|
|
try {
|
|
return execSync(`git ${cmd}`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
} catch { return ''; }
|
|
}
|
|
|
|
async function callDeepSeek(prompt, maxTokens = 800, temperature = 0.3) {
|
|
const apiKey = process.env.DAVEAGENT_API_KEY;
|
|
if (!apiKey) throw new Error('DAVEAGENT_API_KEY not set');
|
|
const res = await fetch('https://api.deepseek.com/v1/chat/completions', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${apiKey}`,
|
|
},
|
|
body: JSON.stringify({
|
|
model: 'deepseek-chat',
|
|
max_tokens: maxTokens,
|
|
temperature,
|
|
messages: [{ role: 'user', content: prompt }],
|
|
}),
|
|
});
|
|
if (!res.ok) {
|
|
const err = await res.text();
|
|
throw new Error(`DeepSeek ${res.status}: ${err}`);
|
|
}
|
|
const data = await res.json();
|
|
return (data.choices?.[0]?.message?.content ?? '').trim();
|
|
}
|
|
|
|
// ── Version ──────────────────────────────────────────────────────
|
|
let version = '2.0.1';
|
|
try {
|
|
const pkg = JSON.parse(fs.readFileSync('frontend/package.json', 'utf8'));
|
|
version = pkg.version ?? '2.0.1';
|
|
} catch {}
|
|
console.log(`Version: v${version}`);
|
|
|
|
// ── Rango exacto del PR mergeado ──────────────────────────────────
|
|
// base.sha = tip de release ANTES del merge
|
|
// head.sha = tip de la rama fuente (master u otra)
|
|
const pr = context.payload.pull_request;
|
|
const baseSha = pr.base.sha;
|
|
const headSha = pr.head.sha;
|
|
const sourceBranch = pr.head.ref; // ej: "master"
|
|
const prTitle = pr.title ?? '';
|
|
const prBody = pr.body ?? '';
|
|
|
|
console.log(`PR: #${pr.number} "${prTitle}"`);
|
|
console.log(`Range: ${baseSha.slice(0,7)}..${headSha.slice(0,7)} (${sourceBranch} → release)`);
|
|
|
|
// Commits que entraron (con hash + subject + body + autor)
|
|
const commits = git(
|
|
`log ${baseSha}..${headSha} --pretty=format:"%h||%s||%b||%an|||" --no-merges`
|
|
) || git(`log -30 --pretty=format:"%h||%s||%b||%an|||" --no-merges`);
|
|
|
|
// Lineas simples para el anuncio Discord
|
|
const commitLines = git(
|
|
`log ${baseSha}..${headSha} --pretty=format:"%h %s" --no-merges`
|
|
) || git(`log -20 --pretty=format:"%h %s" --no-merges`);
|
|
|
|
const changedFiles = git(`diff --name-only ${baseSha}..${headSha}`)
|
|
.split('\n').filter(Boolean);
|
|
|
|
console.log(`Commits found: ${commitLines.split('\n').filter(Boolean).length}`);
|
|
console.log(`Files changed: ${changedFiles.length}`);
|
|
|
|
// ──────────────────────────────────────────────────────────────────
|
|
// FASE 1 — Generar entrada de CHANGELOG con DeepSeek
|
|
// ──────────────────────────────────────────────────────────────────
|
|
const changelogPrompt = `You are generating a CHANGELOG entry for version ${version} of Velxio.
|
|
|
|
Velxio is a fully local, open-source Arduino/RP2040 emulator and circuit simulator.
|
|
|
|
PR TITLE: ${prTitle}
|
|
PR DESCRIPTION: ${prBody}
|
|
SOURCE BRANCH: ${sourceBranch}
|
|
|
|
GIT COMMITS (hash||subject||body||author):
|
|
${commits}
|
|
|
|
Generate a changelog entry using EXACTLY this format:
|
|
|
|
## [${version}] - ${today}
|
|
|
|
### Added
|
|
- New user-facing features
|
|
|
|
### Changed
|
|
- Changes in existing functionality
|
|
|
|
### Fixed
|
|
- Bug fixes
|
|
|
|
RULES:
|
|
1. Group commits by category (Added, Changed, Fixed, Performance, Removed, Security)
|
|
2. Write in past tense, user-facing language
|
|
3. Skip trivial commits (formatting, typos, version bumps)
|
|
4. Combine related commits into single entries
|
|
5. Remove commit hashes and author names
|
|
6. Only include sections that have real content
|
|
7. Output ONLY the changelog entry block, nothing else`;
|
|
|
|
console.log('Generating CHANGELOG entry...');
|
|
const changelogEntry = await callDeepSeek(changelogPrompt, 800, 0.3);
|
|
|
|
// ── Actualizar CHANGELOG.md ───────────────────────────────────────
|
|
const changelogPath = 'CHANGELOG.md';
|
|
let changelogContent;
|
|
|
|
if (!fs.existsSync(changelogPath)) {
|
|
changelogContent = '# Changelog\n\n'
|
|
+ 'All notable changes to Velxio will be documented in this file.\n'
|
|
+ 'The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).\n\n'
|
|
+ changelogEntry + '\n\n'
|
|
+ `[${version}]: https://github.com/davidmonterocrespo24/velxio/releases/tag/v${version}\n`;
|
|
} else {
|
|
const existing = fs.readFileSync(changelogPath, 'utf8');
|
|
const lines = existing.split('\n');
|
|
|
|
// Insertar antes del primer ## [ o despues del header
|
|
let insertIdx = lines.findIndex(l => l.startsWith('## ['));
|
|
if (insertIdx === -1) {
|
|
insertIdx = lines.findIndex((l, i) => i > 3 && l.trim() === '') + 1;
|
|
}
|
|
|
|
lines.splice(insertIdx, 0, changelogEntry, '');
|
|
|
|
const linkLine = `[${version}]: https://github.com/davidmonterocrespo24/velxio/releases/tag/v${version}`;
|
|
if (!existing.includes(linkLine)) lines.push(linkLine);
|
|
|
|
changelogContent = lines.join('\n');
|
|
}
|
|
|
|
fs.writeFileSync(changelogPath, changelogContent, 'utf8');
|
|
console.log('CHANGELOG.md updated');
|
|
|
|
// ── Commit y push del CHANGELOG ───────────────────────────────────
|
|
try {
|
|
execSync('git add CHANGELOG.md', { stdio: 'inherit' });
|
|
execSync(
|
|
`git commit -m "docs: update CHANGELOG for v${version} [skip ci]"`,
|
|
{ stdio: 'inherit' }
|
|
);
|
|
execSync('git push origin release', { stdio: 'inherit' });
|
|
console.log('CHANGELOG.md committed and pushed');
|
|
} catch (e) {
|
|
console.log('Nothing to commit or push failed:', e.message);
|
|
}
|
|
|
|
// ──────────────────────────────────────────────────────────────────
|
|
// FASE 2 — Generar anuncio Discord usando el CHANGELOG recien generado
|
|
// ──────────────────────────────────────────────────────────────────
|
|
const announcementPrompt = `You are writing a Discord announcement for Velxio v${version}.
|
|
|
|
Velxio is a fully local, open-source Arduino/RP2040 emulator and circuit simulator that runs in the browser.
|
|
Website: https://velxio.dev/
|
|
GitHub: https://github.com/davidmonterocrespo24/velxio
|
|
|
|
CRITICAL: The announcement MUST be 1900 characters or less.
|
|
|
|
CHANGELOG for this version (use this as the source of truth):
|
|
${changelogEntry}
|
|
|
|
RECENT COMMITS (for additional context):
|
|
${commitLines}
|
|
|
|
STYLE EXAMPLES:
|
|
|
|
EXAMPLE 1:
|
|
v2.0.0 @everyone
|
|
|
|
Velxio just got a major upgrade. Here is what is new.
|
|
|
|
GROUND CHECK FOR LEDS
|
|
LEDs now require a proper cathode connection to GND. No more phantom lights without a complete circuit.
|
|
|
|
GENERIC OUTPUT COMPONENT PROTECTION
|
|
Any output component connected without a ground wire stays off. The simulator now enforces real circuit behavior for all components.
|
|
|
|
SSD1306 SPI MODE
|
|
The SSD1306 OLED display now supports both I2C and SPI. Switch protocols from the component property dialog.
|
|
|
|
Try it now: https://velxio.dev/
|
|
Full release details: https://github.com/davidmonterocrespo24/velxio/releases/tag/v2.0.0
|
|
|
|
EXAMPLE 2:
|
|
v2.0.1 @everyone
|
|
|
|
Quick fixes in this update:
|
|
|
|
Fixed LED staying on after simulation stops
|
|
Fixed wire color not persisting on reload
|
|
Fixed serial monitor scroll position resetting mid-output
|
|
|
|
Update your Docker image or open https://velxio.dev/
|
|
|
|
STYLE RULES:
|
|
- Start with version number and @everyone on the first line
|
|
- No emojis
|
|
- No markdown (no **, no ###)
|
|
- Use ALL CAPS for section headers when grouping multiple features
|
|
- Focus on USER BENEFITS, not implementation details
|
|
- Under 1900 characters
|
|
- End with link to GitHub release
|
|
- Write in English`;
|
|
|
|
console.log('Generating Discord announcement...');
|
|
let announcement = await callDeepSeek(announcementPrompt, 600, 0.7);
|
|
|
|
// Limpiar markdown residual
|
|
announcement = announcement
|
|
.replace(/###/g, '')
|
|
.replace(/\*\*/g, '')
|
|
.replace(/__/g, '')
|
|
.trim();
|
|
|
|
// Truncar si excede el limite
|
|
if (announcement.length > 1900) {
|
|
const cut = announcement.lastIndexOf('\n', 1900);
|
|
announcement = announcement.slice(0, cut > 1200 ? cut : 1900).trim();
|
|
}
|
|
|
|
console.log('='.repeat(70));
|
|
console.log('DISCORD ANNOUNCEMENT:');
|
|
console.log('='.repeat(70));
|
|
console.log(announcement);
|
|
console.log(`\nCharacters: ${announcement.length}/1900`);
|
|
|
|
// ──────────────────────────────────────────────────────────────────
|
|
// FASE 3 — Enviar a Discord via Bot API
|
|
// ──────────────────────────────────────────────────────────────────
|
|
const botToken = process.env.DISCORD_BOT_TOKEN;
|
|
const channelId = process.env.DISCORD_CHANNEL_ID;
|
|
if (!botToken || !channelId) {
|
|
core.setFailed('DISCORD_BOT_TOKEN or DISCORD_CHANNEL_ID secret not configured');
|
|
return;
|
|
}
|
|
|
|
const discordRes = await fetch(
|
|
`https://discord.com/api/v10/channels/${channelId}/messages`,
|
|
{
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bot ${botToken}`,
|
|
},
|
|
body: JSON.stringify({ content: announcement }),
|
|
}
|
|
);
|
|
|
|
if (!discordRes.ok) {
|
|
const err = await discordRes.text();
|
|
core.setFailed(`Discord ${discordRes.status}: ${err}`);
|
|
return;
|
|
}
|
|
|
|
console.log('='.repeat(70));
|
|
console.log(`Release v${version} announced on Discord`);
|
|
console.log(`PR #${pr.number} | Commits: ${commitLines.split('\n').filter(Boolean).length} | Files: ${changedFiles.length}`);
|