Skip to content

Conversation

@ItzNotABug
Copy link
Member

@ItzNotABug ItzNotABug commented Nov 9, 2025

What does this PR do?

(Provide a description of what this PR does.)

Test Plan

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work.)

Related PRs and Issues

(If this PR is related to any other PR or resolves any issue or related to any issue link all related PR and issues here.)

Have you read the Contributing Guidelines on issues?

(Write your answer here.)

Summary by CodeRabbit

  • New Features

    • HTTP API endpoints: health check, screenshot capture, performance reports, and a test HTML endpoint.
  • Chores

    • Switched runtime to Bun and updated start/dev/build scripts.
    • Added TypeScript configuration and type-checking setup.
    • Expanded linting to support TypeScript and new rules.
    • Added Docker and Git ignore rules to reduce build/context noise.
  • Documentation

    • Added basic test HTML generator for endpoint verification.

@ItzNotABug ItzNotABug self-assigned this Nov 9, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 9, 2025

Walkthrough

This PR migrates the project to the Bun runtime and introduces TypeScript. It replaces the Docker base image and install flow with Bun, adds Bun/TypeScript tooling (tsconfig, eslint changes, devDependencies), and replaces the server with a Bun-based src/server.ts. It adds configuration modules (browser, lighthouse, index), Zod schemas for screenshots and lighthouse requests, several route handlers (health, test, reports, screenshots), a simplified test HTML generator, and new .dockerignore/.gitignore entries.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Heterogeneous changes across build system, Dockerfile, linting, and runtime (Bun) plus TypeScript introduction.
  • Multiple new modules with non-trivial logic: Playwright browser launch (top-level await), Zod schemas, and route handlers that construct contexts, run Lighthouse/playwright flows, and format outputs.
  • ESLint and package.json changes affecting dev/build workflows.

Files/areas to pay extra attention to:

  • Dockerfile: runtime image, dependency installation, Chromium packaging, user switching, environment variables.
  • src/config/browser.ts: top-level await browser launch and executable path/env usage.
  • Zod schemas in src/schemas/*: validation rules, defaults, and inferred types.
  • src/routes/reports.ts and src/routes/screenshots.ts: Playwright context/options, permission grants, header handling, navigation timing/timeout, and error handling.
  • package.json and eslint.config.js: scripts and dependency alignment for Bun + TypeScript.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Update: use ts and bun' directly and clearly describes the main changes across the entire changeset, which involves migrating from JavaScript/Node to TypeScript and Bun runtime.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bun-ts

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 70c9e5d and ff74615.

📒 Files selected for processing (1)
  • src/routes/health.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/routes/health.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ItzNotABug ItzNotABug marked this pull request as ready for review November 9, 2025 11:41
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
package.json (1)

20-20: Update packageManager field to reference Bun.

The packageManager field in package.json should be updated from "pnpm@10.20.0" to "bun@<version>" (or just "bun") to align with the PR's migration to Bun. Bun supports the packageManager field and expects it to be set to "bun" or "bun@", with version pinning being recommended.

🧹 Nitpick comments (10)
src/config/browser.ts (2)

13-18: Consider adding graceful shutdown for browser resources.

The browser is launched at module initialization but there's no cleanup mechanism on application shutdown. This could lead to resource leaks or zombie Chromium processes.

Consider registering a cleanup handler in the server startup file:

// In src/server.ts or main entry point
process.on("SIGTERM", async () => {
	await browser.close();
	process.exit(0);
});

process.on("SIGINT", async () => {
	await browser.close();
	process.exit(0);
});

11-18: Consider structured logging instead of console statements.

The console.log statements for lifecycle events could be replaced with a structured logger for better observability in production.

src/server.ts (1)

9-35: Server implementation looks functional.

The manual route matching approach is straightforward for the current four endpoints. Consider extracting route definitions into a map if the number of routes grows significantly.

If routes expand beyond 5-6 endpoints, consider refactoring to a route map pattern:

const routes = {
  'POST /v1/screenshots': handleScreenshotsRequest,
  'POST /v1/reports': handleReportsRequest,
  'GET /v1/health': handleHealthRequest,
  'GET /v1/test': handleTestRequest,
} as const;

const key = `${req.method} ${path}`;
const handler = routes[key];
if (handler) return await handler(req);
src/routes/test.ts (1)

10-12: Misleading variable name.

The variable html contains a timestamp string, not HTML. Consider renaming to timestamp for clarity.

-	const html = await page.evaluate(() => {
+	const timestamp = await page.evaluate(() => {
 		return new Date().toISOString();
 	});

 	await context.close();

-	return new Response(generateTestHTML(html), {
+	return new Response(generateTestHTML(timestamp), {
 		headers: { "Content-Type": "text/html" },
 	});
src/routes/reports.ts (1)

82-88: Consider distinguishing client errors from server errors.

All errors return status 400 (Bad Request), including server-side failures like browser crashes or network timeouts. Consider returning 500 for internal errors.

 	} catch (error) {
+		// Distinguish validation errors from server errors
+		const isValidationError = error instanceof Error && 
+			error.name === 'ZodError';
+		const status = isValidationError ? 400 : 500;
+		
 		const message = error instanceof Error ? error.message : "Unknown error";
 		return new Response(JSON.stringify({ error: message }), {
-			status: 400,
+			status,
 			headers: { "Content-Type": "application/json" },
 		});
 	}
src/routes/screenshots.ts (2)

40-53: Set up route handler only when headers are provided.

The route handler is registered unconditionally, even when body.headers is not provided. This adds unnecessary overhead for the common case where custom headers aren't needed.

-		// Override headers
-		await page.route("**/*", async (route, request) => {
-			const url = request.url();
-			if (url.startsWith("http://appwrite/")) {
-				return await route.continue({
-					headers: {
-						...request.headers(),
-						...(body.headers || {}),
-					},
-				});
-			}
-
-			return await route.continue({ headers: request.headers() });
-		});
+		// Override headers if provided
+		if (body.headers) {
+			await page.route("**/*", async (route, request) => {
+				const url = request.url();
+				if (url.startsWith("http://appwrite/")) {
+					return await route.continue({
+						headers: {
+							...request.headers(),
+							...body.headers,
+						},
+					});
+				}
+				return await route.continue({ headers: request.headers() });
+			});
+		}

