Files
davila7--claude-code-templates/api/track-installation-outcome.js
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

109 lines
3.1 KiB
JavaScript

// Installation Outcome Tracking API Endpoint - Neon Database
// Tracks CLI installation results (success/failure) for reliability analytics
import { neon } from '@neondatabase/serverless';
function getNeonClient() {
const connectionString = process.env.NEON_DATABASE_URL;
if (!connectionString) {
throw new Error('NEON_DATABASE_URL not configured');
}
return neon(connectionString);
}
function validateOutcomeData(data) {
const { componentType, componentName, outcome } = data;
if (!componentType || !componentName || !outcome) {
return { valid: false, error: 'componentType, componentName, and outcome are required' };
}
const validTypes = ['agent', 'command', 'mcp', 'setting', 'hook', 'skill', 'template'];
if (!validTypes.includes(componentType)) {
return { valid: false, error: 'Invalid component type' };
}
const validOutcomes = ['success', 'failure', 'partial'];
if (!validOutcomes.includes(outcome)) {
return { valid: false, error: 'Invalid outcome. Must be: success, failure, or partial' };
}
if (componentName.length > 255) {
return { valid: false, error: 'Component name too long' };
}
return { valid: true };
}
export default async function handler(req, res) {
// CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, User-Agent');
if (req.method === 'OPTIONS') {
return res.status(200).json({ ok: true });
}
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed', allowed: ['POST'] });
}
try {
const {
componentType,
componentName,
outcome,
errorType,
errorMessage,
durationMs,
cliVersion,
nodeVersion,
platform,
arch,
batchId
} = req.body;
const validation = validateOutcomeData({ componentType, componentName, outcome });
if (!validation.valid) {
return res.status(400).json({ error: validation.error });
}
const sql = getNeonClient();
await sql`
INSERT INTO installation_outcomes (
component_type, component_name, outcome,
error_type, error_message, duration_ms,
cli_version, node_version, platform, arch, batch_id
) VALUES (
${componentType},
${componentName},
${outcome},
${errorType || null},
${errorMessage ? errorMessage.substring(0, 1000) : null},
${durationMs || null},
${cliVersion || 'unknown'},
${nodeVersion || 'unknown'},
${platform || 'unknown'},
${arch || 'unknown'},
${batchId || null}
)
`;
res.status(200).json({
success: true,
message: 'Installation outcome tracked',
data: { componentType, componentName, outcome, timestamp: new Date().toISOString() }
});
} catch (error) {
console.error('Installation outcome tracking error:', error);
res.status(500).json({
error: 'Internal server error',
message: 'Failed to track installation outcome',
details: process.env.NODE_ENV === 'development' ? error.message : undefined
});
}
}