50e25e3ee8
Rename subdirectories for a cleaner single-repo layout: - website-monitoring-backend/ → backend/ - website-monitoring-frontend/ → frontend/ - website-monitoring-devops/ → devops/ Update all references in package.json scripts, CI workflows, docker-compose, pre-commit hooks, and documentation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
172 lines
5.0 KiB
TypeScript
172 lines
5.0 KiB
TypeScript
import express, { Request, Response } from "express";
|
|
import { launch } from "chrome-launcher";
|
|
import lighthouse from "lighthouse";
|
|
import cors from "cors";
|
|
|
|
const app = express();
|
|
app.use(cors());
|
|
app.use(express.json());
|
|
|
|
// Health check endpoint
|
|
app.get("/health", (req: Request, res: Response) => {
|
|
res.json({ status: "ok", timestamp: new Date().toISOString() });
|
|
});
|
|
|
|
// Enhanced lighthouse endpoint
|
|
app.post("/lighthouse", async (req: Request, res: Response) => {
|
|
const { url } = req.body;
|
|
if (!url) return res.status(400).json({ error: "Missing URL" });
|
|
|
|
console.log(`Starting Lighthouse scan for: ${url}`);
|
|
|
|
try {
|
|
// Validate URL format
|
|
try {
|
|
new URL(url);
|
|
} catch (urlError) {
|
|
return res.status(400).json({ error: "Invalid URL format" });
|
|
}
|
|
|
|
const chrome = await launch({
|
|
chromeFlags: [
|
|
"--headless",
|
|
"--no-sandbox",
|
|
"--disable-dev-shm-usage",
|
|
"--disable-gpu",
|
|
"--no-first-run",
|
|
"--disable-extensions",
|
|
"--disable-background-timer-throttling",
|
|
"--disable-backgrounding-occluded-windows",
|
|
"--disable-renderer-backgrounding",
|
|
"--disable-features=TranslateUI",
|
|
"--disable-ipc-flooding-protection",
|
|
],
|
|
});
|
|
|
|
console.log(`Chrome launched on port: ${chrome.port}`);
|
|
|
|
const runnerResult = await lighthouse(url, {
|
|
port: chrome.port,
|
|
output: "json",
|
|
logLevel: "info",
|
|
onlyCategories: ["performance", "seo", "accessibility", "best-practices"],
|
|
formFactor: "desktop",
|
|
screenEmulation: {
|
|
mobile: false,
|
|
width: 1350,
|
|
height: 940,
|
|
deviceScaleFactor: 1,
|
|
disabled: false,
|
|
},
|
|
throttling: {
|
|
rttMs: 40,
|
|
throughputKbps: 10240,
|
|
cpuSlowdownMultiplier: 1,
|
|
requestLatencyMs: 0,
|
|
downloadThroughputKbps: 0,
|
|
uploadThroughputKbps: 0,
|
|
},
|
|
});
|
|
|
|
await chrome.kill();
|
|
|
|
if (!runnerResult) throw new Error("Lighthouse returned no result");
|
|
|
|
const { categories, audits } = runnerResult.lhr;
|
|
|
|
// Extract key metrics for easier access
|
|
const metrics = {
|
|
performance: categories.performance?.score
|
|
? Math.round(categories.performance.score * 100)
|
|
: null,
|
|
seo: categories.seo?.score
|
|
? Math.round(categories.seo.score * 100)
|
|
: null,
|
|
accessibility: categories.accessibility?.score
|
|
? Math.round(categories.accessibility.score * 100)
|
|
: null,
|
|
bestPractices: categories["best-practices"]?.score
|
|
? Math.round(categories["best-practices"].score * 100)
|
|
: null,
|
|
firstContentfulPaint:
|
|
audits["first-contentful-paint"]?.numericValue || null,
|
|
largestContentfulPaint:
|
|
audits["largest-contentful-paint"]?.numericValue || null,
|
|
cumulativeLayoutShift:
|
|
audits["cumulative-layout-shift"]?.numericValue || null,
|
|
totalBlockingTime: audits["total-blocking-time"]?.numericValue || null,
|
|
speedIndex: audits["speed-index"]?.numericValue || null,
|
|
};
|
|
|
|
console.log(
|
|
`Lighthouse scan completed for: ${url} - Performance: ${metrics.performance}%`,
|
|
);
|
|
|
|
res.json({
|
|
categories,
|
|
audits,
|
|
metrics,
|
|
raw: runnerResult.lhr,
|
|
timestamp: new Date().toISOString(),
|
|
url: url,
|
|
});
|
|
} catch (error) {
|
|
console.error(`Lighthouse scan failed for ${url}:`, error);
|
|
res.status(500).json({
|
|
error: error instanceof Error ? error.message : String(error),
|
|
url: url,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
});
|
|
|
|
// Batch lighthouse endpoint for multiple URLs
|
|
app.post("/lighthouse/batch", async (req: Request, res: Response) => {
|
|
const { urls } = req.body;
|
|
if (!urls || !Array.isArray(urls) || urls.length === 0) {
|
|
return res.status(400).json({ error: "Missing or empty URLs array" });
|
|
}
|
|
|
|
if (urls.length > 10) {
|
|
return res.status(400).json({ error: "Maximum 10 URLs allowed per batch" });
|
|
}
|
|
|
|
console.log(`Starting batch Lighthouse scan for ${urls.length} URLs`);
|
|
|
|
const results = [];
|
|
|
|
for (const url of urls) {
|
|
try {
|
|
// Make internal request to single lighthouse endpoint
|
|
const response = await fetch(`http://localhost:${PORT}/lighthouse`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ url }),
|
|
});
|
|
|
|
const result = await response.json();
|
|
results.push({ url, success: response.ok, data: result });
|
|
} catch (error) {
|
|
results.push({
|
|
url,
|
|
success: false,
|
|
error: error instanceof Error ? error.message : String(error),
|
|
});
|
|
}
|
|
}
|
|
|
|
res.json({
|
|
results,
|
|
total: urls.length,
|
|
successful: results.filter((r) => r.success).length,
|
|
failed: results.filter((r) => !r.success).length,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
});
|
|
|
|
const PORT = process.env.PORT || 5001;
|
|
app.listen(PORT, () => {
|
|
console.log(`Lighthouse worker listening on http://localhost:${PORT}`);
|
|
console.log(`Health check available at http://localhost:${PORT}/health`);
|
|
});
|