88-94: Consider distinguishing client errors from server errors.

All errors return status 400 (Bad Request), including server-side failures. Consider returning 500 for internal errors to help clients distinguish between request issues and server issues.

 	} catch (error) {
+		// Distinguish validation errors from server errors
+		const isValidationError = error instanceof Error && 
+			error.name === 'ZodError';
+		const status = isValidationError ? 400 : 500;
+		
 		const message = error instanceof Error ? error.message : "Unknown error";
 		return new Response(JSON.stringify({ error: message }), {
-			status: 400,
+			status,
 			headers: { "Content-Type": "application/json" },
 		});
 	}
src/schemas/screenshot.schema.ts (1)

6-6: Tighten header value validation for better type safety.

Using z.any() for header values bypasses Zod's type safety. HTTP header values are typically strings, numbers, or booleans (which are coerced to strings).

Apply this diff to improve type safety:

-	headers: z.record(z.string(), z.any()).optional(),
+	headers: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(),
src/schemas/lighthouse.schema.ts (2)

11-11: Tighten header value validation for better type safety.

Using z.any() for header values bypasses Zod's type safety. HTTP header values are typically strings, numbers, or booleans.

Apply this diff to improve type safety:

-	headers: z.record(z.string(), z.any()).optional(),
+	headers: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(),

3-73: Consider extracting shared schema fragments to reduce duplication.

Both lighthouseSchema and screenshotSchema share identical definitions for headers, timezoneId, permissions, waitUntil, and timeout. Consider extracting these into a shared base schema or reusable Zod objects to maintain consistency and reduce duplication.

For example, create a src/schemas/common.schema.ts:

import { z } from "zod";

export const headersSchema = z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional();

export const timezoneIdSchema = z
  .string()
  .regex(
    /^(Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Europe|Indian|Pacific|UTC)(\/[A-Za-z_]+)+$/,
    "Must be a valid IANA timezone identifier"
  )
  .optional();

export const permissionsSchema = z.array(z.enum([
  "geolocation", "camera", "microphone", /* ... */
])).optional();

export const navigationOptionsSchema = z.object({
  waitUntil: z.enum(["load", "domcontentloaded", "networkidle", "commit"]).default("domcontentloaded"),
  timeout: z.number().min(0).max(120000).default(30000),
});

Then import and use in both schema files.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e2a3f38 and 70c9e5d.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • .dockerignore (1 hunks)
  • .gitignore (1 hunks)
  • Dockerfile (2 hunks)
  • eslint.config.js (1 hunks)
  • package.json (2 hunks)
  • src/config/browser.ts (1 hunks)
  • src/config/index.ts (1 hunks)
  • src/config/lighthouse.ts (1 hunks)
  • src/routes/health.ts (1 hunks)
  • src/routes/index.ts (1 hunks)
  • src/routes/reports.ts (1 hunks)
  • src/routes/screenshots.ts (1 hunks)
  • src/routes/test.ts (1 hunks)
  • src/schemas/index.ts (1 hunks)
  • src/schemas/lighthouse.schema.ts (1 hunks)
  • src/schemas/screenshot.schema.ts (1 hunks)
  • src/server.ts (1 hunks)
  • src/utils/test-page.ts (6 hunks)
  • tsconfig.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
