Files
davila7--claude-code-templates/api/claude-code-monitor/webhook.js
T
wehub-resource-sync bb5c75ce05
Component Security Validation / Security Audit (push) Has been cancelled
Deploy to Cloudflare Pages / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:58 +08:00

197 lines
5.9 KiB
JavaScript

// Vercel Function: Monitor de Claude Code Changelog
// Se activa con webhooks de NPM o puede ser llamado manualmente
import { neon } from '@neondatabase/serverless';
import axios from 'axios';
const NPM_PACKAGE = '@anthropic-ai/claude-code';
const CHANGELOG_URL = 'https://raw.githubusercontent.com/anthropics/claude-code/main/CHANGELOG.md';
// Inicializar cliente de Neon
function getNeonClient() {
const connectionString = process.env.NEON_DATABASE_URL;
if (!connectionString) {
throw new Error('NEON_DATABASE_URL not configured');
}
return neon(connectionString);
}
// Obtener la última versión de NPM
async function getLatestNPMVersion() {
try {
const response = await axios.get(`https://registry.npmjs.org/${NPM_PACKAGE}/latest`);
return {
version: response.data.version,
publishedAt: response.data.time?.modified || new Date().toISOString(),
npmUrl: `https://www.npmjs.com/package/${NPM_PACKAGE}/v/${response.data.version}`
};
} catch (error) {
console.error('Error fetching NPM version:', error);
throw new Error(`Failed to fetch NPM version: ${error.message}`);
}
}
// Verificar si la versión ya fue procesada
async function isVersionProcessed(sql, version) {
const result = await sql`
SELECT id, discord_notified
FROM claude_code_versions
WHERE version = ${version}
`;
return result.length > 0 ? result[0] : null;
}
// Guardar nueva versión en la base de datos
async function saveVersion(sql, versionData) {
const { version, publishedAt, npmUrl, changelogContent, githubUrl } = versionData;
const result = await sql`
INSERT INTO claude_code_versions (
version,
published_at,
changelog_content,
npm_url,
github_url,
discord_notified
) VALUES (
${version},
${publishedAt},
${changelogContent},
${npmUrl},
${githubUrl},
false
)
RETURNING id, version
`;
return result[0];
}
// Actualizar metadata de monitoreo
async function updateMonitoringMetadata(sql, version, errorMessage = null) {
await sql`
UPDATE monitoring_metadata
SET
last_check_at = NOW(),
last_version_found = ${version},
check_count = check_count + 1,
error_count = error_count + ${errorMessage ? 1 : 0},
last_error = ${errorMessage}
WHERE id = 1
`;
}
// Handler principal
export default async function handler(req, res) {
// CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
return res.status(200).json({ ok: true });
}
// Aceptar GET y POST
if (req.method !== 'POST' && req.method !== 'GET') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const sql = getNeonClient();
console.log('🔍 Checking for new Claude Code version...');
// Obtener última versión de NPM
const latestVersion = await getLatestNPMVersion();
console.log(`📦 Latest NPM version: ${latestVersion.version}`);
// Verificar si ya fue procesada
const existingVersion = await isVersionProcessed(sql, latestVersion.version);
if (existingVersion) {
console.log(`✓ Version ${latestVersion.version} already processed`);
// Si ya existe pero no fue notificada a Discord, podemos reintentarlo
if (!existingVersion.discord_notified) {
console.log('⚠️ Version exists but Discord notification pending');
// Actualizar metadata
await updateMonitoringMetadata(sql, latestVersion.version);
return res.status(200).json({
status: 'pending_notification',
version: latestVersion.version,
message: 'Version exists, Discord notification pending',
versionId: existingVersion.id
});
}
// Actualizar metadata
await updateMonitoringMetadata(sql, latestVersion.version);
return res.status(200).json({
status: 'already_processed',
version: latestVersion.version,
message: 'Version already processed and notified'
});
}
console.log(`🆕 New version detected: ${latestVersion.version}`);
// Obtener el changelog desde GitHub
console.log('📄 Fetching CHANGELOG.md...');
const changelogResponse = await axios.get(CHANGELOG_URL);
const fullChangelog = changelogResponse.data;
// Guardar la nueva versión (sin parsear aún)
const githubUrl = `https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md#${latestVersion.version.replace(/\./g, '')}`;
const savedVersion = await saveVersion(sql, {
version: latestVersion.version,
publishedAt: latestVersion.publishedAt,
npmUrl: latestVersion.npmUrl,
githubUrl,
changelogContent: fullChangelog.substring(0, 50000) // Limitar a 50KB
});
console.log(`✅ Version ${savedVersion.version} saved to database (ID: ${savedVersion.id})`);
// Actualizar metadata
await updateMonitoringMetadata(sql, latestVersion.version);
// Responder con éxito
return res.status(200).json({
status: 'new_version_detected',
version: savedVersion.version,
versionId: savedVersion.id,
message: 'New version saved, ready for processing',
data: {
version: latestVersion.version,
publishedAt: latestVersion.publishedAt,
npmUrl: latestVersion.npmUrl,
githubUrl
}
});
} catch (error) {
console.error('❌ Error in webhook handler:', error);
// Intentar actualizar metadata con el error
try {
const sql = getNeonClient();
await updateMonitoringMetadata(sql, null, error.message);
} catch (metaError) {
console.error('Failed to update metadata:', metaError);
}
return res.status(500).json({
error: 'Internal server error',
message: error.message,
details: process.env.NODE_ENV === 'development' ? error.stack : undefined
});
}
}