From 802801bd773362c7c10ecd4c5a7a1d21415ff564 Mon Sep 17 00:00:00 2001 From: Caknoooo Date: Wed, 26 Nov 2025 14:03:07 +0700 Subject: [PATCH 1/4] feat: change approach crawling with playwright --- Dockerfile | 53 +- README.md | 4 +- docker-compose.yml | 10 +- docs/en/getting-started.md | 2 +- docs/en/index.md | 2 +- docs/id/getting-started.md | 2 +- docs/id/index.md | 2 +- docs/public/openapi.json | 2 +- package-lock.json | 673 +++----------------- package.json | 5 +- src/api/openapi.ts | 2 +- src/api/routes/web.ts | 6 +- src/cli/services/utils/docker-config.ts | 149 +++-- src/cli/services/utils/docker-operations.ts | 17 +- src/cli/utils/command-utils.ts | 12 +- src/services/accessibility.ts | 20 +- src/services/crawling.ts | 46 +- 17 files changed, 283 insertions(+), 724 deletions(-) diff --git a/Dockerfile b/Dockerfile index cc249e1..93c60ca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,46 +1,57 @@ -FROM node:20-alpine AS builder - +# --------------------------- +# Builder Stage +# --------------------------- +FROM node:22-alpine AS builder WORKDIR /app -# Install deps +RUN apk update && apk add --no-cache \ + bash \ + chromium \ + chromium-chromedriver \ + nss \ + freetype \ + harfbuzz \ + ca-certificates \ + ttf-freefont \ + && apk add --no-cache --virtual .build-deps \ + gcc g++ make python3 && \ + npm install -g cross-env + COPY package*.json ./ -RUN npm ci +RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi -# Copy source code -COPY . . +RUN npm install playwright -# Build app -RUN npm run build +COPY tsconfig*.json ./ +COPY src ./src -FROM node:20-alpine AS production +RUN npm run build +RUN npm run build:cli +# --------------------------- +# Runtime Stage +# --------------------------- +FROM node:22-alpine AS runtime WORKDIR /app -# Install dependencies needed for Puppeteer -RUN apk add --no-cache \ +RUN apk update && apk add --no-cache \ chromium \ + chromium-chromedriver \ nss \ freetype \ - freetype-dev \ harfbuzz \ ca-certificates \ ttf-freefont -ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \ - PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser \ +ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \ + PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser \ NODE_ENV=production -# Copy package.json dan node_modules hasil build COPY package*.json ./ COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/dist ./dist COPY --from=builder /app/src/data ./src/data -# Create necessary directories RUN mkdir -p logs storage -# Expose port -EXPOSE 3001 - -# Default command -CMD ["npm", "start"] \ No newline at end of file +CMD ["node", "dist/index.js"] diff --git a/README.md b/README.md index bc0485a..a0f04be 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ TypeScript Node.js
-Puppeteer +Playwright axe-core Express

