From 32964261c1b5a1e747bb0b8277c04432b4bca6fa Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 12 Nov 2025 12:42:04 +0530 Subject: [PATCH 01/19] add: aggressive cleanup. --- Dockerfile | 17 +++-- src/utils/clean-modules.ts | 134 +++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 src/utils/clean-modules.ts diff --git a/Dockerfile b/Dockerfile index 90616b5..fc4b297 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,8 +3,10 @@ FROM oven/bun:1.3.2-alpine AS base WORKDIR /app COPY package.json bun.lock ./ +COPY src/utils/clean-modules.ts ./src/utils/clean-modules.ts RUN bun install --frozen-lockfile --production && \ + bun run ./src/utils/clean-modules.ts && \ rm -rf ~/.bun/install/cache /tmp/* FROM oven/bun:1.3.2-alpine AS final @@ -14,15 +16,20 @@ RUN apk upgrade --no-cache --available && \ chromium \ ttf-freefont \ font-noto-emoji \ - tini && \ + tini \ + upx && \ apk add --no-cache font-wqy-zenhei --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community && \ - # remove unnecessary chromium files to save space - rm -rf /usr/lib/chromium/chrome_crashpad_handler \ - /usr/lib/chromium/chrome_200_percent.pak \ + # Compress chromium with UPX + upx --best --lzma /usr/lib/chromium/chromium 2>/dev/null || true && \ + # Remove UPX after compression + apk del upx && \ + rm -rf /usr/lib/chromium/chrome_200_percent.pak \ /usr/lib/chromium/chrome_100_percent.pak \ /usr/lib/chromium/xdg-mime \ /usr/lib/chromium/xdg-settings \ - /usr/lib/chromium/chrome-sandbox + /usr/lib/chromium/chrome-sandbox && \ + # Clean up caches + rm -rf /var/cache/apk/* /tmp/* /root/.cache RUN addgroup -S chrome && adduser -S -G chrome chrome diff --git a/src/utils/clean-modules.ts b/src/utils/clean-modules.ts new file mode 100644 index 0000000..7f72195 --- /dev/null +++ b/src/utils/clean-modules.ts @@ -0,0 +1,134 @@ +import { readdirSync, unlinkSync } from "node:fs"; +import { join } from "node:path"; +import { $ } from "bun"; + +const NODE_MODULES = "/app/node_modules"; + +async function getDirSize(path: string): Promise { + const result = await $`du -sm ${path}`.quiet(); + return Number.parseInt(result.text().split("\t")[0]); +} + +async function deleteFiles( + pattern: string, + description: string, +): Promise { + console.log(`${description}...`); + await $`find ${NODE_MODULES} -name ${pattern} -delete 2>/dev/null || true`.quiet(); +} + +async function deleteDirectories( + dirName: string, + description: string, +): Promise { + console.log(`${description}...`); + await $`find ${NODE_MODULES} -depth -type d -name ${dirName} -exec rm -rf {} + 2>/dev/null || true`.quiet(); +} + +async function deletePath(path: string): Promise { + const file = Bun.file(path); + if (await file.exists()) { + await $`rm -rf ${path}`.quiet(); + } +} + +async function removeDocumentationFiles(): Promise { + console.log("๐Ÿ“ Removing documentation files..."); + await deleteFiles("*.md", " - Markdown files"); + await deleteFiles("*.d.ts", " - TypeScript declarations"); + await deleteFiles("*.map", " - Source maps"); +} + +async function removeTypeScriptSources(): Promise { + console.log("๐Ÿ”ง Removing TypeScript sources..."); + await deleteFiles("*.ts", " - TypeScript files"); + await deleteFiles("tsconfig.json", " - TypeScript configs"); + await deleteFiles("*.tsbuildinfo", " - TypeScript build info"); +} + +async function removeTestFiles(): Promise { + console.log("๐Ÿงช Removing test files..."); + await deleteFiles("*.test.js", " - JavaScript tests"); + await deleteFiles("*.test.ts", " - TypeScript tests"); + await deleteDirectories("test", " - test/ directories"); + await deleteDirectories("tests", " - tests/ directories"); + await deleteDirectories("__tests__", " - __tests__/ directories"); + await deleteDirectories("coverage", " - coverage/ directories"); +} + +async function removeDevelopmentDirectories(): Promise { + console.log("๐Ÿ—‚๏ธ Removing development directories..."); + await deleteDirectories(".github", " - .github/ directories"); + await deleteDirectories("docs", " - docs/ directories"); + await deleteDirectories("examples", " - examples/ directories"); + await deleteDirectories("benchmark", " - benchmark/ directories"); +} + +async function removeLighthouseLocales(): Promise { + console.log("๐ŸŒ Removing non-English Lighthouse locales..."); + const lighthouseLocalesPath = `${NODE_MODULES}/lighthouse/shared/localization/locales`; + try { + const locales = readdirSync(lighthouseLocalesPath); + for (const locale of locales) { + if (locale !== "en-US.json") { + unlinkSync(join(lighthouseLocalesPath, locale)); + } + } + console.log(` - Removed ${locales.length - 1} Lighthouse locale files`); + } catch { + console.log(" - Lighthouse locales not found (skipped)"); + } +} + +async function removeTraceEngineLocales(): Promise { + console.log("๐ŸŒ Removing non-English trace_engine locales..."); + const traceEngineLocalesPath = `${NODE_MODULES}/@paulirish/trace_engine/locales`; + try { + const traceLocales = readdirSync(traceEngineLocalesPath); + for (const locale of traceLocales) { + if (locale !== "en-US.json") { + unlinkSync(join(traceEngineLocalesPath, locale)); + } + } + console.log( + ` - Removed ${traceLocales.length - 1} trace_engine locale files`, + ); + } catch { + console.log(" - trace_engine locales not found (skipped)"); + } +} + +async function removeUnnecessaryFiles(): Promise { + console.log("๐ŸŽญ Removing unnecessary files..."); + await deletePath(`${NODE_MODULES}/playwright-core/lib/vite`); + await deletePath(`${NODE_MODULES}/puppeteer-core/src`); + await deletePath(`${NODE_MODULES}/zod/src`); + await deletePath(`${NODE_MODULES}/third-party-web/dist/domain-map.csv`); + await deletePath(`${NODE_MODULES}/puppeteer-core/node_modules/devtools-protocol`); +} + +async function cleanModules(): Promise { + console.log("๐Ÿงน Starting node_modules cleanup..."); + const startSize = await getDirSize(NODE_MODULES); + + await Promise.all([ + removeDocumentationFiles(), + removeTypeScriptSources(), + removeTestFiles(), + removeDevelopmentDirectories(), + removeLighthouseLocales(), + removeTraceEngineLocales(), + removeUnnecessaryFiles(), + ]); + + const endSize = await getDirSize(NODE_MODULES); + const saved = startSize - endSize; + + console.log("\nโœ… Cleanup complete!"); + console.log(`๐Ÿ“Š ${startSize}MB โ†’ ${endSize}MB (Saved: ${saved}MB)`); +} + +cleanModules().catch((error) => { + console.error("โŒ Cleanup failed:", error); + process.exit(1); +}); From 7bb74d0ce800a3533da30a13f4d9a8a7a0e7bcbe Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 12 Nov 2025 15:06:29 +0530 Subject: [PATCH 02/19] fixes and lint. --- Dockerfile | 5 ++++- src/utils/clean-modules.ts | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 07cb92a..ba171d1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,9 +40,12 @@ ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \ WORKDIR /app +COPY --chown=chrome:chrome src/ ./src/ COPY --chown=chrome:chrome package.json ./ COPY --chown=chrome:chrome --from=base /app/node_modules ./node_modules -COPY --chown=chrome:chrome src/ ./src/ + +# for e2e tests and `reports` endpoint! +RUN install -d -o chrome -g chrome lighthouse USER chrome diff --git a/src/utils/clean-modules.ts b/src/utils/clean-modules.ts index e6b3b0f..0eaf0e0 100644 --- a/src/utils/clean-modules.ts +++ b/src/utils/clean-modules.ts @@ -88,7 +88,9 @@ async function removeUnnecessaryFiles(): Promise { await deletePath(`${NODE_MODULES}/puppeteer-core/src`); await deletePath(`${NODE_MODULES}/zod/src`); await deletePath(`${NODE_MODULES}/third-party-web/dist/domain-map.csv`); - await deletePath(`${NODE_MODULES}/puppeteer-core/node_modules/devtools-protocol`); + await deletePath( + `${NODE_MODULES}/puppeteer-core/node_modules/devtools-protocol`, + ); } async function cleanModules(): Promise { From 29e89ea8c2b116a8f64ae5c9cebfa158debc28ea Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 13 Nov 2025 22:10:27 +0530 Subject: [PATCH 03/19] address comment. --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b9d9e30..1ba51aa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,6 +45,6 @@ jobs: - name: Run e2e tests run: bun test:e2e - - name: Stop container - if: always() - run: docker compose down \ No newline at end of file + - name: Print logs + if: failure() + run: docker compose logs \ No newline at end of file From 0c569bf6590290f8d67ff0f95d36a184e67bbb82 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 12:15:15 +0530 Subject: [PATCH 04/19] remove: more unneeded files. --- src/utils/clean-modules.ts | 98 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 95 insertions(+), 3 deletions(-) diff --git a/src/utils/clean-modules.ts b/src/utils/clean-modules.ts index 0eaf0e0..e3216b4 100644 --- a/src/utils/clean-modules.ts +++ b/src/utils/clean-modules.ts @@ -26,9 +26,11 @@ async function deleteDirectories( } async function deletePath(path: string): Promise { - const file = Bun.file(path); - if (await file.exists()) { + try { + await $`test -e ${path}`.quiet(); await $`rm -rf ${path}`.quiet(); + } catch { + // ignore } } @@ -37,12 +39,22 @@ async function removeDocumentationFiles(): Promise { await deleteFiles("*.md", " - Markdown files"); await deleteFiles("*.d.ts", " - TypeScript declarations"); await deleteFiles("*.map", " - Source maps"); + await deleteFiles("LICENSE*", " - LICENSE files"); + await deleteFiles("README*", " - README files"); + await deleteFiles("CHANGELOG*", " - CHANGELOG files"); + await deleteFiles("AUTHORS*", " - AUTHORS files"); + await deleteFiles("CONTRIBUTORS*", " - CONTRIBUTORS files"); + await deleteFiles("NOTICE*", " - NOTICE files"); + await deleteFiles("HISTORY*", " - HISTORY files"); + await deleteFiles("*.txt", " - Text files"); } async function removeTypeScriptSources(): Promise { console.log("๐Ÿ”ง Removing TypeScript sources..."); await deleteFiles("*.ts", " - TypeScript files"); - await deleteFiles("tsconfig.json", " - TypeScript configs"); + await deleteFiles("*.jsx", " - JavaScript JSX files"); + await deleteFiles("*.tsx", " - TypeScript JSX files"); + await deleteFiles("tsconfig*.json", " - TypeScript configs"); await deleteFiles("*.tsbuildinfo", " - TypeScript build info"); } @@ -50,9 +62,14 @@ async function removeTestFiles(): Promise { console.log("๐Ÿงช Removing test files..."); await deleteFiles("*.test.js", " - JavaScript tests"); await deleteFiles("*.test.ts", " - TypeScript tests"); + await deleteFiles("*.spec.js", " - JavaScript specs"); + await deleteFiles("*.spec.ts", " - TypeScript specs"); await deleteDirectories("test", " - test/ directories"); await deleteDirectories("tests", " - tests/ directories"); await deleteDirectories("__tests__", " - __tests__/ directories"); + await deleteDirectories("__mocks__", " - __mocks__/ directories"); + await deleteDirectories("__fixtures__", " - __fixtures__/ directories"); + await deleteDirectories("fixtures", " - fixtures/ directories"); await deleteDirectories("coverage", " - coverage/ directories"); } @@ -62,6 +79,30 @@ async function removeDevelopmentDirectories(): Promise { await deleteDirectories("docs", " - docs/ directories"); await deleteDirectories("examples", " - examples/ directories"); await deleteDirectories("benchmark", " - benchmark/ directories"); + await deleteDirectories("samples", " - samples/ directories"); +} + +async function removeDevelopmentFiles(): Promise { + console.log("โš™๏ธ Removing development config files..."); + await deleteFiles(".eslintrc*", " - ESLint configs"); + await deleteFiles(".prettierrc*", " - Prettier configs"); + await deleteFiles(".editorconfig", " - EditorConfig files"); + await deleteFiles("jest.config.*", " - Jest configs"); + await deleteFiles("vitest.config.*", " - Vitest configs"); + await deleteFiles(".npmignore", " - NPM ignore files"); + await deleteFiles(".gitignore", " - Git ignore files"); + await deleteFiles("bun.lock", " - Bun lock files"); + await deleteFiles("yarn.lock", " - Yarn lock files"); + await deleteFiles("package-lock.json", " - NPM lock files"); + await deleteFiles("pnpm-lock.yaml", " - PNPM lock files"); +} + +async function removeScriptsAndDeclarations(): Promise { + console.log("๐Ÿ“œ Removing scripts and declarations..."); + await deleteFiles("*.sh", " - Shell scripts"); + await deleteFiles("*.ps1", " - PowerShell scripts"); + await deleteFiles("*.d.cts", " - CommonJS TypeScript declarations"); + await deleteFiles("*.d.mts", " - ES Module TypeScript declarations"); } async function removeTraceEngineLocales(): Promise { @@ -82,6 +123,42 @@ async function removeTraceEngineLocales(): Promise { } } +async function removeLighthouseLocales(): Promise { + console.log("๐ŸŒ Replacing non-English Lighthouse locales with stubs..."); + const localesPath = `${NODE_MODULES}/lighthouse/shared/localization/locales`; + try { + const locales = readdirSync(localesPath); + const stubContent = "{}"; + for (const locale of locales) { + if (locale !== "en-US.json") { + const filePath = join(localesPath, locale); + unlinkSync(filePath); + await Bun.write(filePath, stubContent); + } + } + } catch { + console.log(" - Lighthouse locales not found (skipped)"); + } +} + +async function removeAxeCoreLocales(): Promise { + console.log("๐ŸŒ Replacing non-English axe-core locales with stubs..."); + const localesPath = `${NODE_MODULES}/axe-core/locales`; + try { + const locales = readdirSync(localesPath); + const stubContent = "{}"; + for (const locale of locales) { + if (locale !== "en.json" && !locale.startsWith("_")) { + const filePath = join(localesPath, locale); + unlinkSync(filePath); + await Bun.write(filePath, stubContent); + } + } + } catch { + console.log(" - axe-core locales not found (skipped)"); + } +} + async function removeUnnecessaryFiles(): Promise { console.log("๐ŸŽญ Removing unnecessary files..."); await deletePath(`${NODE_MODULES}/playwright-core/lib/vite`); @@ -91,6 +168,17 @@ async function removeUnnecessaryFiles(): Promise { await deletePath( `${NODE_MODULES}/puppeteer-core/node_modules/devtools-protocol`, ); + await deletePath(`${NODE_MODULES}/@sentry`); + await deletePath(`${NODE_MODULES}/@opentelemetry`); + // Remove unminified axe-core (we have axe.min.js) + await deletePath(`${NODE_MODULES}/axe-core/axe.js`); + // Remove Lighthouse CLI (we use it programmatically) + await deletePath(`${NODE_MODULES}/lighthouse/cli`); + await deletePath(`${NODE_MODULES}/lighthouse/third-party`); + // Remove config files from lighthouse root + await deletePath(`${NODE_MODULES}/lighthouse/build-tracker.config.js`); + await deletePath(`${NODE_MODULES}/lighthouse/commitlint.config.js`); + await deletePath(`${NODE_MODULES}/lighthouse/eslint.config.mjs`); } async function cleanModules(): Promise { @@ -102,7 +190,11 @@ async function cleanModules(): Promise { removeTypeScriptSources(), removeTestFiles(), removeDevelopmentDirectories(), + removeDevelopmentFiles(), + removeScriptsAndDeclarations(), removeTraceEngineLocales(), + removeLighthouseLocales(), + removeAxeCoreLocales(), removeUnnecessaryFiles(), ]); From 6e8a2f95eb314f8ae7c5ec6a0f0e7b44d5514f4c Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 12:16:06 +0530 Subject: [PATCH 05/19] use: chrome-headless and bun debian! --- Dockerfile | 44 ++++++++++++++++++++------------------------ bun.lock | 1 + 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/Dockerfile b/Dockerfile index ba171d1..a970f02 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ -FROM oven/bun:1.3.2-alpine AS base +# debian so we can re-use! +FROM oven/bun:1.3.2-debian AS base WORKDIR /app @@ -9,33 +10,28 @@ RUN bun install --frozen-lockfile --production && \ bun run ./src/utils/clean-modules.ts && \ rm -rf ~/.bun/install/cache /tmp/* -FROM oven/bun:1.3.2-alpine AS final +# well-known OSS docker image +FROM chromedp/headless-shell AS final -RUN apk upgrade --no-cache --available && \ - apk add --no-cache \ - chromium \ - ttf-freefont \ - font-noto-emoji \ +# install fonts only +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ tini \ - upx && \ - apk add --no-cache font-wqy-zenhei --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community && \ - # Compress chromium with UPX - upx --best --lzma /usr/lib/chromium/chromium 2>/dev/null || true && \ - # Remove UPX after compression - apk del upx && \ - # remove unnecessary chromium files to save space - rm -rf /usr/lib/chromium/chrome_200_percent.pak \ - /usr/lib/chromium/chrome_100_percent.pak \ - /usr/lib/chromium/xdg-mime \ - /usr/lib/chromium/xdg-settings \ - /usr/lib/chromium/chrome-sandbox && \ - # Clean up caches - rm -rf /var/cache/apk/* /tmp/* /root/.cache - -RUN addgroup -S chrome && adduser -S -G chrome chrome + ca-certificates \ + fonts-liberation \ + fonts-noto-color-emoji \ + fonts-wqy-zenhei && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /var/cache/apt/archives/* + +# copy bun from debian base above! +COPY --from=base /usr/local/bin/bun /usr/local/bin/bun + +# Add chrome user +RUN groupadd -r chrome && useradd -r -g chrome chrome ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \ - PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser \ + PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/headless-shell/headless-shell \ NODE_ENV=production WORKDIR /app diff --git a/bun.lock b/bun.lock index 724854e..ab49c02 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "appwrite-browser", From e4e61bd5bac91e724e1c10a0b732964b7e62a760 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 12:30:26 +0530 Subject: [PATCH 06/19] Clean up unused modules from node_modules Removed unnecessary files related to axe-core and Lighthouse. --- src/utils/clean-modules.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/utils/clean-modules.ts b/src/utils/clean-modules.ts index e3216b4..76b14d5 100644 --- a/src/utils/clean-modules.ts +++ b/src/utils/clean-modules.ts @@ -170,12 +170,8 @@ async function removeUnnecessaryFiles(): Promise { ); await deletePath(`${NODE_MODULES}/@sentry`); await deletePath(`${NODE_MODULES}/@opentelemetry`); - // Remove unminified axe-core (we have axe.min.js) await deletePath(`${NODE_MODULES}/axe-core/axe.js`); - // Remove Lighthouse CLI (we use it programmatically) await deletePath(`${NODE_MODULES}/lighthouse/cli`); - await deletePath(`${NODE_MODULES}/lighthouse/third-party`); - // Remove config files from lighthouse root await deletePath(`${NODE_MODULES}/lighthouse/build-tracker.config.js`); await deletePath(`${NODE_MODULES}/lighthouse/commitlint.config.js`); await deletePath(`${NODE_MODULES}/lighthouse/eslint.config.mjs`); From 50808d8e5c11478ebaf16218da08ec7042dc2cd7 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 12:35:22 +0530 Subject: [PATCH 07/19] update: improve the speed! fix: reports endpoint issue. --- src/schemas/screenshot.schema.ts | 2 +- src/utils/clean-modules.ts | 4 ---- tests/unit/screenshot.schema.test.ts | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/schemas/screenshot.schema.ts b/src/schemas/screenshot.schema.ts index ca50351..3e5b156 100644 --- a/src/schemas/screenshot.schema.ts +++ b/src/schemas/screenshot.schema.ts @@ -4,7 +4,7 @@ export const screenshotSchema = z.object({ url: z.string().url(), theme: z.enum(["light", "dark"]).default("light"), headers: z.record(z.string(), z.any()).optional(), - sleep: z.number().min(0).max(60000).default(3000), + sleep: z.number().min(0).max(60000).default(0), // Viewport options viewport: z .object({ diff --git a/src/utils/clean-modules.ts b/src/utils/clean-modules.ts index e3216b4..76b14d5 100644 --- a/src/utils/clean-modules.ts +++ b/src/utils/clean-modules.ts @@ -170,12 +170,8 @@ async function removeUnnecessaryFiles(): Promise { ); await deletePath(`${NODE_MODULES}/@sentry`); await deletePath(`${NODE_MODULES}/@opentelemetry`); - // Remove unminified axe-core (we have axe.min.js) await deletePath(`${NODE_MODULES}/axe-core/axe.js`); - // Remove Lighthouse CLI (we use it programmatically) await deletePath(`${NODE_MODULES}/lighthouse/cli`); - await deletePath(`${NODE_MODULES}/lighthouse/third-party`); - // Remove config files from lighthouse root await deletePath(`${NODE_MODULES}/lighthouse/build-tracker.config.js`); await deletePath(`${NODE_MODULES}/lighthouse/commitlint.config.js`); await deletePath(`${NODE_MODULES}/lighthouse/eslint.config.mjs`); diff --git a/tests/unit/screenshot.schema.test.ts b/tests/unit/screenshot.schema.test.ts index b6082ac..68a9901 100644 --- a/tests/unit/screenshot.schema.test.ts +++ b/tests/unit/screenshot.schema.test.ts @@ -23,7 +23,7 @@ describe("screenshotSchema", () => { expect(result.quality).toBe(90); expect(result.waitUntil).toBe("domcontentloaded"); expect(result.timeout).toBe(30000); - expect(result.sleep).toBe(3000); + expect(result.sleep).toBe(0); }); test("should validate custom viewport", () => { From 28e8118b6c5025eca0623b7c86a6378d0d1fad70 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 12:37:38 +0530 Subject: [PATCH 08/19] use: specific image version! --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a970f02..6f1f518 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ RUN bun install --frozen-lockfile --production && \ rm -rf ~/.bun/install/cache /tmp/* # well-known OSS docker image -FROM chromedp/headless-shell AS final +FROM chromedp/headless-shell:143.0.7445.3 AS final # install fonts only RUN apt-get update && \ From e7ba28c752b43d2161fdcc6fca2bb67985b1ce54 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 12:38:01 +0530 Subject: [PATCH 09/19] Update base image version in Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a970f02..6f1f518 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ RUN bun install --frozen-lockfile --production && \ rm -rf ~/.bun/install/cache /tmp/* # well-known OSS docker image -FROM chromedp/headless-shell AS final +FROM chromedp/headless-shell:143.0.7445.3 AS final # install fonts only RUN apt-get update && \ From 4e6b20d97b8b0571d69527bc2f1cd8ec1b9370d6 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 12:50:43 +0530 Subject: [PATCH 10/19] add: proper, nicer router support. --- src/server.ts | 32 ++++++++------------------------ src/utils/router.ts | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 24 deletions(-) create mode 100644 src/utils/router.ts diff --git a/src/server.ts b/src/server.ts index 6abb755..11c0872 100644 --- a/src/server.ts +++ b/src/server.ts @@ -5,33 +5,17 @@ import { handleScreenshotsRequest, handleTestRequest, } from "./routes"; +import { Router } from "./utils/router"; + +const router = new Router(); +router.add("POST", "/v1/screenshots", handleScreenshotsRequest); +router.add("POST", "/v1/reports", handleReportsRequest); +router.add("GET", "/v1/health", handleHealthRequest); +router.add("GET", "/v1/test", handleTestRequest); const server = Bun.serve({ port, - async fetch(req) { - const url = new URL(req.url); - const path = url.pathname; - - // Route matching - if (path === "/v1/screenshots" && req.method === "POST") { - return await handleScreenshotsRequest(req); - } - - if (path === "/v1/reports" && req.method === "POST") { - return await handleReportsRequest(req); - } - - if (path === "/v1/health" && req.method === "GET") { - return await handleHealthRequest(req); - } - - if (path === "/v1/test" && req.method === "GET") { - return await handleTestRequest(req); - } - - // 404 Not Found - return new Response("Not Found", { status: 404 }); - }, + fetch: (request) => router.handle(request), }); console.log(`Server running on http://0.0.0.0:${server.port}`); diff --git a/src/utils/router.ts b/src/utils/router.ts new file mode 100644 index 0000000..1825a32 --- /dev/null +++ b/src/utils/router.ts @@ -0,0 +1,40 @@ +type HTTPMethod = + | "GET" + | "POST" + | "PUT" + | "PATCH" + | "DELETE" + | "OPTIONS" + | "HEAD"; + +type RouteHandler = (req: Request) => Promise; + +type Route = { + method: HTTPMethod; + pattern: RegExp; + handler: RouteHandler; +}; + +export class Router { + private routes: Route[] = []; + + add(method: HTTPMethod, path: string, handler: RouteHandler): void { + this.routes.push({ + method, + pattern: new RegExp(`^${path}$`), + handler, + }); + } + + async handle(req: Request): Promise { + const url = new URL(req.url); + + for (const route of this.routes) { + if (route.method === req.method && route.pattern.test(url.pathname)) { + return await route.handler(req); + } + } + + return new Response("Not Found", { status: 404 }); + } +} From f999d75ad7ddb3184b416cf76c04fd3c469ab61f Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 13:36:56 +0530 Subject: [PATCH 11/19] update: address commment from rabbit. --- src/utils/clean-modules.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/clean-modules.ts b/src/utils/clean-modules.ts index 76b14d5..36e14e2 100644 --- a/src/utils/clean-modules.ts +++ b/src/utils/clean-modules.ts @@ -170,7 +170,6 @@ async function removeUnnecessaryFiles(): Promise { ); await deletePath(`${NODE_MODULES}/@sentry`); await deletePath(`${NODE_MODULES}/@opentelemetry`); - await deletePath(`${NODE_MODULES}/axe-core/axe.js`); await deletePath(`${NODE_MODULES}/lighthouse/cli`); await deletePath(`${NODE_MODULES}/lighthouse/build-tracker.config.js`); await deletePath(`${NODE_MODULES}/lighthouse/commitlint.config.js`); From f8f05c805d924b6d261be46f12cb53f66ef6b760 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 13:46:22 +0530 Subject: [PATCH 12/19] add: test stats to post on PR. --- .github/workflows/test.yml | 66 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1ba51aa..e00788a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,6 +9,9 @@ jobs: tests: name: "Unit and E2E Tests" runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write steps: - name: Checkout code uses: actions/checkout@v4 @@ -42,6 +45,69 @@ jobs: docker compose logs exit 1 + - name: Collect Docker stats + if: github.event_name == 'pull_request' + id: docker-stats + run: | + # Get image size + IMAGE_SIZE=$(docker images appwrite-browser --format "{{.Size}}") + IMAGE_SIZE_BYTES=$(docker inspect appwrite-browser --format='{{.Size}}') + IMAGE_SIZE_MB=$(echo "scale=2; $IMAGE_SIZE_BYTES / 1024 / 1024" | bc) + + # Get container stats + CONTAINER_ID=$(docker compose ps -q browser) + MEMORY_USAGE=$(docker stats $CONTAINER_ID --no-stream --format "{{.MemUsage}}") + + # Quick screenshot benchmark (3 runs, average) + TOTAL=0 + for i in {1..3}; do + START=$(date +%s%3N) + curl -s -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{"url":"https://appwrite.io"}' \ + -o /dev/null + END=$(date +%s%3N) + DURATION=$((END - START)) + TOTAL=$((TOTAL + DURATION)) + done + SCREENSHOT_AVG=$((TOTAL / 3)) + + # Measure fresh startup time + START_TIME=$(date +%s%3N) + docker compose restart browser + for i in {1..30}; do + if curl -f http://localhost:3000/v1/health > /dev/null 2>&1; then + END_TIME=$(date +%s%3N) + STARTUP_TIME=$((END_TIME - START_TIME)) + break + fi + sleep 0.1 + done + + # Store in GitHub output + echo "image_size=$IMAGE_SIZE" >> $GITHUB_OUTPUT + echo "image_size_mb=$IMAGE_SIZE_MB" >> $GITHUB_OUTPUT + echo "memory_usage=$MEMORY_USAGE" >> $GITHUB_OUTPUT + echo "startup_time=$STARTUP_TIME" >> $GITHUB_OUTPUT + echo "screenshot_time=$SCREENSHOT_AVG" >> $GITHUB_OUTPUT + + - name: Comment PR with stats + if: github.event_name == 'pull_request' + uses: peter-evans/create-or-update-comment@v5 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Docker Image Stats + + | Metric | Value | + |--------|-------| + | Image Size | ${{ steps.docker-stats.outputs.image_size }} (${{ steps.docker-stats.outputs.image_size_mb }} MB) | + | Memory Usage | ${{ steps.docker-stats.outputs.memory_usage }} | + | Startup Time | ${{ steps.docker-stats.outputs.startup_time }}ms | + | Screenshot Time | ${{ steps.docker-stats.outputs.screenshot_time }}ms | + + Benchmark: Average of 3 screenshot runs on https://appwrite.io + - name: Run e2e tests run: bun test:e2e From 7cee01de8c8c488bb12f7ce90f66b6d3516b9e77 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 13:50:41 +0530 Subject: [PATCH 13/19] fix: ci. --- .github/workflows/test.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e00788a..8599fee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,15 +47,16 @@ jobs: - name: Collect Docker stats if: github.event_name == 'pull_request' + continue-on-error: true id: docker-stats run: | # Get image size - IMAGE_SIZE=$(docker images appwrite-browser --format "{{.Size}}") - IMAGE_SIZE_BYTES=$(docker inspect appwrite-browser --format='{{.Size}}') + IMAGE_SIZE=$(docker images appwrite/browser:local --format "{{.Size}}") + IMAGE_SIZE_BYTES=$(docker inspect appwrite/browser:local --format='{{.Size}}') IMAGE_SIZE_MB=$(echo "scale=2; $IMAGE_SIZE_BYTES / 1024 / 1024" | bc) # Get container stats - CONTAINER_ID=$(docker compose ps -q browser) + CONTAINER_ID=$(docker compose ps -q appwrite-browser) MEMORY_USAGE=$(docker stats $CONTAINER_ID --no-stream --format "{{.MemUsage}}") # Quick screenshot benchmark (3 runs, average) @@ -92,7 +93,8 @@ jobs: echo "screenshot_time=$SCREENSHOT_AVG" >> $GITHUB_OUTPUT - name: Comment PR with stats - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' && steps.docker-stats.outcome == 'success' + continue-on-error: true uses: peter-evans/create-or-update-comment@v5 with: issue-number: ${{ github.event.pull_request.number }} From 8575c8dc7854c2c514277e8bb9a16fc4d3afaf98 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 13:54:53 +0530 Subject: [PATCH 14/19] fix: ci. --- .github/workflows/test.yml | 2 +- src/utils/clean-modules.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8599fee..8a8d424 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -75,7 +75,7 @@ jobs: # Measure fresh startup time START_TIME=$(date +%s%3N) - docker compose restart browser + docker compose restart appwrite-browser for i in {1..30}; do if curl -f http://localhost:3000/v1/health > /dev/null 2>&1; then END_TIME=$(date +%s%3N) diff --git a/src/utils/clean-modules.ts b/src/utils/clean-modules.ts index 36e14e2..76b14d5 100644 --- a/src/utils/clean-modules.ts +++ b/src/utils/clean-modules.ts @@ -170,6 +170,7 @@ async function removeUnnecessaryFiles(): Promise { ); await deletePath(`${NODE_MODULES}/@sentry`); await deletePath(`${NODE_MODULES}/@opentelemetry`); + await deletePath(`${NODE_MODULES}/axe-core/axe.js`); await deletePath(`${NODE_MODULES}/lighthouse/cli`); await deletePath(`${NODE_MODULES}/lighthouse/build-tracker.config.js`); await deletePath(`${NODE_MODULES}/lighthouse/commitlint.config.js`); From 4204a3c14cef822d08ef0f14c37922950d2d3c87 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 13:59:35 +0530 Subject: [PATCH 15/19] update: ci. --- .github/workflows/test.yml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8a8d424..d919739 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,12 +52,10 @@ jobs: run: | # Get image size IMAGE_SIZE=$(docker images appwrite/browser:local --format "{{.Size}}") - IMAGE_SIZE_BYTES=$(docker inspect appwrite/browser:local --format='{{.Size}}') - IMAGE_SIZE_MB=$(echo "scale=2; $IMAGE_SIZE_BYTES / 1024 / 1024" | bc) # Get container stats CONTAINER_ID=$(docker compose ps -q appwrite-browser) - MEMORY_USAGE=$(docker stats $CONTAINER_ID --no-stream --format "{{.MemUsage}}") + MEMORY_USAGE=$(docker stats $CONTAINER_ID --no-stream --format "{{.MemUsage}}" | cut -d'/' -f1 | xargs) # Quick screenshot benchmark (3 runs, average) TOTAL=0 @@ -71,7 +69,8 @@ jobs: DURATION=$((END - START)) TOTAL=$((TOTAL + DURATION)) done - SCREENSHOT_AVG=$((TOTAL / 3)) + SCREENSHOT_AVG_MS=$((TOTAL / 3)) + SCREENSHOT_AVG=$(echo "scale=2; $SCREENSHOT_AVG_MS / 1000" | bc) # Measure fresh startup time START_TIME=$(date +%s%3N) @@ -79,15 +78,15 @@ jobs: for i in {1..30}; do if curl -f http://localhost:3000/v1/health > /dev/null 2>&1; then END_TIME=$(date +%s%3N) - STARTUP_TIME=$((END_TIME - START_TIME)) + STARTUP_TIME_MS=$((END_TIME - START_TIME)) break fi sleep 0.1 done + STARTUP_TIME=$(echo "scale=2; $STARTUP_TIME_MS / 1000" | bc) # Store in GitHub output echo "image_size=$IMAGE_SIZE" >> $GITHUB_OUTPUT - echo "image_size_mb=$IMAGE_SIZE_MB" >> $GITHUB_OUTPUT echo "memory_usage=$MEMORY_USAGE" >> $GITHUB_OUTPUT echo "startup_time=$STARTUP_TIME" >> $GITHUB_OUTPUT echo "screenshot_time=$SCREENSHOT_AVG" >> $GITHUB_OUTPUT @@ -103,12 +102,12 @@ jobs: | Metric | Value | |--------|-------| - | Image Size | ${{ steps.docker-stats.outputs.image_size }} (${{ steps.docker-stats.outputs.image_size_mb }} MB) | + | Image Size | ${{ steps.docker-stats.outputs.image_size }} | | Memory Usage | ${{ steps.docker-stats.outputs.memory_usage }} | - | Startup Time | ${{ steps.docker-stats.outputs.startup_time }}ms | - | Screenshot Time | ${{ steps.docker-stats.outputs.screenshot_time }}ms | + | Startup Time | ${{ steps.docker-stats.outputs.startup_time }}s | + | Screenshot Time | ${{ steps.docker-stats.outputs.screenshot_time }}s | - Benchmark: Average of 3 screenshot runs on https://appwrite.io + Screenshot benchmark: Average of 3 runs on https://appwrite.io - name: Run e2e tests run: bun test:e2e From 231a696ac30677d7ef7fe3391234ed38a57f6f6b Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 14:13:46 +0530 Subject: [PATCH 16/19] update: ci for comment spam. --- .github/workflows/test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d919739..7ba0aab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -94,10 +94,11 @@ jobs: - name: Comment PR with stats if: github.event_name == 'pull_request' && steps.docker-stats.outcome == 'success' continue-on-error: true - uses: peter-evans/create-or-update-comment@v5 + uses: marocchino/sticky-pull-request-comment@v2 with: - issue-number: ${{ github.event.pull_request.number }} - body: | + header: docker-image-stats + skip_unchanged: true + message: | ## Docker Image Stats | Metric | Value | From 0580dacdd35f1bd0c47500309941b14f8ac0ff5d Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 14:21:44 +0530 Subject: [PATCH 17/19] fix: startup time. --- .github/workflows/test.yml | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7ba0aab..f63286c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,10 +31,16 @@ jobs: run: docker compose up -d - name: Wait for container to be ready + id: container-startup run: | + START_TIME=$(date +%s%3N) for i in {1..30}; do if curl -f http://localhost:3000/v1/health > /dev/null 2>&1; then - echo "Container is ready!" + END_TIME=$(date +%s%3N) + STARTUP_TIME_MS=$((END_TIME - START_TIME)) + STARTUP_TIME=$(echo "scale=2; $STARTUP_TIME_MS / 1000" | bc) + echo "startup_time=$STARTUP_TIME" >> $GITHUB_OUTPUT + echo "Container is ready in ${STARTUP_TIME}s!" exit 0 fi echo "Waiting for container... ($i/30)" @@ -72,23 +78,9 @@ jobs: SCREENSHOT_AVG_MS=$((TOTAL / 3)) SCREENSHOT_AVG=$(echo "scale=2; $SCREENSHOT_AVG_MS / 1000" | bc) - # Measure fresh startup time - START_TIME=$(date +%s%3N) - docker compose restart appwrite-browser - for i in {1..30}; do - if curl -f http://localhost:3000/v1/health > /dev/null 2>&1; then - END_TIME=$(date +%s%3N) - STARTUP_TIME_MS=$((END_TIME - START_TIME)) - break - fi - sleep 0.1 - done - STARTUP_TIME=$(echo "scale=2; $STARTUP_TIME_MS / 1000" | bc) - # Store in GitHub output echo "image_size=$IMAGE_SIZE" >> $GITHUB_OUTPUT echo "memory_usage=$MEMORY_USAGE" >> $GITHUB_OUTPUT - echo "startup_time=$STARTUP_TIME" >> $GITHUB_OUTPUT echo "screenshot_time=$SCREENSHOT_AVG" >> $GITHUB_OUTPUT - name: Comment PR with stats @@ -105,7 +97,7 @@ jobs: |--------|-------| | Image Size | ${{ steps.docker-stats.outputs.image_size }} | | Memory Usage | ${{ steps.docker-stats.outputs.memory_usage }} | - | Startup Time | ${{ steps.docker-stats.outputs.startup_time }}s | + | Cold Start Time | ${{ steps.container-startup.outputs.startup_time }}s | | Screenshot Time | ${{ steps.docker-stats.outputs.screenshot_time }}s | Screenshot benchmark: Average of 3 runs on https://appwrite.io From 68decdf3d7b2dc3d361e4507c0ae5906d7680c31 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 15:37:51 +0530 Subject: [PATCH 18/19] Apply suggestion from @ItzNotABug --- src/utils/clean-modules.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/clean-modules.ts b/src/utils/clean-modules.ts index 36e14e2..e47bd7e 100644 --- a/src/utils/clean-modules.ts +++ b/src/utils/clean-modules.ts @@ -170,6 +170,7 @@ async function removeUnnecessaryFiles(): Promise { ); await deletePath(`${NODE_MODULES}/@sentry`); await deletePath(`${NODE_MODULES}/@opentelemetry`); + await deletePath(`${NODE_MODULES}/axe-core/axe.js`); await deletePath(`${NODE_MODULES}/lighthouse/cli`); await deletePath(`${NODE_MODULES}/lighthouse/build-tracker.config.js`); await deletePath(`${NODE_MODULES}/lighthouse/commitlint.config.js`); From 77cd303e7f65c8ebf8cd5e34f1efafecf7a32685 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 15:39:36 +0530 Subject: [PATCH 19/19] lint. --- src/utils/clean-modules.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/clean-modules.ts b/src/utils/clean-modules.ts index e47bd7e..76b14d5 100644 --- a/src/utils/clean-modules.ts +++ b/src/utils/clean-modules.ts @@ -170,7 +170,7 @@ async function removeUnnecessaryFiles(): Promise { ); await deletePath(`${NODE_MODULES}/@sentry`); await deletePath(`${NODE_MODULES}/@opentelemetry`); - await deletePath(`${NODE_MODULES}/axe-core/axe.js`); + await deletePath(`${NODE_MODULES}/axe-core/axe.js`); await deletePath(`${NODE_MODULES}/lighthouse/cli`); await deletePath(`${NODE_MODULES}/lighthouse/build-tracker.config.js`); await deletePath(`${NODE_MODULES}/lighthouse/commitlint.config.js`);