Github style badges 101 implementation using native nodejs server
Run: npx -y node@22 index.ts
Server Running: http://localhost:5000/ http://localhost:3000/badge.svg?message=pressed&color=green
Github style badges 101 implementation using native nodejs server
Run: npx -y node@22 index.ts
Server Running: http://localhost:5000/ http://localhost:3000/badge.svg?message=pressed&color=green
| // @ts-nocheck | |
| const fs = require('fs'); | |
| const http = require('http'); | |
| const PORT = Number(process.env.PORT) || 5000; | |
| function escapeXml(value) { | |
| return String(value) | |
| .replace(/&/g, '&') | |
| .replace(/</g, '<') | |
| .replace(/>/g, '>') | |
| .replace(/\"/g, '"') | |
| .replace(/'/g, '''); | |
| } | |
| function createBadge(label, status, color = '#4bc51d') { | |
| const safeLabel = escapeXml(label); | |
| const safeStatus = escapeXml(status); | |
| const safeColor = escapeXml(color); | |
| // Rough width estimation for basic scannability | |
| const labelWidth = safeLabel.length * 6 + 20; | |
| const statusWidth = safeStatus.length * 6 + 20; | |
| const totalWidth = labelWidth + statusWidth; | |
| const svg = ` | |
| <svg xmlns="http://www.w3.org/2000/svg" width="${totalWidth}" height="20" role="img" aria-label="${safeLabel}: ${safeStatus}"> | |
| <title>${safeLabel}: ${safeStatus}</title> | |
| <linearGradient id="s" x2="0" y2="100%"> | |
| <stop offset="0" stop-color="#bbb" stop-opacity=".1"/> | |
| <stop offset="1" stop-opacity=".1"/> | |
| </linearGradient> | |
| <clipPath id="r"> | |
| <rect width="${totalWidth}" height="20" rx="3" fill="#fff"/> | |
| </clipPath> | |
| <g clip-path="url(#r)"> | |
| <rect width="${labelWidth}" height="20" fill="#555"/> | |
| <rect x="${labelWidth}" width="${statusWidth}" height="20" fill="${safeColor}"/> | |
| <rect width="${totalWidth}" height="20" fill="url(#s)"/> | |
| </g> | |
| <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"> | |
| <text aria-hidden="true" x="${(labelWidth / 2) * 10}" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)">${safeLabel}</text> | |
| <text x="${(labelWidth / 2) * 10}" y="140" transform="scale(.1)" fill="#fff">${safeLabel}</text> | |
| <text aria-hidden="true" x="${(labelWidth + statusWidth / 2) * 10}" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)">${safeStatus}</text> | |
| <text x="${(labelWidth + statusWidth / 2) * 10}" y="140" transform="scale(.1)" fill="#fff">${safeStatus}</text> | |
| </g> | |
| </svg>`; | |
| return svg.trim(); | |
| } | |
| function sendJson(res, statusCode, payload) { | |
| res.writeHead(statusCode, { 'Content-Type': 'application/json; charset=utf-8' }); | |
| res.end(JSON.stringify(payload)); | |
| } | |
| function sendHtml(res, statusCode, html) { | |
| res.writeHead(statusCode, { 'Content-Type': 'text/html; charset=utf-8' }); | |
| res.end(html); | |
| } | |
| const server = http.createServer((req, res) => { | |
| const requestUrl = new URL(req.url || '/', `http://${req.headers.host || `localhost:${PORT}`}`); | |
| const label = requestUrl.searchParams.get('label') || 'version'; | |
| const status = requestUrl.searchParams.get('status') || 'v1.4.2'; | |
| const color = requestUrl.searchParams.get('color') || '#007ec6'; | |
| if (requestUrl.pathname === '/') { | |
| const query = `label=${encodeURIComponent(label)}&status=${encodeURIComponent(status)}&color=${encodeURIComponent(color)}`; | |
| const page = `<!doctype html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>Badge Preview</title> | |
| </head> | |
| <body style="font-family: Arial, sans-serif; padding: 24px;"> | |
| <h2>SVG Badge Preview</h2> | |
| <a href="/badge.svg" target="_blank" rel="noopener noreferrer"> | |
| <img src="/badge.svg?${query}" alt="badge" /> | |
| </a> | |
| <a href="/badge.svg?label=build&status=passing&color=%2300c853" target="_blank" rel="noopener noreferrer"> | |
| <img src="/badge.svg?label=build&status=passing&color=%2300c853" alt="badge" /> | |
| </a> | |
| </body> | |
| </html>`; | |
| sendHtml(res, 200, page); | |
| return; | |
| } | |
| if (requestUrl.pathname === '/badge.svg') { | |
| const badgeSvg = createBadge(label, status, color); | |
| res.writeHead(200, { | |
| 'Content-Type': 'image/svg+xml; charset=utf-8', | |
| 'Cache-Control': 'no-cache, no-store, must-revalidate', | |
| Pragma: 'no-cache', | |
| Expires: '0', | |
| 'X-Content-Type-Options': 'nosniff', | |
| }); | |
| res.end(badgeSvg); | |
| return; | |
| } | |
| sendJson(res, 404, { | |
| message: 'Not found', | |
| routes: ['/', '/badge.svg'], | |
| }); | |
| }); | |
| server.listen(PORT, () => { | |
| console.log(`Badge server running on http://localhost:${PORT}`); | |
| }); |