@@ -937,7 +937,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file ## 🙏 Acknowledgments - [axe-core](https://github.com/dequelabs/axe-core) for accessibility testing -- [Puppeteer](https://github.com/puppeteer/puppeteer) for web automation +- [Playwright](https://github.com/microsoft/playwright) for web automation - [Express.js](https://expressjs.com/) for the web framework - [OpenAI](https://openai.com/) for AI-powered accessibility analysis - [Docker](https://www.docker.com/) for containerization diff --git a/docker-compose.yml b/docker-compose.yml index b1945ca..d1872da 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.9' - services: lenscore-init: build: @@ -7,7 +5,7 @@ services: dockerfile: Dockerfile volumes: - node_modules_data:/app/node_modules - profiles: ['init'] + profiles: ["init"] lenscore: container_name: lenscore-app @@ -15,7 +13,7 @@ services: context: . dockerfile: Dockerfile ports: - - '${LENSCORE_PORT:-3001}:${LENSCORE_PORT:-3001}' + - "${LENSCORE_PORT:-3001}:${LENSCORE_PORT:-3001}" environment: - NODE_ENV=development - PORT=${LENSCORE_PORT:-3001} @@ -36,14 +34,14 @@ services: - ./cache:/app/cache - ./web:/app/web - ./storage:/app/storage - - node_modules_data:/app/node_modules + - ${HOME}/.lenscore/web:/app/.lenscore/web depends_on: - redis redis: image: redis:7-alpine ports: - - '6379:6379' + - "6379:6379" volumes: - redis_data:/data command: redis-server --appendonly yes diff --git a/docs/en/getting-started.md b/docs/en/getting-started.md index 7a6fc44..e61a398 100644 --- a/docs/en/getting-started.md +++ b/docs/en/getting-started.md @@ -7,7 +7,7 @@ Welcome to LensCore! This guide will help you get up and running with our access LensCore is an open-source platform that combines: - **Accessibility Testing** powered by axe-core -- **Intelligent Web Crawling** with Puppeteer +- **Intelligent Web Crawling** with Playwright - **AI-Enhanced Analysis** using OpenAI (optional) - **Comprehensive Reporting** in HTML and JSON formats diff --git a/docs/en/index.md b/docs/en/index.md index a3ac58d..2226aa2 100644 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -35,7 +35,7 @@ features: - icon: ⚡ title: High Performance - details: Built with TypeScript and Puppeteer for fast, reliable testing at scale + details: Built with TypeScript and Playwright for fast, reliable testing at scale - icon: 🌐 title: Multi-Language diff --git a/docs/id/getting-started.md b/docs/id/getting-started.md index 2ac3681..f802cf7 100644 --- a/docs/id/getting-started.md +++ b/docs/id/getting-started.md @@ -7,7 +7,7 @@ Selamat datang di LensCore! Panduan ini akan membantu Anda memulai dengan platfo LensCore adalah platform open-source yang menggabungkan: - **Testing Aksesibilitas** dengan axe-core -- **Web Crawling Cerdas** menggunakan Puppeteer +- **Web Crawling Cerdas** menggunakan Playwright - **Analisis Berbasis AI** dengan OpenAI (opsional) - **Pelaporan Komprehensif** dalam format HTML dan JSON diff --git a/docs/id/index.md b/docs/id/index.md index eaebe99..80b6218 100644 --- a/docs/id/index.md +++ b/docs/id/index.md @@ -35,7 +35,7 @@ features: - icon: ⚡ title: Performa Tinggi - details: Dibangun dengan TypeScript dan Puppeteer untuk testing yang cepat dan reliable dalam skala besar + details: Dibangun dengan TypeScript dan Playwright untuk testing yang cepat dan reliable dalam skala besar - icon: 🌐 title: Multi-Bahasa diff --git a/docs/public/openapi.json b/docs/public/openapi.json index 117e8e3..084cd67 100644 --- a/docs/public/openapi.json +++ b/docs/public/openapi.json @@ -103,7 +103,7 @@ "networkidle0", "networkidle2" ], - "description": "Puppeteer navigation wait condition", + "description": "Playwright navigation wait condition", "example": "networkidle2" } }, diff --git a/package-lock.json b/package-lock.json index 85e3c8f..3e5d11b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "@accesstime/lenscore", "version": "0.1.68", + "hasInstallScript": true, "license": "MIT", "dependencies": { "@google-cloud/storage": "^7.7.0", @@ -27,7 +28,7 @@ "marked": "^16.4.1", "openai": "^6.5.0", "ora": "^5.4.1", - "puppeteer": "^24.15.0", + "playwright": "^1.57.0", "sharp": "^0.33.0", "uuid": "^9.0.1", "winston": "^3.11.0", @@ -330,6 +331,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", @@ -501,6 +503,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -3356,27 +3359,6 @@ "@noble/hashes": "^1.1.5" } }, - "node_modules/@puppeteer/browsers": { - "version": "2.10.12", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.12.tgz", - "integrity": "sha512-mP9iLFZwH+FapKJLeA7/fLqOlSUwYpMwjR1P5J23qd4e7qGJwecJccJqHYrjw33jmIZYV4dtiTHPD/J+1e7cEw==", - "license": "Apache-2.0", - "dependencies": { - "debug": "^4.4.3", - "extract-zip": "^2.0.1", - "progress": "^2.0.3", - "proxy-agent": "^6.5.0", - "semver": "^7.7.3", - "tar-fs": "^3.1.1", - "yargs": "^17.7.2" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.52.5", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", @@ -3872,12 +3854,6 @@ "node": ">= 10" } }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "license": "MIT" - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -4348,16 +4324,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.46.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz", @@ -5036,6 +5002,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, "license": "Python-2.0" }, "node_modules/array-flatten": { @@ -5067,18 +5034,6 @@ "dev": true, "license": "MIT" }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -5155,20 +5110,6 @@ "node": ">=4" } }, - "node_modules/b4a": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", - "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", - "license": "Apache-2.0", - "peerDependencies": { - "react-native-b4a": "*" - }, - "peerDependenciesMeta": { - "react-native-b4a": { - "optional": true - } - } - }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -5302,97 +5243,6 @@ "dev": true, "license": "MIT" }, - "node_modules/bare-events": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.1.tgz", - "integrity": "sha512-oxSAxTS1hRfnyit2CL5QpAOS5ixfBjj6ex3yTNvXyY/kE719jQ/IjuESJBK2w5v4wwQRAHGseVJXx9QBYOtFGQ==", - "license": "Apache-2.0", - "peerDependencies": { - "bare-abort-controller": "*" - }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - } - } - }, - "node_modules/bare-fs": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.0.tgz", - "integrity": "sha512-GljgCjeupKZJNetTqxKaQArLK10vpmK28or0+RwWjEl5Rk+/xG3wkpmkv+WrcBm3q1BwHKlnhXzR8O37kcvkXQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4", - "bare-url": "^2.2.2", - "fast-fifo": "^1.3.2" - }, - "engines": { - "bare": ">=1.16.0" - }, - "peerDependencies": { - "bare-buffer": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } - } - }, - "node_modules/bare-os": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", - "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "bare": ">=1.14.0" - } - }, - "node_modules/bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-os": "^3.0.1" - } - }, - "node_modules/bare-stream": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", - "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "streamx": "^2.21.0" - }, - "peerDependencies": { - "bare-buffer": "*", - "bare-events": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } - } - }, - "node_modules/bare-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", - "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-path": "^3.0.0" - } - }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -5423,15 +5273,6 @@ "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/basic-ftp": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", - "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", @@ -5647,15 +5488,6 @@ "isarray": "^1.0.0" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -5729,6 +5561,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5910,19 +5743,6 @@ "node": ">= 6" } }, - "node_modules/chromium-bidi": { - "version": "10.5.1", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-10.5.1.tgz", - "integrity": "sha512-rlj6OyhKhVTnk4aENcUme3Jl9h+cq4oXu4AzBcvr8RMmT6BR4a3zSNT9dbIfXr9/BS6ibzRyDhowuw4n2GgzsQ==", - "license": "Apache-2.0", - "dependencies": { - "mitt": "^3.0.1", - "zod": "^3.24.1" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -5983,6 +5803,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -5997,6 +5818,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -6275,6 +6097,7 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.1", @@ -6400,15 +6223,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -6487,20 +6301,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "license": "MIT", - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -6581,12 +6381,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/devtools-protocol": { - "version": "0.0.1521046", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1521046.tgz", - "integrity": "sha512-vhE6eymDQSKWUXwwA37NtTTVEzjtGVfDr3pRbsWEQ5onH/Snp2c+2xZHWJJawG/0hCCJLRGt4xVtEVUVILol4w==", - "license": "BSD-3-Clause" - }, "node_modules/dezalgo": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", @@ -6815,6 +6609,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6824,6 +6619,7 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -6920,6 +6716,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6944,27 +6741,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, "node_modules/eslint": { "version": "9.39.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.0.tgz", @@ -7137,6 +6913,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", @@ -7176,6 +6953,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -7192,6 +6970,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -7224,15 +7003,6 @@ "node": ">=0.4.x" } }, - "node_modules/events-universal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", - "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", - "license": "Apache-2.0", - "dependencies": { - "bare-events": "^2.7.0" - } - }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -7357,41 +7127,6 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extract-zip/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7399,12 +7134,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "license": "MIT" - }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -7511,15 +7240,6 @@ "bser": "2.1.1" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", @@ -7791,6 +7511,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -7869,20 +7590,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", - "license": "MIT", - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/git-raw-commits": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", @@ -8273,19 +7980,6 @@ "node": ">= 0.8" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -8364,6 +8058,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -8495,15 +8190,6 @@ "url": "https://opencollective.com/ioredis" } }, - "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -8533,6 +8219,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, "license": "MIT" }, "node_modules/is-binary-path": { @@ -9439,12 +9126,14 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -9486,6 +9175,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { @@ -9617,6 +9307,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, "license": "MIT" }, "node_modules/locate-path": { @@ -10096,6 +9787,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, "license": "MIT" }, "node_modules/ms": { @@ -10154,15 +9846,6 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "license": "MIT" }, - "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -10489,42 +10172,11 @@ "node": ">=6" } }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "license": "MIT", - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "license": "MIT", - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -10537,6 +10189,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", @@ -10652,12 +10305,6 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "license": "MIT" - }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -10669,6 +10316,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -10763,6 +10411,50 @@ "node": ">=8" } }, + "node_modules/playwright": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -10866,15 +10558,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -10913,40 +10596,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -10954,16 +10603,6 @@ "dev": true, "license": "MIT" }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -10974,45 +10613,6 @@ "node": ">=6" } }, - "node_modules/puppeteer": { - "version": "24.27.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.27.0.tgz", - "integrity": "sha512-eEcAFGxmHRSrk74DVkFAMAwfj4l3Ak8avBuA2bZaAoocY1+Fb9WLS3I7jlOc/tIOU7EmGLiDdVP08R44wADpHw==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "2.10.12", - "chromium-bidi": "10.5.1", - "cosmiconfig": "^9.0.0", - "devtools-protocol": "0.0.1521046", - "puppeteer-core": "24.27.0", - "typed-query-selector": "^2.12.0" - }, - "bin": { - "puppeteer": "lib/cjs/puppeteer/node/cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/puppeteer-core": { - "version": "24.27.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.27.0.tgz", - "integrity": "sha512-yubwj2XXmTM3wRIpbhO5nCjbByPgpFHlgrsD4IK+gMPqO7/a5FfnoSXDKjmqi8A2M1Ewusz0rTI/r+IN0GU0MA==", - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "2.10.12", - "chromium-bidi": "10.5.1", - "debug": "^4.4.3", - "devtools-protocol": "0.0.1521046", - "typed-query-selector": "^2.12.0", - "webdriver-bidi-protocol": "0.3.8", - "ws": "^8.18.3" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -11197,6 +10797,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11260,6 +10861,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -11813,44 +11415,6 @@ "node": ">=8" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "license": "MIT", - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -11981,17 +11545,6 @@ "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", "license": "MIT" }, - "node_modules/streamx": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", - "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", - "license": "MIT", - "dependencies": { - "events-universal": "^1.0.0", - "fast-fifo": "^1.3.2", - "text-decoder": "^1.1.0" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -12210,31 +11763,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tar-fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", - "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", - "license": "MIT", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" - } - }, - "node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "license": "MIT", - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, "node_modules/teeny-request": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", @@ -12329,15 +11857,6 @@ "node": "*" } }, - "node_modules/text-decoder": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", - "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", - "license": "Apache-2.0", - "dependencies": { - "b4a": "^1.6.4" - } - }, "node_modules/text-extensions": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", @@ -12590,17 +12109,11 @@ "node": ">= 0.6" } }, - "node_modules/typed-query-selector": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", - "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", - "license": "MIT" - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -13465,12 +12978,6 @@ "defaults": "^1.0.3" } }, - "node_modules/webdriver-bidi-protocol": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.3.8.tgz", - "integrity": "sha512-21Yi2GhGntMc671vNBCjiAeEVknXjVRoyu+k+9xOMShu+ZQfpGQwnBqbNz/Sv4GXZ6JmutlPAi2nIJcrymAWuQ==", - "license": "Apache-2.0" - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -13638,27 +13145,6 @@ "dev": true, "license": "ISC" }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/xml2js": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", @@ -13685,6 +13171,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -13701,6 +13188,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -13719,21 +13207,12 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" } }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index b743306..b637083 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@accesstime/lenscore", - "version": "0.1.68", + "version": "0.1.77", "description": "Open-source accessibility testing and web crawling platform", "main": "dist/index.js", "bin": { @@ -24,6 +24,7 @@ "format:check": "prettier -c .", "typecheck": "tsc --noEmit", "prepare": "husky", + "postinstall": "playwright install chromium", "docs:dev": "vitepress dev docs", "docs:build": "vitepress build docs", "docs:preview": "vitepress preview docs" @@ -56,7 +57,7 @@ "marked": "^16.4.1", "openai": "^6.5.0", "ora": "^5.4.1", - "puppeteer": "^24.15.0", + "playwright": "^1.57.0", "sharp": "^0.33.0", "uuid": "^9.0.1", "winston": "^3.11.0", diff --git a/src/api/openapi.ts b/src/api/openapi.ts index d3f6986..b1d779d 100644 --- a/src/api/openapi.ts +++ b/src/api/openapi.ts @@ -99,7 +99,7 @@ export const openApiSpec = { 'networkidle0', 'networkidle2', ], - description: 'Puppeteer navigation wait condition', + description: 'Playwright navigation wait condition', example: 'networkidle2', }, }, diff --git a/src/api/routes/web.ts b/src/api/routes/web.ts index 40f55ae..6f0702b 100644 --- a/src/api/routes/web.ts +++ b/src/api/routes/web.ts @@ -12,14 +12,16 @@ const router = Router(); */ function findWebOutputDir(): string { const possiblePaths = [ + // Docker container - mounted from ~/.lenscore/web + path.join('/app', '.lenscore', 'web', 'output'), + // Docker container - from app directory + path.join('/app', 'web', 'output'), // User's home directory (preferred for global usage) path.join(os.homedir(), '.lenscore', 'web', 'output'), // Current working directory with .lenscore path.join(process.cwd(), '.lenscore', 'web', 'output'), // Development mode - from source path.join(process.cwd(), 'web', 'output'), - // Docker container - from app directory - path.join('/app', 'web', 'output'), ]; // Try to add global install path if available diff --git a/src/cli/services/utils/docker-config.ts b/src/cli/services/utils/docker-config.ts index 5c1925d..839db90 100644 --- a/src/cli/services/utils/docker-config.ts +++ b/src/cli/services/utils/docker-config.ts @@ -84,7 +84,7 @@ export class DockerConfigService { - ./cache:/app/cache - ./web:/app/web - ./storage:/app/storage - - node_modules_data:/app/node_modules + - \${HOME}/.lenscore/web:/app/.lenscore/web depends_on: - redis @@ -102,45 +102,63 @@ volumes: } private getDockerfileContent(): string { - return `FROM node:20-alpine AS builder - + return `# --------------------------- +# Builder Stage +# --------------------------- +FROM node:22-alpine AS builder WORKDIR /app +RUN apk update && apk add --no-cache \\ + bash \\ + chromium \\ + chromium-chromedriver \\ + nss \\ + freetype \\ + harfbuzz \\ + ca-certificates \\ + ttf-freefont \\ + && apk add --no-cache --virtual .build-deps \\ + gcc g++ make python3 && \\ + npm install -g cross-env + COPY package*.json ./ +RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi -RUN npm install +RUN npm install playwright -COPY . . +COPY tsconfig*.json ./ +COPY src ./src RUN npm run build +RUN npm run build:cli -FROM node:20-alpine AS production - +# --------------------------- +# Runtime Stage +# --------------------------- +FROM node:22-alpine AS runtime WORKDIR /app -RUN apk add --no-cache \\ - chromium \\ - nss \\ - freetype \\ - freetype-dev \\ - harfbuzz \\ - ca-certificates \\ - ttf-freefont - -ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \\ - PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser \\ +RUN apk update && apk add --no-cache \\ + chromium \\ + chromium-chromedriver \\ + nss \\ + freetype \\ + harfbuzz \\ + ca-certificates \\ + ttf-freefont + +ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \\ + PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser \\ NODE_ENV=production COPY package*.json ./ -RUN npm install --production - +COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/dist ./dist +COPY --from=builder /app/src/data ./src/data RUN mkdir -p logs storage -EXPOSE 3001 - -CMD ["npm", "start"]`; +CMD ["node", "dist/index.js"]`; } private async setupPackageFiles(lenscoreDir: string): Promise { @@ -152,13 +170,38 @@ CMD ["npm", "start"]`; JSON.stringify(packageJsonContent, null, 2) ); + const possiblePackageDirs: string[] = [ + process.cwd(), + path.resolve(__filename, '../../../../'), + ]; + try { - const packageDir = await this.findPackageDirectory(); - await this.copyConfigFiles(packageDir, lenscoreDir); - await this.copySourceFiles(packageDir, lenscoreDir); - await this.copyWebTemplates(packageDir, lenscoreDir); - } catch (error) { - console.warn(`⚠️ Package setup warning: ${error}`); + const packagePath = require.resolve('@accesstime/lenscore'); + possiblePackageDirs.push( + path.resolve(packagePath, '../..'), + path.dirname(packagePath) + ); + } catch { + // + } + + let packageDirFound = false; + for (const packageDir of possiblePackageDirs) { + try { + const packageJsonPath = path.join(packageDir, 'package.json'); + await fs.access(packageJsonPath); + await this.copyConfigFiles(packageDir, lenscoreDir); + await this.copySourceFiles(packageDir, lenscoreDir); + await this.copyWebTemplates(packageDir, lenscoreDir); + packageDirFound = true; + break; + } catch { + // + } + } + + if (!packageDirFound) { + console.warn('⚠️ Could not find package directory, some files may be missing'); } } @@ -214,7 +257,7 @@ CMD ["npm", "start"]`; marked: '^16.4.1', openai: '^6.5.0', ora: '^5.4.1', - puppeteer: '^24.15.0', + playwright: '^1.48.0', sharp: '^0.33.0', uuid: '^9.0.1', winston: '^3.11.0', @@ -245,34 +288,6 @@ CMD ["npm", "start"]`; }; } - private async findPackageDirectory(): Promise { - const possiblePackageDirs: string[] = [ - path.resolve(__filename, '../../../../'), - ]; - - // Try to add global install paths if available - try { - const packagePath = require.resolve('@accesstime/lenscore'); - possiblePackageDirs.push( - path.resolve(packagePath, '../..'), - path.dirname(packagePath) - ); - } catch { - // Package not found, skip this path (development mode) - } - - for (const dir of possiblePackageDirs) { - try { - const packageJsonPath = path.join(dir, 'package.json'); - await fs.access(packageJsonPath); - return dir; - } catch { - // - } - } - - throw new Error('Could not find package directory'); - } private async copyConfigFiles( packageDir: string, @@ -283,18 +298,24 @@ CMD ["npm", "start"]`; try { const srcPath = path.join(packageDir, tsconfigFile); const destPath = path.join(lenscoreDir, tsconfigFile); + await fs.access(srcPath); await fs.copyFile(srcPath, destPath); } catch { // } } - try { - const packageLockSrc = path.join(packageDir, 'package-lock.json'); - const packageLockDest = path.join(lenscoreDir, 'package-lock.json'); - await fs.copyFile(packageLockSrc, packageLockDest); - } catch { - // + const packageLockDest = path.join(lenscoreDir, 'package-lock.json'); + + for (const possibleDir of [packageDir, process.cwd(), path.resolve(__filename, '../../../../')]) { + try { + const packageLockSrc = path.join(possibleDir, 'package-lock.json'); + await fs.access(packageLockSrc); + await fs.copyFile(packageLockSrc, packageLockDest); + console.log(`✅ Copied package-lock.json from ${possibleDir}`); + } catch { + // + } } } diff --git a/src/cli/services/utils/docker-operations.ts b/src/cli/services/utils/docker-operations.ts index 2a822d0..2975c60 100644 --- a/src/cli/services/utils/docker-operations.ts +++ b/src/cli/services/utils/docker-operations.ts @@ -118,8 +118,21 @@ export class DockerOperationsService { try { const portInUse = await this.validationService.validatePort(this.port); if (portInUse) { - spinner.succeed('Port already in use - services likely running'); - return; + try { + const healthResponse = await fetch(`http://localhost:${this.port}/api/health`, { + method: 'GET', + signal: AbortSignal.timeout(3000), + }); + if (healthResponse.ok) { + spinner.succeed('Port already in use - services likely running'); + return; + } + } catch { + spinner.text = 'Port in use but service not responding, rebuilding...'; + await this.stop(); + await this.build(); + return; + } } const imageExists = await this.validationService.validateImage(); diff --git a/src/cli/utils/command-utils.ts b/src/cli/utils/command-utils.ts index e36c5ee..2ff7480 100644 --- a/src/cli/utils/command-utils.ts +++ b/src/cli/utils/command-utils.ts @@ -28,7 +28,17 @@ export class CommandUtils { await dockerService.ensureServicesReady(); spinner.text = 'Waiting for LensCore to be ready...'; - await client.waitForReady(); + await client.waitForReady(60000); + } else { + spinner.text = 'Verifying LensCore is fully ready...'; + try { + await client.waitForReady(10000); + } catch { + spinner.text = 'Service responding but may need restart, rebuilding...'; + const dockerService = await this.getDockerService(); + await dockerService.ensureServicesReady(); + await client.waitForReady(60000); + } } spinner.succeed('LensCore ready'); diff --git a/src/services/accessibility.ts b/src/services/accessibility.ts index ca48c5c..9d17d24 100644 --- a/src/services/accessibility.ts +++ b/src/services/accessibility.ts @@ -1,4 +1,4 @@ -import puppeteer, { Browser } from 'puppeteer'; +import { chromium, Browser } from 'playwright'; import { v4 as uuidv4 } from 'uuid'; import fs from 'fs/promises'; import { @@ -24,10 +24,18 @@ export class AccessibilityService { } async initialize(): Promise { - this.browser = await puppeteer.launch({ + const executablePath = process.env['PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH']; + + const launchOptions: any = { headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'], - }); + }; + + if (executablePath) { + launchOptions.executablePath = executablePath; + } + + this.browser = await chromium.launch(launchOptions); } async close(): Promise { @@ -234,7 +242,11 @@ export class AccessibilityService { } } catch (error) { clearTimeout(timeoutId); - logger.error('Test accessibility error:', { error }); + logger.error('Test accessibility error:', { + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined, + url: request.url, + }); return this.getMockResult(request); } } diff --git a/src/services/crawling.ts b/src/services/crawling.ts index e970eda..223dca3 100644 --- a/src/services/crawling.ts +++ b/src/services/crawling.ts @@ -1,4 +1,4 @@ -import puppeteer, { Browser } from 'puppeteer'; +import { chromium, Browser } from 'playwright'; import * as cheerio from 'cheerio'; import { CrawlRequest, CrawlResponse, CrawlResult, CrawlRules } from '../types'; import { env } from '../utils/env'; @@ -17,7 +17,9 @@ export class CrawlingService { async initialize(): Promise { try { - this.browser = await puppeteer.launch({ + const executablePath = process.env['PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH']; + + const launchOptions: any = { headless: true, args: [ '--no-sandbox', @@ -32,7 +34,13 @@ export class CrawlingService { '--disable-renderer-backgrounding', ], timeout: 30000, - }); + }; + + if (executablePath) { + launchOptions.executablePath = executablePath; + } + + this.browser = await chromium.launch(launchOptions); logger.info('Browser initialized successfully'); } catch (error) { @@ -136,14 +144,11 @@ export class CrawlingService { page = await this.browser!.newPage(); page.setDefaultTimeout(Math.min(timeout, 15000)); - await page.setViewport({ width: 1280, height: 720 }); - await page.setUserAgent( - 'Mozilla/5.0 (compatible; LensCore/1.0; +https://github.com/accesslens/lenscore)' - ); - - if (request.headers) { - await page.setExtraHTTPHeaders(request.headers); - } + await page.setViewportSize({ width: 1280, height: 720 }); + await page.setExtraHTTPHeaders({ + 'User-Agent': 'Mozilla/5.0 (compatible; LensCore/1.0; +https://github.com/accesslens/lenscore)', + ...(request.headers || {}), + }); const pageController = new AbortController(); const pageTimeoutId = setTimeout( @@ -156,17 +161,24 @@ export class CrawlingService { try { const response = await page.goto(url, { waitUntil: - request.waitUntil || - (env.CRAWL_WAIT_UNTIL as - | 'domcontentloaded' - | 'networkidle0' - | 'networkidle2'), + request.waitUntil === 'networkidle0' + ? 'networkidle' + : request.waitUntil === 'networkidle2' + ? 'networkidle' + : request.waitUntil === 'domcontentloaded' + ? 'domcontentloaded' + : (env.CRAWL_WAIT_UNTIL === 'networkidle0' || + env.CRAWL_WAIT_UNTIL === 'networkidle2' + ? 'networkidle' + : env.CRAWL_WAIT_UNTIL === 'domcontentloaded' + ? 'domcontentloaded' + : 'load'), timeout: Math.min(timeout, 15000), }); clearTimeout(pageTimeoutId); - const statusCode = response?.status() || 0; + const statusCode = response?.status() ?? 0; if (statusCode >= 400) { logger.warn('HTTP error status', { url, statusCode }); From 45536932c367deae259fe4ec955997c99eee5892 Mon Sep 17 00:00:00 2001 From: Caknoooo Date: Wed, 26 Nov 2025 14:04:33 +0700 Subject: [PATCH 2/4] style: running formatter --- docker-compose.yml | 6 ++--- src/cli/services/utils/docker-config.ts | 13 +++++++--- src/cli/services/utils/docker-operations.ts | 14 +++++++---- src/cli/utils/command-utils.ts | 3 ++- src/services/accessibility.ts | 8 +++--- src/services/crawling.ts | 27 +++++++++++---------- 6 files changed, 41 insertions(+), 30 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index d1872da..8550a5c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: dockerfile: Dockerfile volumes: - node_modules_data:/app/node_modules - profiles: ["init"] + profiles: ['init'] lenscore: container_name: lenscore-app @@ -13,7 +13,7 @@ services: context: . dockerfile: Dockerfile ports: - - "${LENSCORE_PORT:-3001}:${LENSCORE_PORT:-3001}" + - '${LENSCORE_PORT:-3001}:${LENSCORE_PORT:-3001}' environment: - NODE_ENV=development - PORT=${LENSCORE_PORT:-3001} @@ -41,7 +41,7 @@ services: redis: image: redis:7-alpine ports: - - "6379:6379" + - '6379:6379' volumes: - redis_data:/data command: redis-server --appendonly yes diff --git a/src/cli/services/utils/docker-config.ts b/src/cli/services/utils/docker-config.ts index 839db90..0810126 100644 --- a/src/cli/services/utils/docker-config.ts +++ b/src/cli/services/utils/docker-config.ts @@ -201,7 +201,9 @@ CMD ["node", "dist/index.js"]`; } if (!packageDirFound) { - console.warn('⚠️ Could not find package directory, some files may be missing'); + console.warn( + '⚠️ Could not find package directory, some files may be missing' + ); } } @@ -288,7 +290,6 @@ CMD ["node", "dist/index.js"]`; }; } - private async copyConfigFiles( packageDir: string, lenscoreDir: string @@ -306,8 +307,12 @@ CMD ["node", "dist/index.js"]`; } const packageLockDest = path.join(lenscoreDir, 'package-lock.json'); - - for (const possibleDir of [packageDir, process.cwd(), path.resolve(__filename, '../../../../')]) { + + for (const possibleDir of [ + packageDir, + process.cwd(), + path.resolve(__filename, '../../../../'), + ]) { try { const packageLockSrc = path.join(possibleDir, 'package-lock.json'); await fs.access(packageLockSrc); diff --git a/src/cli/services/utils/docker-operations.ts b/src/cli/services/utils/docker-operations.ts index 2975c60..0919e13 100644 --- a/src/cli/services/utils/docker-operations.ts +++ b/src/cli/services/utils/docker-operations.ts @@ -119,16 +119,20 @@ export class DockerOperationsService { const portInUse = await this.validationService.validatePort(this.port); if (portInUse) { try { - const healthResponse = await fetch(`http://localhost:${this.port}/api/health`, { - method: 'GET', - signal: AbortSignal.timeout(3000), - }); + const healthResponse = await fetch( + `http://localhost:${this.port}/api/health`, + { + method: 'GET', + signal: AbortSignal.timeout(3000), + } + ); if (healthResponse.ok) { spinner.succeed('Port already in use - services likely running'); return; } } catch { - spinner.text = 'Port in use but service not responding, rebuilding...'; + spinner.text = + 'Port in use but service not responding, rebuilding...'; await this.stop(); await this.build(); return; diff --git a/src/cli/utils/command-utils.ts b/src/cli/utils/command-utils.ts index 2ff7480..ea0300f 100644 --- a/src/cli/utils/command-utils.ts +++ b/src/cli/utils/command-utils.ts @@ -34,7 +34,8 @@ export class CommandUtils { try { await client.waitForReady(10000); } catch { - spinner.text = 'Service responding but may need restart, rebuilding...'; + spinner.text = + 'Service responding but may need restart, rebuilding...'; const dockerService = await this.getDockerService(); await dockerService.ensureServicesReady(); await client.waitForReady(60000); diff --git a/src/services/accessibility.ts b/src/services/accessibility.ts index 9d17d24..6ee50b4 100644 --- a/src/services/accessibility.ts +++ b/src/services/accessibility.ts @@ -1,4 +1,4 @@ -import { chromium, Browser } from 'playwright'; +import { chromium, Browser, LaunchOptions } from 'playwright'; import { v4 as uuidv4 } from 'uuid'; import fs from 'fs/promises'; import { @@ -25,8 +25,8 @@ export class AccessibilityService { async initialize(): Promise { const executablePath = process.env['PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH']; - - const launchOptions: any = { + + const launchOptions: LaunchOptions = { headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'], }; @@ -34,7 +34,7 @@ export class AccessibilityService { if (executablePath) { launchOptions.executablePath = executablePath; } - + this.browser = await chromium.launch(launchOptions); } diff --git a/src/services/crawling.ts b/src/services/crawling.ts index 223dca3..7040f73 100644 --- a/src/services/crawling.ts +++ b/src/services/crawling.ts @@ -1,4 +1,4 @@ -import { chromium, Browser } from 'playwright'; +import { chromium, Browser, LaunchOptions } from 'playwright'; import * as cheerio from 'cheerio'; import { CrawlRequest, CrawlResponse, CrawlResult, CrawlRules } from '../types'; import { env } from '../utils/env'; @@ -18,8 +18,8 @@ export class CrawlingService { async initialize(): Promise { try { const executablePath = process.env['PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH']; - - const launchOptions: any = { + + const launchOptions: LaunchOptions = { headless: true, args: [ '--no-sandbox', @@ -39,7 +39,7 @@ export class CrawlingService { if (executablePath) { launchOptions.executablePath = executablePath; } - + this.browser = await chromium.launch(launchOptions); logger.info('Browser initialized successfully'); @@ -146,7 +146,8 @@ export class CrawlingService { await page.setViewportSize({ width: 1280, height: 720 }); await page.setExtraHTTPHeaders({ - 'User-Agent': 'Mozilla/5.0 (compatible; LensCore/1.0; +https://github.com/accesslens/lenscore)', + 'User-Agent': + 'Mozilla/5.0 (compatible; LensCore/1.0; +https://github.com/accesslens/lenscore)', ...(request.headers || {}), }); @@ -164,15 +165,15 @@ export class CrawlingService { request.waitUntil === 'networkidle0' ? 'networkidle' : request.waitUntil === 'networkidle2' - ? 'networkidle' - : request.waitUntil === 'domcontentloaded' - ? 'domcontentloaded' - : (env.CRAWL_WAIT_UNTIL === 'networkidle0' || - env.CRAWL_WAIT_UNTIL === 'networkidle2' - ? 'networkidle' - : env.CRAWL_WAIT_UNTIL === 'domcontentloaded' + ? 'networkidle' + : request.waitUntil === 'domcontentloaded' ? 'domcontentloaded' - : 'load'), + : env.CRAWL_WAIT_UNTIL === 'networkidle0' || + env.CRAWL_WAIT_UNTIL === 'networkidle2' + ? 'networkidle' + : env.CRAWL_WAIT_UNTIL === 'domcontentloaded' + ? 'domcontentloaded' + : 'load', timeout: Math.min(timeout, 15000), }); From 46d1cdff9687a2213e234306268a514b7abc8404 Mon Sep 17 00:00:00 2001 From: Caknoooo Date: Fri, 28 Nov 2025 09:05:56 +0700 Subject: [PATCH 3/4] refactor: change approach for docker --- .dockerignore | 8 + Dockerfile | 9 +- docker-compose.yml | 8 +- package.json | 2 +- src/api/routes/web.ts | 2 +- src/cli/services/utils/docker-config.ts | 246 +++++------------------- 6 files changed, 62 insertions(+), 213 deletions(-) diff --git a/.dockerignore b/.dockerignore index 3ce09d2..37255b2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,3 +8,11 @@ coverage .cache .DS_Store *.log +node_modules +dist +tests +.vscode +.idea +*.swp +*.swo +*~ diff --git a/Dockerfile b/Dockerfile index 93c60ca..5e8efb4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,12 +18,9 @@ RUN apk update && apk add --no-cache \ npm install -g cross-env COPY package*.json ./ -RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi +RUN npm ci -RUN npm install playwright - -COPY tsconfig*.json ./ -COPY src ./src +COPY . . RUN npm run build RUN npm run build:cli @@ -54,4 +51,4 @@ COPY --from=builder /app/src/data ./src/data RUN mkdir -p logs storage -CMD ["node", "dist/index.js"] +CMD ["npm", "start"] diff --git a/docker-compose.yml b/docker-compose.yml index 8550a5c..fb4a738 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: dockerfile: Dockerfile volumes: - node_modules_data:/app/node_modules - profiles: ['init'] + profiles: ["init"] lenscore: container_name: lenscore-app @@ -13,7 +13,7 @@ services: context: . dockerfile: Dockerfile ports: - - '${LENSCORE_PORT:-3001}:${LENSCORE_PORT:-3001}' + - "${LENSCORE_PORT:-3001}:${LENSCORE_PORT:-3001}" environment: - NODE_ENV=development - PORT=${LENSCORE_PORT:-3001} @@ -34,14 +34,14 @@ services: - ./cache:/app/cache - ./web:/app/web - ./storage:/app/storage - - ${HOME}/.lenscore/web:/app/.lenscore/web + - ./.lenscore/web:/app/.lenscore/web depends_on: - redis redis: image: redis:7-alpine ports: - - '6379:6379' + - "6379:6379" volumes: - redis_data:/data command: redis-server --appendonly yes diff --git a/package.json b/package.json index b637083..5467595 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@accesstime/lenscore", - "version": "0.1.77", + "version": "0.1.68", "description": "Open-source accessibility testing and web crawling platform", "main": "dist/index.js", "bin": { diff --git a/src/api/routes/web.ts b/src/api/routes/web.ts index 6f0702b..38b64d5 100644 --- a/src/api/routes/web.ts +++ b/src/api/routes/web.ts @@ -12,7 +12,7 @@ const router = Router(); */ function findWebOutputDir(): string { const possiblePaths = [ - // Docker container - mounted from ~/.lenscore/web + // Docker container - mounted from ./.lenscore/web (project directory) path.join('/app', '.lenscore', 'web', 'output'), // Docker container - from app directory path.join('/app', 'web', 'output'), diff --git a/src/cli/services/utils/docker-config.ts b/src/cli/services/utils/docker-config.ts index 0810126..2bfa998 100644 --- a/src/cli/services/utils/docker-config.ts +++ b/src/cli/services/utils/docker-config.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable no-console */ import path from 'path'; import { promises as fs } from 'fs'; @@ -49,127 +48,50 @@ export class DockerConfigService { const composePath = path.join(lenscoreDir, 'docker-compose.yml'); const dockerfilePath = path.join(lenscoreDir, 'Dockerfile'); - const dockerComposeContent = this.getDockerComposeContent(); - const dockerfileContent = this.getDockerfileContent(); + const possiblePackageDirs: string[] = [ + process.cwd(), + path.resolve(__filename, '../../../../'), + ]; - await fs.writeFile(composePath, dockerComposeContent); - await fs.writeFile(dockerfilePath, dockerfileContent); - } + try { + const packagePath = require.resolve('@accesstime/lenscore'); + possiblePackageDirs.push( + path.resolve(packagePath, '../..'), + path.dirname(packagePath) + ); + } catch { + // + } - private getDockerComposeContent(): string { - return `services: - lenscore-init: - build: - context: . - dockerfile: Dockerfile - volumes: - - node_modules_data:/app/node_modules - profiles: ['init'] - - lenscore: - container_name: lenscore-app - build: - context: . - dockerfile: Dockerfile - ports: - - '\${LENSCORE_PORT:-3001}:\${LENSCORE_PORT:-3001}' - environment: - - NODE_ENV=development - - PORT=\${LENSCORE_PORT:-3001} - - CACHE_TYPE=redis - - REDIS_HOST=redis - - REDIS_PORT=6379 - volumes: - - ./logs:/app/logs - - ./cache:/app/cache - - ./web:/app/web - - ./storage:/app/storage - - \${HOME}/.lenscore/web:/app/.lenscore/web - depends_on: - - redis - - redis: - image: redis:7-alpine - ports: - - '6379:6379' - volumes: - - redis_data:/data - command: redis-server --appendonly yes - -volumes: - redis_data: - node_modules_data:`; - } + let dockerFilesCopied = false; + for (const packageDir of possiblePackageDirs) { + try { + const srcComposePath = path.join(packageDir, 'docker-compose.yml'); + const srcDockerfilePath = path.join(packageDir, 'Dockerfile'); + + await fs.access(srcComposePath); + await fs.access(srcDockerfilePath); + + await fs.copyFile(srcComposePath, composePath); + await fs.copyFile(srcDockerfilePath, dockerfilePath); + + console.log(`✅ Copied Docker files from ${packageDir}`); + dockerFilesCopied = true; + break; + } catch { + // + } + } - private getDockerfileContent(): string { - return `# --------------------------- -# Builder Stage -# --------------------------- -FROM node:22-alpine AS builder -WORKDIR /app - -RUN apk update && apk add --no-cache \\ - bash \\ - chromium \\ - chromium-chromedriver \\ - nss \\ - freetype \\ - harfbuzz \\ - ca-certificates \\ - ttf-freefont \\ - && apk add --no-cache --virtual .build-deps \\ - gcc g++ make python3 && \\ - npm install -g cross-env - -COPY package*.json ./ -RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi - -RUN npm install playwright - -COPY tsconfig*.json ./ -COPY src ./src - -RUN npm run build -RUN npm run build:cli - -# --------------------------- -# Runtime Stage -# --------------------------- -FROM node:22-alpine AS runtime -WORKDIR /app - -RUN apk update && apk add --no-cache \\ - chromium \\ - chromium-chromedriver \\ - nss \\ - freetype \\ - harfbuzz \\ - ca-certificates \\ - ttf-freefont - -ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \\ - PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser \\ - NODE_ENV=production - -COPY package*.json ./ -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/dist ./dist -COPY --from=builder /app/src/data ./src/data - -RUN mkdir -p logs storage - -CMD ["node", "dist/index.js"]`; + if (!dockerFilesCopied) { + throw new Error( + 'Could not find Dockerfile or docker-compose.yml in package directory. Please ensure the package is properly installed.' + ); + } } private async setupPackageFiles(lenscoreDir: string): Promise { const packageJsonPath = path.join(lenscoreDir, 'package.json'); - const packageJsonContent = await this.getPackageJsonContent(); - - await fs.writeFile( - packageJsonPath, - JSON.stringify(packageJsonContent, null, 2) - ); - const possiblePackageDirs: string[] = [ process.cwd(), path.resolve(__filename, '../../../../'), @@ -188,8 +110,12 @@ CMD ["node", "dist/index.js"]`; let packageDirFound = false; for (const packageDir of possiblePackageDirs) { try { - const packageJsonPath = path.join(packageDir, 'package.json'); - await fs.access(packageJsonPath); + const srcPackageJsonPath = path.join(packageDir, 'package.json'); + await fs.access(srcPackageJsonPath); + + await fs.copyFile(srcPackageJsonPath, packageJsonPath); + console.log(`✅ Copied package.json from ${packageDir}`); + await this.copyConfigFiles(packageDir, lenscoreDir); await this.copySourceFiles(packageDir, lenscoreDir); await this.copyWebTemplates(packageDir, lenscoreDir); @@ -201,94 +127,12 @@ CMD ["node", "dist/index.js"]`; } if (!packageDirFound) { - console.warn( - '⚠️ Could not find package directory, some files may be missing' - ); - } - } - - private async getPackageJsonContent(): Promise { - try { - const currentFile = __filename; - const packageDir = path.resolve(currentFile, '../../../../'); - const originalPackageJsonPath = path.join(packageDir, 'package.json'); - const originalPackageJson = await fs.readFile( - originalPackageJsonPath, - 'utf8' + throw new Error( + 'Could not find package.json in package directory. Please ensure the package is properly installed.' ); - const packageJsonContent = JSON.parse(originalPackageJson); - - packageJsonContent.scripts = { - start: 'node dist/index.js', - build: 'tsc', - 'build:cli': 'tsc -p tsconfig.cli.json', - }; - - return packageJsonContent; - } catch { - return this.getDefaultPackageJson(); } } - private getDefaultPackageJson(): any { - return { - name: 'lenscore', - version: '1.0.0', - main: 'dist/index.js', - scripts: { - start: 'node dist/index.js', - build: 'tsc', - 'build:cli': 'tsc -p tsconfig.cli.json', - }, - dependencies: { - '@google-cloud/storage': '^7.7.0', - '@types/inquirer': '^9.0.9', - '@types/ioredis': '^4.28.10', - 'aws-sdk': '^2.1490.0', - 'axe-core': '^4.8.2', - chalk: '^4.1.2', - cheerio: '^1.0.0', - commander: '^11.1.0', - cors: '^2.8.5', - dotenv: '^16.3.1', - express: '^4.18.2', - handlebars: '^4.7.8', - helmet: '^7.1.0', - inquirer: '^12.10.0', - ioredis: '^5.8.1', - marked: '^16.4.1', - openai: '^6.5.0', - ora: '^5.4.1', - playwright: '^1.48.0', - sharp: '^0.33.0', - uuid: '^9.0.1', - winston: '^3.11.0', - zod: '^3.22.4', - }, - devDependencies: { - '@types/cors': '^2.8.17', - '@types/express': '^4.17.21', - '@types/handlebars': '^4.0.40', - '@types/jest': '^29.5.8', - '@types/marked': '^5.0.2', - '@types/multer': '^1.4.11', - '@types/node': '^20.10.5', - '@types/supertest': '^2.0.16', - '@types/uuid': '^9.0.8', - '@typescript-eslint/eslint-plugin': '^8.15.0', - '@typescript-eslint/parser': '^8.15.0', - eslint: '^9.15.0', - globals: '^13.24.0', - jest: '^29.7.0', - nodemon: '^3.0.2', - prettier: '^3.1.1', - supertest: '^7.1.3', - 'ts-jest': '^29.1.1', - tsx: '^4.6.2', - typescript: '^5.3.3', - }, - }; - } private async copyConfigFiles( packageDir: string, From cadb2bd248a93a8ea70aa3a9ebfb3f1d5675d049 Mon Sep 17 00:00:00 2001 From: Caknoooo Date: Fri, 28 Nov 2025 09:06:17 +0700 Subject: [PATCH 4/4] style: run formatter --- docker-compose.yml | 6 +++--- src/cli/services/utils/docker-config.ts | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index fb4a738..983b28c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: dockerfile: Dockerfile volumes: - node_modules_data:/app/node_modules - profiles: ["init"] + profiles: ['init'] lenscore: container_name: lenscore-app @@ -13,7 +13,7 @@ services: context: . dockerfile: Dockerfile ports: - - "${LENSCORE_PORT:-3001}:${LENSCORE_PORT:-3001}" + - '${LENSCORE_PORT:-3001}:${LENSCORE_PORT:-3001}' environment: - NODE_ENV=development - PORT=${LENSCORE_PORT:-3001} @@ -41,7 +41,7 @@ services: redis: image: redis:7-alpine ports: - - "6379:6379" + - '6379:6379' volumes: - redis_data:/data command: redis-server --appendonly yes diff --git a/src/cli/services/utils/docker-config.ts b/src/cli/services/utils/docker-config.ts index 2bfa998..8c00e47 100644 --- a/src/cli/services/utils/docker-config.ts +++ b/src/cli/services/utils/docker-config.ts @@ -133,7 +133,6 @@ export class DockerConfigService { } } - private async copyConfigFiles( packageDir: string, lenscoreDir: string