diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e69de29..213ac7c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -0,0 +1,46 @@ +name: Code Executor Battle tester + +on: + push: + branches: + - main + pull_request: + branches: ["*"] + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" # Or your preferred Node.js version + + - name: Install Node.js dependencies + run: npm install + + - name: Set up Docker BuildX (for multi-platform builds, good practice) + uses: docker/setup-buildx-action@v3 + + - name: Build Docker images + # This builds all services defined in your docker-compose.yaml + # It's crucial to build them before running tests. + run: docker compose build + + - name: Run Vitest Battle Tests + run: echo "this will handle letter" + # # 1. Start all Docker Compose services (including your Node.js server and language executors). + # # 2. Run the Vitest tests, which will make HTTP requests to your Node.js server. + # # 3. The tests will internally use `docker run --rm` against your language executor images. + # # 4. After tests complete, the `afterAll` hook in your tests will tear down Docker Compose. + # env: + # # Pass HOST_PROJECT_ROOT to your Node.js server if it needs it during tests. + # # In GitHub Actions, GITHUB_WORKSPACE is the root of your checked-out repo. + # HOST_PROJECT_ROOT: ${{ github.workspace }} + # # Ensure your Node.js server uses the correct port for testing + # PORT: 9091 + # run: npm test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..41587db --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,5 @@ +## Contributing rules will be here + +1. Go to the language config file from the `root/language/config.js` +2. Add your langage as we add others language + - Make sure your `cmd` is perfect diff --git a/app.js b/app.js index d4b25a6..fecc25f 100644 --- a/app.js +++ b/app.js @@ -9,6 +9,10 @@ import { LANGUAGE_CONFIG } from "./language/config.js"; import { exec as executeProcess } from "child_process"; import { allowOrigins } from "./origin/index.js"; import { extractError } from "./utils/index.js"; +import http from "http"; +import { Server as SocketServer } from "socket.io"; +import NodeCache from "node-cache"; + const exec = promisify(executeProcess); const fs = fileSystem.promises; dotenv.config(); @@ -20,9 +24,46 @@ app.use( origin: allowOrigins, }) ); +const server = http.createServer(app); +const io = new SocketServer(server, { + cors: { + origin: allowOrigins, + methods: ["GET", "POST"], + }, + pingTimeout: 60000, // 60 seconds + pingInterval: 25000, // 25 seconds + connectTimeout: 45000, // 45 seconds +}); + +const cache = new NodeCache({ + stdTTL: 60 * 60, // Cache TTL of 1 hour +}); +/** socket setup to display all current active users length */ +io.on("connection", (socket) => { + const coders = cache.get("active_coders"); + console.log(coders); + if (!cache.has(socket.id)) { + coders + ? cache.set("active_coders", [...coders, socket.id]) + : cache.set("active_coders", [socket.id]); + } + emitActiveCoder(); + socket.on("disconnect", () => { + const coders = cache.get("active_coders"); + if (coders) { + cache.set( + "active_coders", + coders.filter((coder) => coder !== socket.id) + ); + } + + emitActiveCoder(); + }); +}); -// which will be passed from docker-compose.yml +/* which will be passed from docker-compose.yaml */ const HOST_PROJECT_ROOT = process.env.HOST_PROJECT_ROOT; +/** Project root directory */ const directory_name = process.cwd(); app.post("/run", async (req, res) => { @@ -39,18 +80,20 @@ app.post("/run", async (req, res) => { if (!langConfig) return res.status(400).json({ error: `Unsupported language: ${language}` }); - // Generate a unique name for the temporary directory + /* Generate a unique name for the temporary directory */ const tempDirName = uuidv4(); - // This is the path *inside the nodejs-server container* where the code will be written + /* This is the path *inside the nodejs-server container* where the code will be written */ const tempDirInsideNodeServer = path.join( directory_name, "temp", tempDirName ); + /* code file path where our code will be store */ const codeFilePath = path.join(tempDirInsideNodeServer, langConfig.mainFile); - // This is the absolute path *on the host machine* that corresponds to tempDirInsideNodeServer. - // We use HOST_PROJECT_ROOT environment variable to construct this absolute path. + /** 1. This is the absolute path **on the host machine** that corresponds to tempDirInsideNodeServer. + 2. We use HOST_PROJECT_ROOT environment variable to construct this absolute path. + */ if (!HOST_PROJECT_ROOT) { console.error( "HOST_PROJECT_ROOT environment variable is not set. Volume mounts might fail." @@ -58,11 +101,12 @@ app.post("/run", async (req, res) => { return res.status(500).json({ error: "Server configuration error", details: - "HOST_PROJECT_ROOT environment variable is missing. Please ensure it's set in docker-compose.yaml.", + "HOST_PROJECT_ROOT environment variable is missing. Please ensure it's set in docker-compose.yaml", isDeveloper: true, }); } const hostVolumePath = path.join(HOST_PROJECT_ROOT, "temp", tempDirName); + console.log("host: ", hostVolumePath); try { // 1. Create the temporary directory inside the nodejs-server container @@ -113,7 +157,11 @@ app.use((req, res) => { res.type("txt").send("404 not found"); } }); +function emitActiveCoder() { + const activeUsers = cache.get("active_coders"); + io.emit("active_coders", activeUsers); +} const PORT = process.env.PORT || 9091; -app.listen(PORT, () => { +server.listen(PORT, () => { console.log(`Code executor backend listening on port ${PORT}`); }); diff --git a/browsers/screenshot_executeme.png b/browsers/screenshot_executeme.png new file mode 100644 index 0000000..28e329c Binary files /dev/null and b/browsers/screenshot_executeme.png differ diff --git a/changelog.md b/changelog.md index 47513be..fd00e66 100644 --- a/changelog.md +++ b/changelog.md @@ -9,10 +9,15 @@ ## v0.0.2 Releases 🎉 -- **TypeScript** Language support with `denoland` -- Extracted actual error from the whole error -- Readable and user-friendly output details error -- Reduce execution time -- Root routes and invalid path view `html` page setup -- Improve websites code complex issue -- Use server action to hide **API endpoint** +- [feat] **Added kotlin language support** +- [feat] **TypeScript** Language support with `denoland` +- [chore] Extracted actual error from the whole error +- [chore] Readable and user-friendly output details error +- [chore] Reduce execution time +- [feat] Root routes and invalid path view `html` page setup +- [refact] Improve websites code complex issue +- [feat] Use server action to hide **API endpoint** + +- [feat] View live online coders with in-memory cache i backend +- [feat] shows a floating button for displaying online coders +- [fix] Fixed mobile responsive issues diff --git a/docker-compose.yaml b/docker-compose.yaml index 27169c3..2820049 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -23,6 +23,12 @@ services: dockerfile: docker/Dockerfile.jvm image: executor-java:latest + kotlin-image: + build: + context: . + dockerfile: docker/Dockerfile.kotlin + image: executor-kotlin:latest + nodejs-server-image: build: context: . diff --git a/docker/Dockerfile.deno b/docker/Dockerfile.deno index 554ecfc..ab952e5 100644 --- a/docker/Dockerfile.deno +++ b/docker/Dockerfile.deno @@ -2,4 +2,3 @@ FROM denoland/deno:alpine-1.44.1 WORKDIR /app -# Entrypoint command will be overridden at runtime diff --git a/docker/Dockerfile.kotlin b/docker/Dockerfile.kotlin new file mode 100644 index 0000000..ba01acd --- /dev/null +++ b/docker/Dockerfile.kotlin @@ -0,0 +1,27 @@ +# Use a slim OpenJDK 21 image as the base, same as your Java setup +FROM openjdk:21-jdk-slim + +# Set the working directory inside the container. +# User's Kotlin source code will be mounted into this directory. +WORKDIR /app + +# Install necessary tools: curl for downloading, unzip for extracting. +# This works for Debian-based slim images. +RUN apt-get update && apt-get install -y curl unzip && rm -rf /var/lib/apt/lists/* + +# Define Kotlin version as an argument for easier updates +ARG KOTLIN_VERSION=1.9.20 +# NOTE: As of 2025-07-02, 1.9.20 is a good stable choice. +# You might want to update this to the absolute latest stable version if needed. + +# Download, extract, and clean up the Kotlin compiler +RUN curl -L https://github.com/JetBrains/kotlin/releases/download/v${KOTLIN_VERSION}/kotlin-compiler-${KOTLIN_VERSION}.zip -o /tmp/kotlin-compiler.zip \ + && unzip /tmp/kotlin-compiler.zip -d /opt/kotlin \ + && rm /tmp/kotlin-compiler.zip + +# Add the Kotlin compiler's 'bin' directory to the PATH +ENV PATH="/opt/kotlin/kotlinc/bin:${PATH}" + +# This CMD keeps the container running quietly when started by `docker-compose up`. +# It does NOT affect the actual execution when your Node.js server uses `docker run`. +CMD ["tail", "-f", "/dev/null"] \ No newline at end of file diff --git a/docker/Dockerfile.python b/docker/Dockerfile.python index a56c935..1f45e4f 100644 --- a/docker/Dockerfile.python +++ b/docker/Dockerfile.python @@ -1,3 +1,3 @@ FROM python:3.12-alpine -WORKDIR /app -# CMD will be overridden or provided by the mount \ No newline at end of file + +WORKDIR /app \ No newline at end of file diff --git a/Main.java b/example/Main.java similarity index 100% rename from Main.java rename to example/Main.java diff --git a/example/index.kt b/example/index.kt new file mode 100644 index 0000000..1f7eef8 --- /dev/null +++ b/example/index.kt @@ -0,0 +1,3 @@ +fun main() { + println("Hello, World!") +} diff --git a/index.ts b/example/index.ts similarity index 100% rename from index.ts rename to example/index.ts diff --git a/main.py b/example/main.py similarity index 100% rename from main.py rename to example/main.py diff --git a/language/config.js b/language/config.js index d67c807..11daf4a 100644 --- a/language/config.js +++ b/language/config.js @@ -19,4 +19,9 @@ export const LANGUAGE_CONFIG = { mainFile: "Main.java", cmd: 'sh -c "javac Main.java && java Main"', }, + kotlin: { + image: "executor-kotlin", + mainFile: "index.kt", + cmd: 'sh -c "kotlinc index.kt -include-runtime -d index.jar && java -jar index.jar"', + }, }; diff --git a/package.json b/package.json index 2202ad5..9a05d85 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "A robust and secure backend service for executing user-submitted code in isolated Docker containers. Supports multiple programming languages like Python, Node.js, and Java, with resource limiting for safe execution. Ideal for online compilers, coding platforms, and educational tools.", "scripts": { "dev": "nodemon app.js", + "test": "vitest run --reporter verbose --pool forks", "start": "node --trace-warnings app.js" }, "repository": { @@ -43,12 +44,15 @@ "dependencies": { "cors": "^2.8.5", "express": "^5.1.0", + "node-cache": "^5.1.2", + "socket.io": "^4.8.1", "util": "^0.12.5", "uuid": "^11.1.0" }, "devDependencies": { "dotenv": "^16.6.0", - "nodemon": "^3.1.10" + "nodemon": "^3.1.10", + "vitest": "^3.2.4" }, "funding": [ { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24d2773..0106664 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,12 @@ importers: express: specifier: ^5.1.0 version: 5.1.0 + node-cache: + specifier: ^5.1.2 + version: 5.1.2 + socket.io: + specifier: ^4.8.1 + version: 4.8.1 util: specifier: ^0.12.5 version: 0.12.5 @@ -27,9 +33,316 @@ importers: nodemon: specifier: ^3.1.10 version: 3.1.10 + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/node@24.0.10) packages: + '@esbuild/aix-ppc64@0.25.5': + resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.5': + resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.5': + resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.5': + resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.5': + resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.5': + resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.5': + resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.5': + resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.5': + resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.5': + resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.5': + resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.5': + resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.5': + resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.5': + resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.5': + resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.5': + resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.5': + resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.5': + resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.5': + resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.5': + resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.5': + resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.5': + resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.5': + resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.5': + resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.5': + resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/sourcemap-codec@1.5.4': + resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} + + '@rollup/rollup-android-arm-eabi@4.44.1': + resolution: {integrity: sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.44.1': + resolution: {integrity: sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.44.1': + resolution: {integrity: sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.44.1': + resolution: {integrity: sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.44.1': + resolution: {integrity: sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.44.1': + resolution: {integrity: sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.44.1': + resolution: {integrity: sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.44.1': + resolution: {integrity: sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.44.1': + resolution: {integrity: sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.44.1': + resolution: {integrity: sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.44.1': + resolution: {integrity: sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.44.1': + resolution: {integrity: sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.44.1': + resolution: {integrity: sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.44.1': + resolution: {integrity: sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.44.1': + resolution: {integrity: sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.44.1': + resolution: {integrity: sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.44.1': + resolution: {integrity: sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.44.1': + resolution: {integrity: sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.44.1': + resolution: {integrity: sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.44.1': + resolution: {integrity: sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==} + cpu: [x64] + os: [win32] + + '@socket.io/component-emitter@3.1.2': + resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + + '@types/cors@2.8.19': + resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/node@24.0.10': + resolution: {integrity: sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==} + + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -38,6 +351,10 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -45,6 +362,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64id@2.0.0: + resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} + engines: {node: ^4.5.0 || >= 5.9} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -64,6 +385,10 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -76,10 +401,22 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} + chai@5.2.0: + resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} + engines: {node: '>=12'} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -103,6 +440,15 @@ packages: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -112,6 +458,10 @@ packages: supports-color: optional: true + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -135,6 +485,14 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + engine.io-parser@5.2.3: + resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} + engines: {node: '>=10.0.0'} + + engine.io@6.6.4: + resolution: {integrity: sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==} + engines: {node: '>=10.2.0'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -143,21 +501,44 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} + esbuild@0.25.5: + resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} + engines: {node: '>=18'} + hasBin: true + escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + expect-type@1.2.1: + resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} + engines: {node: '>=12.0.0'} + express@5.1.0: resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} engines: {node: '>= 18'} + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -278,6 +659,15 @@ packages: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + loupe@3.1.4: + resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -290,10 +680,18 @@ packages: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + mime-db@1.54.0: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + mime-types@3.0.1: resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} engines: {node: '>= 0.6'} @@ -304,10 +702,23 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + negotiator@1.0.0: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} + node-cache@5.1.2: + resolution: {integrity: sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==} + engines: {node: '>= 8.0.0'} + nodemon@3.1.10: resolution: {integrity: sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==} engines: {node: '>=10'} @@ -340,14 +751,32 @@ packages: resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} engines: {node: '>=16'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -371,6 +800,11 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + rollup@4.44.1: + resolution: {integrity: sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -421,10 +855,31 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + simple-update-notifier@2.0.0: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} + socket.io-adapter@2.5.5: + resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==} + + socket.io-parser@4.2.4: + resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} + engines: {node: '>=10.0.0'} + + socket.io@4.8.1: + resolution: {integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==} + engines: {node: '>=10.2.0'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -433,10 +888,38 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} + std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + + strip-literal@3.0.0: + resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.3: + resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} + engines: {node: '>=14.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -456,6 +939,9 @@ packages: undefsafe@2.0.5: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} + undici-types@7.8.0: + resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -471,15 +957,307 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@7.0.0: + resolution: {integrity: sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + which-typed-array@1.1.19: resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} engines: {node: '>= 0.4'} + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + 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 + snapshots: + '@esbuild/aix-ppc64@0.25.5': + optional: true + + '@esbuild/android-arm64@0.25.5': + optional: true + + '@esbuild/android-arm@0.25.5': + optional: true + + '@esbuild/android-x64@0.25.5': + optional: true + + '@esbuild/darwin-arm64@0.25.5': + optional: true + + '@esbuild/darwin-x64@0.25.5': + optional: true + + '@esbuild/freebsd-arm64@0.25.5': + optional: true + + '@esbuild/freebsd-x64@0.25.5': + optional: true + + '@esbuild/linux-arm64@0.25.5': + optional: true + + '@esbuild/linux-arm@0.25.5': + optional: true + + '@esbuild/linux-ia32@0.25.5': + optional: true + + '@esbuild/linux-loong64@0.25.5': + optional: true + + '@esbuild/linux-mips64el@0.25.5': + optional: true + + '@esbuild/linux-ppc64@0.25.5': + optional: true + + '@esbuild/linux-riscv64@0.25.5': + optional: true + + '@esbuild/linux-s390x@0.25.5': + optional: true + + '@esbuild/linux-x64@0.25.5': + optional: true + + '@esbuild/netbsd-arm64@0.25.5': + optional: true + + '@esbuild/netbsd-x64@0.25.5': + optional: true + + '@esbuild/openbsd-arm64@0.25.5': + optional: true + + '@esbuild/openbsd-x64@0.25.5': + optional: true + + '@esbuild/sunos-x64@0.25.5': + optional: true + + '@esbuild/win32-arm64@0.25.5': + optional: true + + '@esbuild/win32-ia32@0.25.5': + optional: true + + '@esbuild/win32-x64@0.25.5': + optional: true + + '@jridgewell/sourcemap-codec@1.5.4': {} + + '@rollup/rollup-android-arm-eabi@4.44.1': + optional: true + + '@rollup/rollup-android-arm64@4.44.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.44.1': + optional: true + + '@rollup/rollup-darwin-x64@4.44.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.44.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.44.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.44.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.44.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.44.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.44.1': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.44.1': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.44.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.44.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.44.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.44.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.44.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.44.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.44.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.44.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.44.1': + optional: true + + '@socket.io/component-emitter@3.1.2': {} + + '@types/chai@5.2.2': + dependencies: + '@types/deep-eql': 4.0.2 + + '@types/cors@2.8.19': + dependencies: + '@types/node': 24.0.10 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/node@24.0.10': + dependencies: + undici-types: 7.8.0 + + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.2 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.0 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.4(vite@7.0.0(@types/node@24.0.10))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 7.0.0(@types/node@24.0.10) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.0.0 + + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.17 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.3 + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.1.4 + tinyrainbow: 2.0.0 + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + accepts@2.0.0: dependencies: mime-types: 3.0.1 @@ -490,12 +1268,16 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + assertion-error@2.0.1: {} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 balanced-match@1.0.2: {} + base64id@2.0.0: {} + binary-extensions@2.3.0: {} body-parser@2.2.0: @@ -523,6 +1305,8 @@ snapshots: bytes@3.1.2: {} + cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -540,6 +1324,16 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + chai@5.2.0: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.4 + pathval: 2.0.1 + + check-error@2.1.1: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -552,6 +1346,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + clone@2.1.2: {} + concat-map@0.0.1: {} content-disposition@1.0.0: @@ -569,12 +1365,18 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 + debug@4.3.7: + dependencies: + ms: 2.1.3 + debug@4.4.1(supports-color@5.5.0): dependencies: ms: 2.1.3 optionalDependencies: supports-color: 5.5.0 + deep-eql@5.0.2: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -595,18 +1397,72 @@ snapshots: encodeurl@2.0.0: {} + engine.io-parser@5.2.3: {} + + engine.io@6.6.4: + dependencies: + '@types/cors': 2.8.19 + '@types/node': 24.0.10 + accepts: 1.3.8 + base64id: 2.0.0 + cookie: 0.7.2 + cors: 2.8.5 + debug: 4.3.7 + engine.io-parser: 5.2.3 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + es-define-property@1.0.1: {} es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 + esbuild@0.25.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.5 + '@esbuild/android-arm': 0.25.5 + '@esbuild/android-arm64': 0.25.5 + '@esbuild/android-x64': 0.25.5 + '@esbuild/darwin-arm64': 0.25.5 + '@esbuild/darwin-x64': 0.25.5 + '@esbuild/freebsd-arm64': 0.25.5 + '@esbuild/freebsd-x64': 0.25.5 + '@esbuild/linux-arm': 0.25.5 + '@esbuild/linux-arm64': 0.25.5 + '@esbuild/linux-ia32': 0.25.5 + '@esbuild/linux-loong64': 0.25.5 + '@esbuild/linux-mips64el': 0.25.5 + '@esbuild/linux-ppc64': 0.25.5 + '@esbuild/linux-riscv64': 0.25.5 + '@esbuild/linux-s390x': 0.25.5 + '@esbuild/linux-x64': 0.25.5 + '@esbuild/netbsd-arm64': 0.25.5 + '@esbuild/netbsd-x64': 0.25.5 + '@esbuild/openbsd-arm64': 0.25.5 + '@esbuild/openbsd-x64': 0.25.5 + '@esbuild/sunos-x64': 0.25.5 + '@esbuild/win32-arm64': 0.25.5 + '@esbuild/win32-ia32': 0.25.5 + '@esbuild/win32-x64': 0.25.5 + escape-html@1.0.3: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + etag@1.8.1: {} + expect-type@1.2.1: {} + express@5.1.0: dependencies: accepts: 2.0.0 @@ -639,6 +1495,10 @@ snapshots: transitivePeerDependencies: - supports-color + fdir@6.4.6(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -764,14 +1624,28 @@ snapshots: dependencies: which-typed-array: 1.1.19 + js-tokens@9.0.1: {} + + loupe@3.1.4: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.4 + math-intrinsics@1.1.0: {} media-typer@1.1.0: {} merge-descriptors@2.0.0: {} + mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + mime-types@3.0.1: dependencies: mime-db: 1.54.0 @@ -782,8 +1656,16 @@ snapshots: ms@2.1.3: {} + nanoid@3.3.11: {} + + negotiator@0.6.3: {} + negotiator@1.0.0: {} + node-cache@5.1.2: + dependencies: + clone: 2.1.2 + nodemon@3.1.10: dependencies: chokidar: 3.6.0 @@ -815,10 +1697,24 @@ snapshots: path-to-regexp@8.2.0: {} + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + picomatch@2.3.1: {} + picomatch@4.0.2: {} + possible-typed-array-names@1.1.0: {} + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -843,6 +1739,32 @@ snapshots: dependencies: picomatch: 2.3.1 + rollup@4.44.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.44.1 + '@rollup/rollup-android-arm64': 4.44.1 + '@rollup/rollup-darwin-arm64': 4.44.1 + '@rollup/rollup-darwin-x64': 4.44.1 + '@rollup/rollup-freebsd-arm64': 4.44.1 + '@rollup/rollup-freebsd-x64': 4.44.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.44.1 + '@rollup/rollup-linux-arm-musleabihf': 4.44.1 + '@rollup/rollup-linux-arm64-gnu': 4.44.1 + '@rollup/rollup-linux-arm64-musl': 4.44.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.44.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.44.1 + '@rollup/rollup-linux-riscv64-gnu': 4.44.1 + '@rollup/rollup-linux-riscv64-musl': 4.44.1 + '@rollup/rollup-linux-s390x-gnu': 4.44.1 + '@rollup/rollup-linux-x64-gnu': 4.44.1 + '@rollup/rollup-linux-x64-musl': 4.44.1 + '@rollup/rollup-win32-arm64-msvc': 4.44.1 + '@rollup/rollup-win32-ia32-msvc': 4.44.1 + '@rollup/rollup-win32-x64-msvc': 4.44.1 + fsevents: 2.3.3 + router@2.2.0: dependencies: debug: 4.4.1(supports-color@5.5.0) @@ -929,18 +1851,75 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + simple-update-notifier@2.0.0: dependencies: semver: 7.7.2 + socket.io-adapter@2.5.5: + dependencies: + debug: 4.3.7 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-parser@4.2.4: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + + socket.io@4.8.1: + dependencies: + accepts: 1.3.8 + base64id: 2.0.0 + cors: 2.8.5 + debug: 4.3.7 + engine.io: 6.6.4 + socket.io-adapter: 2.5.5 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + statuses@2.0.1: {} statuses@2.0.2: {} + std-env@3.9.0: {} + + strip-literal@3.0.0: + dependencies: + js-tokens: 9.0.1 + supports-color@5.5.0: dependencies: has-flag: 3.0.0 + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.3: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -957,6 +1936,8 @@ snapshots: undefsafe@2.0.5: {} + undici-types@7.8.0: {} + unpipe@1.0.0: {} util@0.12.5: @@ -971,6 +1952,80 @@ snapshots: vary@1.1.2: {} + vite-node@3.2.4(@types/node@24.0.10): + dependencies: + cac: 6.7.14 + debug: 4.4.1(supports-color@5.5.0) + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.0.0(@types/node@24.0.10) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@7.0.0(@types/node@24.0.10): + dependencies: + esbuild: 0.25.5 + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + postcss: 8.5.6 + rollup: 4.44.1 + tinyglobby: 0.2.14 + optionalDependencies: + '@types/node': 24.0.10 + fsevents: 2.3.3 + + vitest@3.2.4(@types/node@24.0.10): + dependencies: + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.0.0(@types/node@24.0.10)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.0 + debug: 4.4.1(supports-color@5.5.0) + expect-type: 1.2.1 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.0.0(@types/node@24.0.10) + vite-node: 3.2.4(@types/node@24.0.10) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.0.10 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + which-typed-array@1.1.19: dependencies: available-typed-arrays: 1.0.7 @@ -981,4 +2036,11 @@ snapshots: gopd: 1.2.0 has-tostringtag: 1.0.2 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wrappy@1.0.2: {} + + ws@8.17.1: {} diff --git a/scripts.sh b/scripts.sh index e2cc582..0f9c019 100644 --- a/scripts.sh +++ b/scripts.sh @@ -1,3 +1,8 @@ -docker build -t executor-python -f Dockerfile.python . -docker build -t executor-nodejs -f Dockerfile.nodejs . -# Add more for other languages (e.g., executor-java, executor-go) \ No newline at end of file +## delete all containers including its volumes use +docker rm -vf $(docker ps -aq) + +## delete all the images +docker rmi -f $(docker images -aq) + +## create container with new force command +docker compose up --force-recreate \ No newline at end of file diff --git a/tests/executor.test.js b/tests/executor.test.js new file mode 100644 index 0000000..6b871c7 --- /dev/null +++ b/tests/executor.test.js @@ -0,0 +1,243 @@ +import { describe, it, expect, beforeAll, afterAll } from "vitest"; +import { exec } from "child_process"; +import { promisify } from "util"; +import path from "path"; + +const execPromise = promisify(exec); + +const SERVER_URL = "http://localhost:9091"; /** local server url */ + +// Store the child process for docker-compose up +let dockerComposeProcess; + +// --- Global Setup and Teardown --- +// These hooks run once before all tests and once after all tests in this file. +beforeAll(async () => { + console.log("\n--- Setting up Docker Compose for tests ---"); + try { + // Build all images (if not already built) and start services in detached mode + // --force-recreate ensures fresh containers from potentially updated images + // --build ensures images are rebuilt if Dockerfiles/context changed + await execPromise("docker-compose up -d --build --force-recreate", { + cwd: path.resolve(process.cwd(), "../"), + }); + console.log("Docker Compose services started."); + + // Give the server a moment to fully start up and be ready to accept connections + await new Promise((resolve) => setTimeout(resolve, 5000)); // Wait 5 seconds + console.log("Server should be ready."); + } catch (error) { + console.error( + "Failed to start Docker Compose services:", + error.stdout || error.stderr || error.message + ); + throw error; // Fail the test suite if setup fails + } +}, 60000); // 60 seconds timeout for beforeAll hook + +afterAll(async () => { + console.log("\n--- Tearing down Docker Compose after tests ---"); + try { + // Stop and remove all services, networks, and volumes created by docker-compose up + await execPromise("docker-compose down --volumes --remove-orphans", { + cwd: path.resolve(__dirname, "../"), + }); + console.log("Docker Compose services stopped and removed."); + } catch (error) { + console.error( + "Failed to stop Docker Compose services:", + error.stdout || error.stderr || error.message + ); + // Even if teardown fails, we want to report test results, but log the error + } +}); + +// --- Helper function to send code execution requests --- +async function executeCode(language, code) { + const response = await fetch(`${SERVER_URL}/run`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ code, language }), + }); + return response.json(); +} + +// --- Test Suite for Code Executor --- +describe("Code Executor Battle Tests", () => { + // Test basic "Hello World" for each supported language + it("should execute basic Python code successfully", async () => { + const result = await executeCode("python", 'print("Hello from Python!")'); + expect(result.output).toBe("Hello from Python!\n"); + expect(result.error).toBeUndefined(); + }); + + it("should execute basic Node.js code successfully", async () => { + const result = await executeCode( + "nodejs", + 'console.log("Hello from Node.js!");' + ); + expect(result.output).toBe("Hello from Node.js!\n"); + expect(result.error).toBeUndefined(); + }); + + it("should execute basic TypeScript (Deno) code successfully", async () => { + const result = await executeCode( + "typescript", + 'console.log("Hello from Deno/TypeScript!");' + ); + expect(result.output).toBe("Hello from Deno/TypeScript!\n"); + expect(result.error).toBeUndefined(); + }); + + it("should execute basic Java code successfully", async () => { + const result = await executeCode( + "java", + 'public class Main { public static void main(String[] args) { System.out.println("Hello from Java!"); } }' + ); + expect(result.output).toBe("Hello from Java!\n"); + expect(result.error).toBeUndefined(); + }); + + it("should execute basic Kotlin code successfully", async () => { + const result = await executeCode( + "kotlin", + 'fun main() { println("Hello from Kotlin!") }' + ); + expect(result.output).toBe("Hello from Kotlin!\n"); + expect(result.error).toBeUndefined(); + }); + + // Test cases for syntax errors + it("should return error for Python syntax error", async () => { + const result = await executeCode("python", 'print("Hello'); // Missing closing quote + expect(result.error).toBeDefined(); + expect(result.error).toContain("SyntaxError"); + expect(result.output).toBeUndefined(); + }); + + it("should return error for Java compilation error", async () => { + const result = await executeCode( + "java", + 'public class Main { public static void main(String[] args) { System.out.println("Hello from Java!" } }' + ); // Missing parenthesis + expect(result.error).toBeDefined(); + expect(result.error).toContain("compilation failed"); // Or similar message from javac + expect(result.output).toBeUndefined(); + }); + + it("should return error for Kotlin compilation error", async () => { + const result = await executeCode( + "kotlin", + 'fun main() { println("Hello from Kotlin!" }' + ); // Missing parenthesis + expect(result.error).toBeDefined(); + expect(result.error).toContain("error:"); // Or similar message from kotlinc + expect(result.output).toBeUndefined(); + }); + + // Test cases for runtime errors + it("should return error for Python runtime error (division by zero)", async () => { + const result = await executeCode("python", "print(1/0)"); + expect(result.error).toBeDefined(); + expect(result.error).toContain("ZeroDivisionError"); + expect(result.output).toBeUndefined(); + }); + + it("should return error for Java runtime error (division by zero)", async () => { + const result = await executeCode( + "java", + "public class Main { public static void main(String[] args) { System.out.println(1/0); } }" + ); + expect(result.error).toBeDefined(); + expect(result.error).toContain("java.lang.ArithmeticException: / by zero"); + expect(result.output).toBeUndefined(); + }); + + // Test resource limits (these might vary based on Docker daemon config) + // The Docker `--memory` and `--cpus` limits are handled by Docker itself. + // We can send code that *attempts* to exceed them and check for errors. + it("should handle large output (e.g., 1MB string)", async () => { + const largeString = "a".repeat(1024 * 1024); // 1 MB string + const result = await executeCode( + "nodejs", + `console.log('${largeString}');` + ); + expect(result.output.length).toBeGreaterThan(1024 * 1024); // Output should be roughly 1MB + newline + expect(result.error).toBeUndefined(); + }, 15000); // Give more time for large output + + it("should handle code that attempts to consume too much memory (Python)", async () => { + // This script tries to allocate a very large list. + // Docker's --memory=256m should ideally kill it. + const code = ` +import sys +# Try to allocate 500MB, which should exceed 256MB limit +try: + large_list = [0] * (500 * 1024 * 1024 // sys.getsizeof(0)) + print("Allocated large list (should not happen)") +except MemoryError: + print("MemoryError caught in script") +except Exception as e: + print(f"Other error: {e}") +`; + const result = await executeCode("python", code); + // Expecting a Docker-level error or a specific memory error message + // The exact error message depends on how Docker kills the container and what stderr it returns. + expect(result.error).toBeDefined(); + expect(result.error).toContain("out of memory") || + expect(result.error).toContain("MemoryError") || + expect(result.error).toContain("OOM"); + expect(result.output).toBeUndefined(); + }, 20000); // Increased timeout for potential OOM kill + + it("should handle code that attempts to run too long (infinite loop in Node.js)", async () => { + // This script runs an infinite loop. Docker's default timeout (if any) or CPU limit + // should eventually terminate it. Your `exec` call might also have a default timeout. + const code = ` +let i = 0; +while (true) { + i++; + // Prevent console spam but keep it busy + if (i % 1000000 === 0) { + console.log('Still looping...'); + } +} +`; + const result = await executeCode("nodejs", code); + // Expecting a timeout error from Docker or the child_process.exec + expect(result.error).toBeDefined(); + // The error message could be 'Command failed', 'timed out', or related to Docker stopping the container + expect(result.error).toMatch( + /command failed|timed out|killed|exit status 137/i + ); + // Output might contain partial logs if it ran for a bit + }, 30000); // Increased timeout for potential long-running process before termination + + // Test concurrency: Send multiple requests simultaneously + it("should handle multiple concurrent requests", async () => { + const numRequests = 10; + const pythonCode = + 'import time\nimport random\ntime.sleep(random.uniform(0.1, 0.5))\nprint("Concurrent Python done!")'; + const javaCode = + 'public class Main { public static void main(String[] args) { try { Thread.sleep((long)(Math.random() * 400 + 100)); } catch (Exception e) {} System.out.println("Concurrent Java done!"); } }'; + const kotlinCode = + 'import kotlin.random.Random\nimport kotlinx.coroutines.*\nfun main() = runBlocking { delay(Random.nextLong(100, 500)); println("Concurrent Kotlin done!") }'; + + const requests = []; + for (let i = 0; i < numRequests; i++) { + requests.push(executeCode("python", pythonCode)); + requests.push(executeCode("java", javaCode)); + requests.push(executeCode("kotlin", kotlinCode)); + } + + const results = await Promise.all(requests); + + results.forEach((result) => { + expect(result.output).toBeDefined(); + expect(result.output).toMatch(/Concurrent (Python|Java|Kotlin) done!\n/); + expect(result.error).toBeUndefined(); + }); + }, 60000); // Increased timeout for concurrent tests +}); diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 0000000..5576aea --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + // globalSetup: './tests/globalSetup.js', + // globalTeardown: './tests/globalTeardown.js', + // Timeout for individual tests (in milliseconds) + testTimeout: 30000, // 30 seconds for each test + hookTimeout: 60000, // 60 seconds for beforeAll/afterAll hooks + // Exclude your temporary directories from test discovery + exclude: ["**/node_modules/**", "**/dist/**", "**/temp/**"], + }, +}); diff --git a/websites/.env.local b/websites/.env.local deleted file mode 100644 index e4df1a0..0000000 --- a/websites/.env.local +++ /dev/null @@ -1 +0,0 @@ -SERVER_BASE_URL_LOCAL="http://localhost:9091" \ No newline at end of file diff --git a/websites/package.json b/websites/package.json index 646bd9b..81d2a78 100644 --- a/websites/package.json +++ b/websites/package.json @@ -22,8 +22,10 @@ "perf_hooks": "^0.0.1", "react": "^19.0.0", "react-dom": "^19.0.0", + "socket.io-client": "^4.8.1", "sonner": "^2.0.5", - "tailwind-merge": "^3.3.1" + "tailwind-merge": "^3.3.1", + "zustand": "^5.0.6" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/websites/pnpm-lock.yaml b/websites/pnpm-lock.yaml index 7ddad8e..e38c6a6 100644 --- a/websites/pnpm-lock.yaml +++ b/websites/pnpm-lock.yaml @@ -47,12 +47,18 @@ importers: react-dom: specifier: ^19.0.0 version: 19.1.0(react@19.1.0) + socket.io-client: + specifier: ^4.8.1 + version: 4.8.1 sonner: specifier: ^2.0.5 version: 2.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) tailwind-merge: specifier: ^3.3.1 version: 3.3.1 + zustand: + specifier: ^5.0.6 + version: 5.0.6(@types/react@19.1.8)(react@19.1.0) devDependencies: '@eslint/eslintrc': specifier: ^3 @@ -699,6 +705,9 @@ packages: '@rushstack/eslint-patch@1.12.0': resolution: {integrity: sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==} + '@socket.io/component-emitter@3.1.2': + resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} @@ -1160,6 +1169,15 @@ packages: supports-color: optional: true + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -1202,6 +1220,13 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + engine.io-client@6.6.3: + resolution: {integrity: sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==} + + engine.io-parser@5.2.3: + resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} + engines: {node: '>=10.0.0'} + enhanced-resolve@5.18.2: resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==} engines: {node: '>=10.13.0'} @@ -2100,6 +2125,14 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + socket.io-client@4.8.1: + resolution: {integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==} + engines: {node: '>=10.0.0'} + + socket.io-parser@4.2.4: + resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} + engines: {node: '>=10.0.0'} + sonner@2.0.5: resolution: {integrity: sha512-YwbHQO6cSso3HBXlbCkgrgzDNIhws14r4MO87Ofy+cV2X7ES4pOoAK3+veSmVTvqNx1BWUxlhPmZzP00Crk2aQ==} peerDependencies: @@ -2296,6 +2329,22 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + 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 + + xmlhttprequest-ssl@2.1.2: + resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==} + engines: {node: '>=0.4.0'} + yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} @@ -2304,6 +2353,24 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zustand@5.0.6: + resolution: {integrity: sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -2836,6 +2903,8 @@ snapshots: '@rushstack/eslint-patch@1.12.0': {} + '@socket.io/component-emitter@3.1.2': {} + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.15': @@ -3313,6 +3382,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.3.7: + dependencies: + ms: 2.1.3 + debug@4.4.1: dependencies: ms: 2.1.3 @@ -3349,6 +3422,20 @@ snapshots: emoji-regex@9.2.2: {} + engine.io-client@6.6.3: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + engine.io-parser: 5.2.3 + ws: 8.17.1 + xmlhttprequest-ssl: 2.1.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + engine.io-parser@5.2.3: {} + enhanced-resolve@5.18.2: dependencies: graceful-fs: 4.2.11 @@ -4428,6 +4515,24 @@ snapshots: is-arrayish: 0.3.2 optional: true + socket.io-client@4.8.1: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + engine.io-client: 6.6.3 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-parser@4.2.4: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + sonner@2.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 @@ -4688,6 +4793,15 @@ snapshots: word-wrap@1.2.5: {} + ws@8.17.1: {} + + xmlhttprequest-ssl@2.1.2: {} + yallist@5.0.0: {} yocto-queue@0.1.0: {} + + zustand@5.0.6(@types/react@19.1.8)(react@19.1.0): + optionalDependencies: + '@types/react': 19.1.8 + react: 19.1.0 diff --git a/websites/src/app/_components/code-input.tsx b/websites/src/app/_components/code-input.tsx index 576c191..3372b7b 100644 --- a/websites/src/app/_components/code-input.tsx +++ b/websites/src/app/_components/code-input.tsx @@ -54,7 +54,7 @@ export default function CodeInput() { return ( <> - +
@@ -71,7 +71,7 @@ export default function CodeInput() { setSelectedLanguage={setSelectedLanguage} /> - + - + + {/* Top bar for editor controls - now dark */}
@@ -52,14 +52,16 @@ export function CodeEditor({
-
+
-
+
diff --git a/websites/src/components/shared/online-coders/content.tsx b/websites/src/components/shared/online-coders/content.tsx new file mode 100644 index 0000000..0692c00 --- /dev/null +++ b/websites/src/components/shared/online-coders/content.tsx @@ -0,0 +1,49 @@ +import { Users, Zap } from "lucide-react"; + +type Props = { + displayCount: number; +}; +export default function Content({ displayCount }: Props) { + return ( +
+
+ {/* Animated icon container */} +
+
+
+ +
+
+ +
+

+ Online Coders +

+
+ {/* Animated count */} + + {displayCount.toLocaleString()} + + + {/* Live indicator */} +
+
+ + Live + +
+
+
+
+ + {/* Activity indicator */} +
+
+ +
+
+ Active +
+
+ ); +} diff --git a/websites/src/components/shared/online-coders/floating-particles.tsx b/websites/src/components/shared/online-coders/floating-particles.tsx new file mode 100644 index 0000000..e3865e5 --- /dev/null +++ b/websites/src/components/shared/online-coders/floating-particles.tsx @@ -0,0 +1,20 @@ +export default function FloatingParticles() { + return ( +
+ {[...Array(6)].map((_, i) => ( +
+ ))} +
+ ); +} diff --git a/websites/src/components/shared/online-coders/online-coders.tsx b/websites/src/components/shared/online-coders/online-coders.tsx new file mode 100644 index 0000000..34f1211 --- /dev/null +++ b/websites/src/components/shared/online-coders/online-coders.tsx @@ -0,0 +1,76 @@ +"use client"; +import { useState, useEffect } from "react"; +import { ClassValue } from "clsx"; +import "./style.css"; +import Content from "./content"; +import FloatingParticles from "./floating-particles"; +import { socket } from "@/lib/socket"; + +type Props = { + className?: ClassValue; +}; +export default function OnlineCoders({ className }: Props) { + const [count, setCount] = useState(0); + const [displayCount, setDisplayCount] = useState(0); + const [isVisible, setIsVisible] = useState(false); + + // Animated counter effect + useEffect(() => { + setIsVisible(true); + const duration = 2000; // 2 seconds + const steps = 60; + const increment = count / steps; + let current = 0; + + const timer = setInterval(() => { + current += increment; + if (current >= count) { + setDisplayCount(count); + clearInterval(timer); + } else { + setDisplayCount(Math.floor(current)); + } + }, duration / steps); + + return () => clearInterval(timer); + }, [count]); + + useEffect(() => { + const handleActiveCoders = (data: string[]) => { + console.log("data: ", data); + setCount(data.length || 0); + }; + socket.on("active_coders", handleActiveCoders); + return () => { + socket.off("active_coders", handleActiveCoders); + }; + }, []); + return ( +
+ {/* Background glow effect */} +
+ + {/* Main container with glassmorphism */} +
+ {/* Animated background gradient */} +
+ + {/* Floating particles */} + + + {/* Content */} + + + {/* Bottom accent line */} +
+
+
+ ); +} diff --git a/websites/src/components/shared/online-coders/style.css b/websites/src/components/shared/online-coders/style.css new file mode 100644 index 0000000..09e4174 --- /dev/null +++ b/websites/src/components/shared/online-coders/style.css @@ -0,0 +1,77 @@ +@keyframes gradient-xy { + 0%, + 100% { + transform: translate(0%, 0%) rotate(0deg); + } + 25% { + transform: translate(10%, 10%) rotate(90deg); + } + 50% { + transform: translate(0%, 20%) rotate(180deg); + } + 75% { + transform: translate(-10%, 10%) rotate(270deg); + } +} + +@keyframes gradient-x { + 0%, + 100% { + transform: translateX(-100%); + } + 50% { + transform: translateX(100%); + } +} + +@keyframes float-1 { + 0%, + 100% { + transform: translateY(0px) rotate(0deg); + opacity: 0.4; + } + 50% { + transform: translateY(-20px) rotate(180deg); + opacity: 1; + } +} + +@keyframes float-2 { + 0%, + 100% { + transform: translateY(0px) rotate(0deg); + opacity: 0.6; + } + 50% { + transform: translateY(-15px) rotate(-180deg); + opacity: 0.2; + } +} + +@keyframes float-3 { + 0%, + 100% { + transform: translateY(0px) rotate(0deg); + opacity: 0.3; + } + 50% { + transform: translateY(-25px) rotate(360deg); + opacity: 0.8; + } +} + +.animate-gradient-xy { + animation: gradient-xy 4s ease infinite; +} +.animate-gradient-x { + animation: gradient-x 3s ease infinite; +} +.animate-float-1 { + animation: float-1 6s ease-in-out infinite; +} +.animate-float-2 { + animation: float-2 8s ease-in-out infinite; +} +.animate-float-3 { + animation: float-3 7s ease-in-out infinite; +} diff --git a/websites/src/components/shared/providers/base-provider.tsx b/websites/src/components/shared/providers/base-provider.tsx index b32057c..d6723dd 100644 --- a/websites/src/components/shared/providers/base-provider.tsx +++ b/websites/src/components/shared/providers/base-provider.tsx @@ -1,6 +1,7 @@ import { Toaster } from "@/components/ui/sonner"; import { PropsWithChildren } from "react"; import { ThemeProvider } from "./theme-provider"; +import SocketProvider from "./socket-provider"; export default function BaseProvider({ children }: PropsWithChildren) { return ( @@ -10,8 +11,9 @@ export default function BaseProvider({ children }: PropsWithChildren) { enableSystem disableTransitionOnChange > - {children} - + + {children} + ); } diff --git a/websites/src/components/shared/providers/socket-provider.tsx b/websites/src/components/shared/providers/socket-provider.tsx new file mode 100644 index 0000000..90075ee --- /dev/null +++ b/websites/src/components/shared/providers/socket-provider.tsx @@ -0,0 +1,23 @@ +"use client"; +import { socket } from "@/lib/socket"; +import { PropsWithChildren, useEffect } from "react"; +import OnlineCoders from "../online-coders/online-coders"; + +const SocketProvider = ({ children }: PropsWithChildren) => { + useEffect(() => { + socket.connect(); + + return () => { + socket.disconnect(); + }; + }, []); + + return ( +
+ {children} + +
+ ); +}; + +export default SocketProvider; diff --git a/websites/src/constants/base-memetadata.ts b/websites/src/constants/base-memetadata.ts new file mode 100644 index 0000000..26e1f38 --- /dev/null +++ b/websites/src/constants/base-memetadata.ts @@ -0,0 +1,14 @@ +export const baseMetadata = { + authors: { + name: "Sabbir Hossain Shuvo", + url: "devlopersabbir.github.io", + }, + appLinks: { + web: { + url: "https://executeme.vercel.app", + }, + }, + category: "Programming Practices Platform", + creator: "Sabbir Hossain Shuvo", + generator: "NextJs", +}; diff --git a/websites/src/constants/base.ts b/websites/src/constants/base.ts index 19e7a60..2db735f 100644 --- a/websites/src/constants/base.ts +++ b/websites/src/constants/base.ts @@ -1,4 +1,5 @@ +export const BASE_URI = "http://localhost:9091"; export const baseUri = process.env.NODE_ENV !== "development" ? process.env.SERVER_BASE_URL - : process.env.SERVER_BASE_URL_LOCAL; + : process.env.SERVER_BASE_URL_LOCAL || BASE_URI; diff --git a/websites/src/constants/language.ts b/websites/src/constants/language.ts index 0f7e253..30a495b 100644 --- a/websites/src/constants/language.ts +++ b/websites/src/constants/language.ts @@ -3,6 +3,7 @@ export const SUPPORTED_LANGUAGES = [ { value: "nodejs", label: "JavaScript", extension: ".js" }, { value: "typescript", label: "TypeScript", extension: ".ts" }, { value: "java", label: "Java", extension: ".java" }, + { value: "kotlin", label: "Kotlin", extension: ".kt" }, // { value: 'cpp', label: 'C++', extension: '.cpp' }, // { value: 'c', label: 'C', extension: '.c' }, // { value: 'go', label: 'Go', extension: '.go' }, diff --git a/websites/src/lib/socket.ts b/websites/src/lib/socket.ts new file mode 100644 index 0000000..998966f --- /dev/null +++ b/websites/src/lib/socket.ts @@ -0,0 +1,12 @@ +import { baseUri } from "@/constants/base"; +import { io } from "socket.io-client"; + +export const socket = io(`${baseUri}`, { + autoConnect: false, + transports: ["websocket"], + + timeout: 20000, + reconnection: true, + reconnectionDelay: 1000, + reconnectionAttempts: 5, +});