src/routes/reports.ts (3)
src/schemas/lighthouse.schema.ts (1)
  • lighthouseSchema (3-73)
src/config/browser.ts (2)
  • defaultContext (4-9)
  • browser (13-16)
src/config/lighthouse.ts (1)
  • lighthouseConfigs (4-7)
src/routes/screenshots.ts (2)
src/schemas/screenshot.schema.ts (1)
  • screenshotSchema (3-90)
src/config/browser.ts (2)
  • defaultContext (4-9)
  • browser (13-16)
src/routes/test.ts (2)
src/config/browser.ts (2)
  • browser (13-16)
  • defaultContext (4-9)
src/utils/test-page.ts (1)
  • generateTestHTML (1-326)
src/server.ts (4)
src/routes/screenshots.ts (1)
  • handleScreenshotsRequest (8-95)
src/routes/reports.ts (1)
  • handleReportsRequest (6-89)
src/routes/health.ts (1)
  • handleHealthRequest (3-12)
src/routes/test.ts (1)
  • handleTestRequest (4-19)
src/routes/health.ts (1)
src/config/browser.ts (1)
  • browser (13-16)
🔇 Additional comments (12)
eslint.config.js (1)

1-29: LGTM! Solid TypeScript ESLint configuration.

The TypeScript integration is well-configured with appropriate rules and the Bun global properly declared as readonly. The underscore ignore pattern for unused variables is a good convention.

src/schemas/index.ts (1)

1-2: LGTM! Standard schema re-export pattern.

The use of .js extensions in imports follows the Node.js ESM convention for TypeScript and is compatible with Bun's bundler module resolution.

src/routes/health.ts (1)

3-12: LGTM! Clean health check implementation.

The health check logic is straightforward and correctly uses browser.isConnected() to report status. The underscore prefix on the unused _req parameter follows the ESLint configuration.

src/config/lighthouse.ts (1)

1-7: LGTM!

The Lighthouse configuration aggregator is cleanly structured. The as const assertion ensures type safety for the viewport keys used in route handlers.

src/config/index.ts (1)

1-4: LGTM!

The configuration index cleanly aggregates config modules and handles port resolution correctly. The .js extensions in imports are correct for TypeScript ESM module resolution.

src/routes/index.ts (1)

1-4: LGTM!

Clean barrel export pattern for route handlers.

src/utils/test-page.ts (1)

1-326: LGTM!

The HTML generator provides comprehensive browser configuration testing. The use of innerHTML with error messages and API data is safe since all values originate from browser APIs rather than user input.

src/schemas/screenshot.schema.ts (1)

3-90: Comprehensive schema design with sensible defaults.

The schema provides thorough validation coverage for screenshot requests with appropriate:

  • Range constraints (viewport 1-3840x1-2160, quality 0-100, sleep 0-60s, timeout 0-120s)
  • Sensible defaults (1280x720 viewport, PNG format, 3s sleep, domcontentloaded wait)
  • Extensive permission allowlist covering modern browser APIs
  • Proper geolocation bounds (±90 lat, ±180 long)
Dockerfile (3)

12-25: Good practice: Chromium installation with size optimization.

The Dockerfile appropriately installs Chromium with necessary fonts and removes unnecessary files to reduce image size. The cleanup of crash handlers, resource paks, and sandbox binaries is a solid optimization.


27-34: Proper security and runtime configuration.

Good practices implemented:

  • Non-root user (chrome) for improved security
  • Playwright environment variables correctly configured
  • tini as PID 1 for proper signal handling

1-1: Checking for known security vulnerabilities in Bun 1.3.2:

No action required—Bun version is current and secure.

Bun 1.3.2 is the latest available version, released November 8, 2025. No known critical vulnerabilities specific to this version have been disclosed. The alpine variant minimizes base OS attack surface.

src/schemas/lighthouse.schema.ts (1)

59-67: Well-designed Lighthouse performance thresholds.

The thresholds object provides comprehensive coverage of Lighthouse metrics (performance, accessibility, best-practices, SEO, PWA) with appropriate 0-100 ranges and sensible defaults of 0 (no threshold enforcement by default).

Co-authored-by: Matej Bačo <matejbaco2000@gmail.com>
@ItzNotABug ItzNotABug marked this pull request as draft November 10, 2025 09:58
@ItzNotABug
Copy link
Member Author

ItzNotABug commented Nov 10, 2025

Moved to draft for simplifying endpoint/routing logic.
In a separate PR.

@ItzNotABug ItzNotABug marked this pull request as ready for review November 10, 2025 10:36
@ItzNotABug ItzNotABug merged commit 6a8c374 into main Nov 10, 2025
3 checks passed
@ItzNotABug ItzNotABug deleted the bun-ts branch November 10, 2025 10:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants