refactor: flatten monorepo structure to backend/ frontend/ devops/
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>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
# Server
|
||||
PORT=5000
|
||||
|
||||
# Database (PostgreSQL)
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/monitoring
|
||||
|
||||
# CORS
|
||||
CORS_ORIGIN=http://localhost:3000
|
||||
|
||||
# Chrome (for Docker/CI)
|
||||
CHROME_PATH=/usr/bin/chromium
|
||||
@@ -0,0 +1,130 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"semi": true,
|
||||
"trailingComma": "all",
|
||||
"singleQuote": false,
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
# --- Stage 1: Build ---
|
||||
FROM node:20-slim AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# --- Stage 2: Production ---
|
||||
FROM node:20-slim AS runtime
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends chromium \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV CHROME_BIN=/usr/bin/chromium
|
||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN groupadd -r app && useradd -r -g app -d /app app
|
||||
|
||||
COPY --from=builder --chown=app:app /app/dist ./dist
|
||||
COPY --from=builder --chown=app:app /app/node_modules ./node_modules
|
||||
COPY --from=builder --chown=app:app /app/package.json ./
|
||||
|
||||
USER app
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD node -e "const h=require('http');h.get('http://localhost:5000/health',(r)=>{process.exit(r.statusCode===200?0:1)}).on('error',()=>process.exit(1))"
|
||||
|
||||
CMD ["node", "dist/index.js"]
|
||||
@@ -0,0 +1,57 @@
|
||||
# Website Monitoring Backend
|
||||
|
||||
Express.js API server that runs Google Lighthouse audits on websites and streams real-time progress via Server-Sent Events.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Runtime**: Node.js 18+
|
||||
- **Framework**: Express.js
|
||||
- **Language**: TypeScript
|
||||
- **Auditing**: Google Lighthouse + Chrome Headless
|
||||
- **Database**: PostgreSQL (via `pg`)
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
npm install
|
||||
npm run build
|
||||
npm start
|
||||
```
|
||||
|
||||
## Scripts
|
||||
|
||||
| Script | Description |
|
||||
|--------|-------------|
|
||||
| `npm run build` | Compile TypeScript to `dist/` |
|
||||
| `npm start` | Run the production server |
|
||||
| `npm run dev` | Watch mode for development |
|
||||
| `npm test` | Run Jest tests |
|
||||
| `npm run test:coverage` | Run tests with coverage report |
|
||||
| `npm run lint` | Run ESLint |
|
||||
| `npm run format` | Format code with Prettier |
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| `GET` | `/` | API info |
|
||||
| `GET` | `/health` | Health check |
|
||||
| `POST` | `/api/lighthouse` | Start Lighthouse audit (body: `{ "url": "https://example.com" }`) |
|
||||
| `GET` | `/api/lighthouse/status/:id` | SSE stream for audit progress |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `PORT` | `5000` | Server port |
|
||||
| `DATABASE_URL` | — | PostgreSQL connection string |
|
||||
| `CORS_ORIGIN` | `*` | Allowed CORS origin |
|
||||
| `CHROME_PATH` | — | Path to Chrome binary (Docker) |
|
||||
|
||||
## Docker
|
||||
|
||||
```bash
|
||||
docker build -t website-monitoring-backend .
|
||||
docker run -p 5000:5000 website-monitoring-backend
|
||||
```
|
||||
@@ -0,0 +1,23 @@
|
||||
import eslint from "@typescript-eslint/eslint-plugin";
|
||||
import parser from "@typescript-eslint/parser";
|
||||
|
||||
export default [
|
||||
{
|
||||
files: ["src/**/*.ts"],
|
||||
languageOptions: {
|
||||
parser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: "module",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
"@typescript-eslint": eslint,
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"no-console": "off",
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,21 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
export default {
|
||||
preset: "ts-jest/presets/default-esm",
|
||||
testEnvironment: "node",
|
||||
roots: ["<rootDir>/src"],
|
||||
moduleNameMapper: {
|
||||
"^(\\.{1,2}/.*)\\.js$": "$1",
|
||||
},
|
||||
transform: {
|
||||
"^.+\\.tsx?$": [
|
||||
"ts-jest",
|
||||
{
|
||||
useESM: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
extensionsToTreatAsEsm: [".ts"],
|
||||
collectCoverageFrom: ["src/**/*.ts", "!src/**/*.test.ts"],
|
||||
coverageDirectory: "coverage",
|
||||
coverageReporters: ["text", "lcov", "clover"],
|
||||
};
|
||||
Generated
+9368
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "website-monitoring-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"test": "NODE_OPTIONS='--experimental-vm-modules' jest --passWithNoTests",
|
||||
"test:coverage": "NODE_OPTIONS='--experimental-vm-modules' jest --coverage",
|
||||
"lint": "eslint src/",
|
||||
"lint:fix": "eslint src/ --fix",
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/denshooter/website-monitoring-backend.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/denshooter/website-monitoring-backend/issues"
|
||||
},
|
||||
"homepage": "https://github.com/denshooter/website-monitoring-backend#readme",
|
||||
"dependencies": {
|
||||
"chrome-launcher": "^1.1.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"lighthouse": "^12.4.0",
|
||||
"lighthouse-logger": "^2.0.1",
|
||||
"pg": "^8.13.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^22.13.9",
|
||||
"@types/supertest": "^7.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
||||
"@typescript-eslint/parser": "^8.56.1",
|
||||
"eslint": "^10.0.2",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"jest": "^30.2.0",
|
||||
"prettier": "^3.8.1",
|
||||
"supertest": "^7.2.2",
|
||||
"ts-jest": "^29.4.6",
|
||||
"tsx": "^4.21.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import request from "supertest";
|
||||
import { app } from "../index.js";
|
||||
|
||||
describe("API Server", () => {
|
||||
describe("GET /health", () => {
|
||||
it("should return 200 with status ok", async () => {
|
||||
const res = await request(app).get("/health");
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.status).toBe("ok");
|
||||
expect(res.body.timestamp).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /", () => {
|
||||
it("should return API info", async () => {
|
||||
const res = await request(app).get("/");
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.name).toBe("Website Monitoring API");
|
||||
expect(res.body.version).toBe("1.0.0");
|
||||
expect(res.body.endpoints).toContain("/health");
|
||||
expect(res.body.endpoints).toContain("/api/lighthouse");
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /api/lighthouse", () => {
|
||||
it("should return 400 when no URL provided", async () => {
|
||||
const res = await request(app).post("/api/lighthouse").send({});
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toBe("Missing URL");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
import request from "supertest";
|
||||
import { app } from "../index.js";
|
||||
|
||||
describe("Lighthouse API", () => {
|
||||
describe("POST /api/lighthouse", () => {
|
||||
it("should return 400 when body is empty", async () => {
|
||||
const res = await request(app).post("/api/lighthouse").send({});
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toBe("Missing URL");
|
||||
});
|
||||
|
||||
it("should return 400 when URL is missing from body", async () => {
|
||||
const res = await request(app)
|
||||
.post("/api/lighthouse")
|
||||
.send({ notUrl: "something" });
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toBe("Missing URL");
|
||||
});
|
||||
|
||||
it("should accept a valid URL and return a clientId", async () => {
|
||||
// This test will get a clientId back but Chrome won't be available in test
|
||||
// The endpoint returns 200 with clientId before starting Chrome
|
||||
const res = await request(app)
|
||||
.post("/api/lighthouse")
|
||||
.send({ url: "https://example.com" });
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.clientId).toBeDefined();
|
||||
expect(typeof res.body.clientId).toBe("string");
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /api/lighthouse/status/:id", () => {
|
||||
it("should be a valid SSE endpoint", () => {
|
||||
// SSE endpoints keep connections open, so we just verify the route exists
|
||||
// Full SSE testing would require a dedicated SSE test client
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Health & Info Endpoints", () => {
|
||||
describe("GET /health", () => {
|
||||
it("should return status ok with timestamp", async () => {
|
||||
const res = await request(app).get("/health");
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body).toMatchObject({
|
||||
status: "ok",
|
||||
});
|
||||
expect(res.body.timestamp).toBeDefined();
|
||||
// Timestamp should be valid ISO string
|
||||
expect(new Date(res.body.timestamp).toISOString()).toBe(res.body.timestamp);
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /", () => {
|
||||
it("should return API metadata", async () => {
|
||||
const res = await request(app).get("/");
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.name).toBe("Website Monitoring API");
|
||||
expect(res.body.version).toMatch(/^\d+\.\d+\.\d+$/);
|
||||
expect(Array.isArray(res.body.endpoints)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("404 handling", () => {
|
||||
it("should return 404 for unknown routes", async () => {
|
||||
const res = await request(app).get("/api/unknown");
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe("CORS", () => {
|
||||
it("should include CORS headers", async () => {
|
||||
const res = await request(app).get("/health");
|
||||
// CORS middleware is enabled
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import express, { Request, Response } from "express";
|
||||
import cors from "cors";
|
||||
import dotenv from "dotenv";
|
||||
import lighthouseRouter from "./routes/lighthouse.js";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
// Rate limiting (simple in-memory for single instance)
|
||||
const rateLimitMap = new Map<string, { count: number; resetAt: number }>();
|
||||
const RATE_LIMIT_WINDOW = 60_000; // 1 minute
|
||||
const RATE_LIMIT_MAX = 30; // requests per window
|
||||
|
||||
function rateLimit(req: Request, res: Response, next: () => void) {
|
||||
const ip = req.ip || req.headers["x-forwarded-for"] || "unknown";
|
||||
const key = String(ip);
|
||||
const now = Date.now();
|
||||
const entry = rateLimitMap.get(key);
|
||||
|
||||
if (!entry || now > entry.resetAt) {
|
||||
rateLimitMap.set(key, { count: 1, resetAt: now + RATE_LIMIT_WINDOW });
|
||||
return next();
|
||||
}
|
||||
|
||||
if (entry.count >= RATE_LIMIT_MAX) {
|
||||
res.status(429).json({ error: "Too many requests" });
|
||||
return;
|
||||
}
|
||||
|
||||
entry.count++;
|
||||
next();
|
||||
}
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(cors({ origin: process.env.CORS_ORIGIN || "*" }));
|
||||
app.use(express.json());
|
||||
app.use(rateLimit);
|
||||
|
||||
app.get("/health", (_req: Request, res: Response) => {
|
||||
res.status(200).json({ status: "ok", timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
app.get("/", (_req: Request, res: Response) => {
|
||||
res.status(200).json({
|
||||
name: "Website Monitoring API",
|
||||
version: "1.0.0",
|
||||
endpoints: ["/health", "/api/lighthouse"],
|
||||
});
|
||||
});
|
||||
|
||||
app.use("/api/lighthouse", lighthouseRouter);
|
||||
|
||||
export { app };
|
||||
|
||||
if (process.env.NODE_ENV !== "test") {
|
||||
const PORT = process.env.PORT || 5000;
|
||||
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
import express, { Request, Response } from "express";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
const router = express.Router();
|
||||
const progressClients = new Map<string, Response>();
|
||||
|
||||
/** Send SSE progress data to the browser */
|
||||
function sendProgress(clientId: string, data: any) {
|
||||
const client = progressClients.get(clientId);
|
||||
if (client) {
|
||||
client.write(`data: ${JSON.stringify(data)}\n\n`);
|
||||
}
|
||||
}
|
||||
|
||||
// SSE endpoint for progress
|
||||
router.get("/status/:id", (req: Request, res: Response) => {
|
||||
res.setHeader("Content-Type", "text/event-stream");
|
||||
res.setHeader("Cache-Control", "no-cache");
|
||||
res.setHeader("Connection", "keep-alive");
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
|
||||
const clientId = req.params.id;
|
||||
progressClients.set(clientId, res);
|
||||
|
||||
// Send an initial event
|
||||
sendProgress(clientId, {
|
||||
status: "Connected",
|
||||
progress: 0,
|
||||
stage: "setup",
|
||||
});
|
||||
|
||||
req.on("close", () => {
|
||||
progressClients.delete(clientId);
|
||||
});
|
||||
});
|
||||
|
||||
router.post("/", async (req: Request, res: Response) => {
|
||||
const { url } = req.body;
|
||||
if (!url) {
|
||||
return res.status(400).json({ error: "Missing URL" });
|
||||
}
|
||||
|
||||
const clientId = uuidv4(); // Generate a UUID
|
||||
console.log(`New client ID: ${clientId}`);
|
||||
let currentProgress = 0;
|
||||
let auditCount = 0;
|
||||
let totalAudits = 0;
|
||||
|
||||
try {
|
||||
// Immediately tell the frontend we’re starting (0%)
|
||||
sendProgress(clientId, {
|
||||
status: "Starting analysis...",
|
||||
progress: 0,
|
||||
stage: "setup",
|
||||
});
|
||||
|
||||
// Return clientId so the frontend can open the SSE channel
|
||||
res.status(200).json({ clientId });
|
||||
|
||||
// Dynamically import chrome-launcher and lighthouse
|
||||
const { launch } = await import("chrome-launcher");
|
||||
const lighthouse = (await import("lighthouse")).default;
|
||||
const log = (await import("lighthouse-logger")).default;
|
||||
|
||||
// Launch Chrome
|
||||
const chrome = await launch({
|
||||
chromeFlags: ["--headless", "--no-sandbox", "--disable-dev-shm-usage"],
|
||||
});
|
||||
|
||||
// Update progress to 10% after launching Chrome
|
||||
currentProgress = 10;
|
||||
sendProgress(clientId, {
|
||||
status: "Chrome launched",
|
||||
progress: currentProgress,
|
||||
stage: "setup",
|
||||
});
|
||||
|
||||
// Turn on Lighthouse logs at "info" level
|
||||
log.setLevel("info");
|
||||
|
||||
// Listen for Lighthouse status events
|
||||
log.events.addListener("status", (lhStatus) => {
|
||||
let msg = lhStatus[1].toLowerCase();
|
||||
let newProgress = currentProgress;
|
||||
let newStage = "analyzing";
|
||||
|
||||
if (msg.includes("initialize config")) {
|
||||
newProgress = Math.max(newProgress, 15);
|
||||
newStage = "setup";
|
||||
} else if (msg.includes("resolve artifact definitions")) {
|
||||
newProgress = Math.max(newProgress, 20);
|
||||
newStage = "setup";
|
||||
} else if (msg.includes("gather phase")) {
|
||||
newProgress = Math.max(newProgress, 25);
|
||||
newStage = "analyzing";
|
||||
} else if (msg.includes("connecting to browser")) {
|
||||
newProgress = Math.max(newProgress, 28);
|
||||
newStage = "analyzing";
|
||||
} else if (msg.includes("navigating to")) {
|
||||
newProgress = Math.max(newProgress, 30);
|
||||
newStage = "analyzing";
|
||||
} else if (msg.includes("benchmarking machine")) {
|
||||
newProgress = Math.max(newProgress, 35);
|
||||
newStage = "analyzing";
|
||||
} else if (msg.includes("getting artifact")) {
|
||||
newProgress = Math.max(newProgress, 40);
|
||||
newStage = "analyzing";
|
||||
} else if (msg.includes("computing artifact")) {
|
||||
newProgress = Math.max(newProgress, 50);
|
||||
newStage = "analyzing";
|
||||
} else if (msg.includes("audit phase")) {
|
||||
newProgress = Math.max(newProgress, 60);
|
||||
newStage = "auditing";
|
||||
} else if (msg.includes("auditing:")) {
|
||||
auditCount++;
|
||||
totalAudits = Math.max(totalAudits, auditCount);
|
||||
newProgress = Math.max(
|
||||
newProgress,
|
||||
60 + (auditCount / totalAudits) * 30,
|
||||
);
|
||||
newStage = "auditing";
|
||||
} else if (msg.includes("generating results")) {
|
||||
newProgress = Math.max(newProgress, 95);
|
||||
newStage = "finishing";
|
||||
}
|
||||
|
||||
// Add some randomness to the progress increments
|
||||
newProgress += Math.random() * 2;
|
||||
|
||||
// Only send progress if it actually increased
|
||||
if (newProgress > currentProgress) {
|
||||
currentProgress = newProgress;
|
||||
sendProgress(clientId, {
|
||||
status: lhStatus.message || "Processing...",
|
||||
progress: currentProgress,
|
||||
stage: newStage,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Run Lighthouse
|
||||
const runnerResult = await lighthouse(url, {
|
||||
port: chrome.port,
|
||||
output: "json",
|
||||
logLevel: "info",
|
||||
onlyCategories: ["performance", "seo", "accessibility", "best-practices"],
|
||||
});
|
||||
|
||||
if (!runnerResult) {
|
||||
throw new Error("Lighthouse returned no result");
|
||||
}
|
||||
|
||||
// Finish progress
|
||||
sendProgress(clientId, {
|
||||
status: "Analysis complete",
|
||||
progress: 100,
|
||||
stage: "complete",
|
||||
report: runnerResult.lhr,
|
||||
});
|
||||
|
||||
await chrome.kill();
|
||||
} catch (error) {
|
||||
console.error("Lighthouse error:", error);
|
||||
sendProgress(clientId, {
|
||||
status: "Error occurred",
|
||||
progress: 0,
|
||||
stage: "error",
|
||||
error:
|
||||
error instanceof Error ? error.message : "An unknown error occurred",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,14 @@
|
||||
// filepath: /c:/Users/denni/OneDrive/Dokumente/code/website-monitoring/backend/tsconfig.json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"target": "es2018",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user