#!/usr/bin/env node
/**
* Post release notification to Discord
*
* Usage:
* node scripts/discord-release-notify.js v7.4.2
* node scripts/discord-release-notify.js v7.4.2 "Custom release notes"
*
* Requires DISCORD_UPDATES_WEBHOOK in .env file
*/
import { execSync } from 'child_process';
import { readFileSync, existsSync } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const projectRoot = resolve(__dirname, '..');
function loadEnv() {
const envPath = resolve(projectRoot, '.env');
if (!existsSync(envPath)) {
console.error('β .env file not found');
process.exit(1);
}
const envContent = readFileSync(envPath, 'utf-8');
const webhookMatch = envContent.match(/DISCORD_UPDATES_WEBHOOK=(.+)/);
if (!webhookMatch) {
console.error('β DISCORD_UPDATES_WEBHOOK not found in .env');
process.exit(1);
}
return webhookMatch[1].trim();
}
function getReleaseNotes(version) {
try {
const notes = execSync(`gh release view ${version} --json body --jq '.body'`, {
encoding: 'utf-8',
cwd: projectRoot,
}).trim();
return notes;
} catch {
return null;
}
}
function cleanNotes(notes) {
// Remove Claude Code footer and clean up
return notes
.replace(/π€ Generated with \[Claude Code\].*$/s, '')
.replace(/---\n*$/s, '')
.trim();
}
function truncate(text, maxLength) {
if (text.length <= maxLength) return text;
return text.slice(0, maxLength - 3) + '...';
}
async function postToDiscord(webhookUrl, version, notes) {
const cleanedNotes = notes ? cleanNotes(notes) : 'No release notes available.';
const repoUrl = 'https://github.com/nhevers/claude-recall';
const payload = {
embeds: [
{
title: `π claude-recall ${version} released`,
url: `${repoUrl}/releases/tag/${version}`,
description: truncate(cleanedNotes, 2000),
color: 0x7c3aed, // Purple
fields: [
{
name: 'π¦ Install',
value: 'Update via Claude Code plugin marketplace',
inline: true,
},
{
name: 'π Docs',
value: '[docs.claude-recall.ai](https://docs.claude-recall.ai)',
inline: true,
},
],
footer: {
text: 'claude-recall β’ Persistent memory for Claude Code',
},
timestamp: new Date().toISOString(),
},
],
};
const response = await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Discord API error: ${response.status} - ${errorText}`);
}
return true;
}
async function main() {
const version = process.argv[2];
const customNotes = process.argv[3];
if (!version) {
console.error('Usage: node scripts/discord-release-notify.js <version> [notes]');
console.error('Example: node scripts/discord-release-notify.js v7.4.2');
process.exit(1);
}
console.log(`π£ Posting release notification for ${version}...`);
const webhookUrl = loadEnv();
const notes = customNotes || getReleaseNotes(version);
if (!notes && !customNotes) {
console.warn('β οΈ Could not fetch release notes from GitHub, proceeding without them');
}
try {
await postToDiscord(webhookUrl, version, notes);
console.log('β
Discord notification sent successfully!');
} catch (error) {
console.error('β Failed to send Discord notification:', error.message);
process.exit(1);
}
}
main();