197 lines
5.9 KiB
JavaScript
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
|
|
});
|
|
}
|
|
}
|