feat: initialize monorepo with full dev team best practices
- Unified monorepo with backend (Express), frontend (Next.js), and devops - Backend: ESLint, Prettier, Jest tests (3 passing), health endpoint, .env.example - Frontend: Fixed build errors, fixed all lint errors (0 remaining), tests passing - DevOps: Docker Compose with PostgreSQL, backend, frontend + healthchecks - CI/CD: 3 GitHub Actions workflows (backend, frontend, docker integration) - DX: Husky pre-commit hooks with smart change detection - Docs: Root README with architecture, CONTRIBUTING.md, PR template Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
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`);
|
||||
});
|
||||
Reference in New Issue
Block a user