diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000..e5b6d8d --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json index 54a7982..d023419 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,11 +1,14 @@ { - "$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json", + "$schema": "https://unpkg.com/@changesets/config@3.0.5/schema.json", + "changelog": [ + "@changesets/changelog-github", + { "repo": "apteryxxyz/next-ws" } + ], "commit": false, "fixed": [], "linked": [], "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", - "ignore": [], - "changelog": ["@changesets/changelog-git", { "repo": "apteryxxyz/next-ws" }] + "ignore": [] } diff --git a/.changeset/sweet-spies-impress.md b/.changeset/sweet-spies-impress.md new file mode 100644 index 0000000..011ec0a --- /dev/null +++ b/.changeset/sweet-spies-impress.md @@ -0,0 +1,5 @@ +--- +"next-ws": major +--- + +Merged the core and CLI packages into a single package. This change does not introduce any breaking changes to the public API of the core package. diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..be79a3d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: apteryxxyz +ko_fi: apteryx diff --git a/.github/actions/setup/action.yml b/.github/actions/node/action.yml similarity index 80% rename from .github/actions/setup/action.yml rename to .github/actions/node/action.yml index b1cceb1..a150caa 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/node/action.yml @@ -1,5 +1,5 @@ -name: Setup Workflow -description: Setup workflow for GitHub Actions +name: Setup Node.js +description: Setup Node.js, PNPM, and install dependencies runs: using: "composite" diff --git a/.github/labels.yml b/.github/labels.yml index fb8ece8..4136089 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -21,19 +21,3 @@ - name: dependencies color: d93f0b description: Dependency updates!! - -- name: e:simple-chat-app - color: 0075ca - description: Simple chat app example - -- name: e:with-custom-server - color: 0075ca - description: Example with custom server - -- name: p:cli - color: 0075ca - description: CLI package - -- name: p:core - color: 0075ca - description: Core package diff --git a/.github/pr-labels.yml b/.github/pr-labels.yml index 868a273..547c35c 100644 --- a/.github/pr-labels.yml +++ b/.github/pr-labels.yml @@ -1,19 +1,3 @@ "dependencies": - changed-files: - any-glob-to-any-file: ["pnpm-lock.yaml"] - -"e:simple-chat-app": - - changed-files: - - any-glob-to-any-file: ["examples/simple-chat-app/**"] - -"e:with-custom-server": - - changed-files: - - any-glob-to-any-file: ["examples/with-custom-server/**"] - -"p:cli": - - changed-files: - - any-glob-to-any-file: ["packages/cli/**"] - -"p:core": - - changed-files: - - any-glob-to-any-file: ["packages/core/**"] diff --git a/.github/workflows/bump-next-version.yml b/.github/workflows/bump-next-version.yml new file mode 100644 index 0000000..b7d21bb --- /dev/null +++ b/.github/workflows/bump-next-version.yml @@ -0,0 +1,74 @@ +name: Bump Next.js Version + +on: + # TODO: Test this when the next version of Next.js is released + # schedule: + # - cron: "0 0 * * *" + workflow_dispatch: + +jobs: + check-next-version: + name: Check for New Next.js Version + runs-on: ubuntu-latest + outputs: + current_version: ${{ steps.get-current-version.outputs.current_version }} + latest_version: ${{ steps.get-latest-version.outputs.latest_version }} + bump_needed: ${{ steps.compare-versions.outputs.bump_needed }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: ./.github/actions/node + + - name: Get Current Next.js Version + id: get-current-version + run: | + current_version=$(pnpm list next | grep -o 'next [0-9].*' | cut -d ' ' -f 2) + echo "current_version=$current_version" >> $GITHUB_OUTPUT + + - name: Get Latest Next.js Version + id: get-latest-version + run: | + latest_version=$(npm show next version) + echo "latest_version=$latest_version" >> $GITHUB_OUTPUT + + - name: Compare Versions + id: compare-versions + run: | + if [ "$latest_version" != "$current_version" ]; then + echo "bump_needed=true" >> $GITHUB_OUTPUT + else + echo "bump_needed=false" >> $GITHUB_OUTPUT + + update-next-version: + name: Bump Next.js Version + runs-on: ubuntu-latest + if: ${{ needs.check-next-version.outputs.bump_needed == 'true' }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: ./.github/actions/node + + - name: Update Next.js Version + run: pnpm update next@latest -r + + - name: Bump Supported Range + run: | + new_version="${{ needs.check-next-version.outputs.latest_version }}" + patch_file=$(ls src/patches/patch-*.ts | sort -V | tail -n 1) + sed -i "s/\(versions: .*\) <=[0-9\.]\+/\1 <=$new_version/" "$patch_file" + + - name: Make Pull Request + uses: peter-evans/create-pull-request@v5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "Bump patch supported range to ${{ needs.check-next-version.outputs.latest_version }}" + branch: "bump-next-version" + title: "Bump patch supported range to ${{ needs.check-next-version.outputs.latest_version }}" + body: "Bump patch supported range to ${{ needs.check-next-version.outputs.latest_version }}" + delete-branch: true diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 337086d..d2bd14a 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -2,39 +2,82 @@ name: Continuous Integration on: push: - branches: - - main + branches: [main] pull_request: + branches: [main] types: [opened, synchronize, reopened] workflow_dispatch: workflow_call: jobs: - lint: + check: + name: Type Checking + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: ./.github/actions/node + + - name: Run the type checker + run: pnpm check + + quality: name: Linting runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 + - name: Checkout repository + uses: actions/checkout@v4 - - name: Setup Workflow - uses: ./.github/actions/setup + - name: Setup Node.js + uses: ./.github/actions/node - - name: Run Linter + - name: Run the linter run: pnpm lint build: name: Building - needs: lint runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 + - name: Checkout repository + uses: actions/checkout@v4 - - name: Setup Workflow - uses: ./.github/actions/setup + - name: Setup Node.js + uses: ./.github/actions/node - - name: Build project + - name: Build the package run: pnpm build + + test: + name: Testing + needs: build + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: ./.github/actions/node + + - name: Install playwright browsers + run: pnpm playwright install --with-deps + + - name: Run e2e tests + run: pnpm test + + - name: Upload test outputs + uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: test-outputs + path: | + tests/.report/ + tests/.results/ + include-hidden-files: true + retention-days: 7 diff --git a/.github/workflows/label-sync.yml b/.github/workflows/label-sync.yml index 3efaa04..b67f29a 100644 --- a/.github/workflows/label-sync.yml +++ b/.github/workflows/label-sync.yml @@ -3,24 +3,22 @@ name: Label Sync on: workflow_dispatch: push: - branches: - - main - paths: - - .github/labels.yml + branches: [main] + paths: [.github/labels.yml] jobs: sync: - name: Label sync + name: Label Sync runs-on: ubuntu-latest permissions: contents: read issues: write steps: - - name: Checkout code + - name: Checkout repository uses: actions/checkout@v4 - - name: Label sync + - name: Sync labels uses: crazy-max/ghaction-github-labeler@v5 with: github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr-triage.yml b/.github/workflows/pr-triage.yml index 27a37da..dab0e7f 100644 --- a/.github/workflows/pr-triage.yml +++ b/.github/workflows/pr-triage.yml @@ -6,7 +6,7 @@ on: jobs: label: - name: Label pull request + name: Label Pull Request if: github.event.action != 'edited' runs-on: ubuntu-latest permissions: @@ -14,7 +14,7 @@ jobs: pull-requests: write steps: - - name: Label pull request + - name: Apply pull request labels uses: actions/labeler@v5 with: repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml deleted file mode 100644 index dcad8af..0000000 --- a/.github/workflows/publish-snapshot.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Publish Snapshots - -on: - workflow_dispatch: - push: - branches: - - main - paths: - - "packages/**" - - "package.json" - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -jobs: - release: - name: Release - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Workflow - uses: ./.github/actions/setup - - - name: Publish Snapshots - run: pnpm run publish:snapshot - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/release-snapshot.yml b/.github/workflows/release-snapshot.yml new file mode 100644 index 0000000..056898f --- /dev/null +++ b/.github/workflows/release-snapshot.yml @@ -0,0 +1,26 @@ +name: Release Snapshot + +on: + push: + branches: [main] + paths: [".changesets/**"] + workflow_dispatch: + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + release: + name: Release Snapshot + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: ./.github/actions/node + + - name: Publish snapshot version + run: pnpm run release:snapshot + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 33d6a01..df5cf28 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,10 @@ +# Node node_modules/ -.turbo/ -.nx/ - +# Build dist/ .next/ + +# Test Results +tests/.report/ +tests/.results/ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..7047e02 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,3 @@ +#!/bin/sh +set -eu +pnpm biome check --staged --files-ignore-unknown=true --no-errors-on-unmatched diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 9b30599..0000000 --- a/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -**/.turbo/ -**/node_modules/ -**/tsup.config.ts -**/tsconfig.json -**/*.d.cts -**/*.tgz \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 93b1623..7edf13c 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,8 +1,8 @@ { "recommendations": [ "biomejs.biome", - "jgclark.vscode-todo-highlight", "streetsidesoftware.code-spell-checker", - "formulahendry.auto-rename-tag" + "gruntfuggly.todo-tree", + "ms-playwright.playwright" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index a309239..425cb54 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,70 +1,77 @@ { - "editor.formatOnSave": true, + "cSpell.language": "en-AU", + "explorer.compactFolders": true, "explorer.fileNesting.enabled": true, "explorer.fileNesting.patterns": { - ".env": "*.env, .env.*, .envrc, env.d.ts", + ".env": "*.env, .env.*", "package.json": "pnpm-lock.yaml, pnpm-workspace.yaml", - "*.ts": "$(capture).js, $(capture).d.ts.map, $(capture).*.ts, $(capture)_*.js, $(capture)_*.ts", - "*.tsx": "$(capture).ts, $(capture).*.tsx, $(capture)_*.ts, $(capture)_*.tsx, $(capture).less, $(capture).module.less, $(capture).module.less.d.ts, $(capture).scss, $(capture).module.scss, $(capture).module.scss.d.ts, $(capture).css.ts" + "*.js": "$(capture).js.map, $(capture).d.ts", + "*.jsx": "$(capture).jsx.map, $(capture).d.ts", + "*.d.ts": "$(capture).d.ts.map" }, - "todohighlight.keywords": [ - { - "text": "NOTE:", - "regex": { - "pattern": "NOTE(\\(.*?\\))?:" - }, - "color": "cyan", - "border": "1px solid cyan", - "borderRadius": "2px", - "backgroundColor": "rgba(0,0,0,.2)", - "isWholeLine": true + "editor.formatOnSave": true, + "editor.insertSpaces": true, + "editor.tabSize": 2, + "editor.codeActionsOnSave": { "source.organizeImports.biome": "explicit" }, + "cSpell.words": [], + "todo-tree.general.tags": ["=====", "NOTE", "IDEA", "TODO", "FIX", "HACK"], + "todo-tree.highlights.customHighlight": { + "=====": { + "type": "whole-line", + "icon": "bookmark", + "foreground": "#dadada", + "gutterIcon": true, + "hideFromTree": true + }, + "NOTE": { + "type": "whole-line", + "icon": "note", + "foreground": "#8df7ff", + "gutterIcon": true, + "hideFromTree": true }, - { - "text": "IDEA:", - "regex": { - "pattern": "IDEA(\\(.*?\\))?:" - }, - "color": "lime", - "border": "1px solid green", - "borderRadius": "2px", - "backgroundColor": "rgba(0,0,0,.2)", - "isWholeLine": true + "IDEA": { + "type": "whole-line", + "icon": "light-bulb", + "foreground": "#458efc", + "gutterIcon": true }, - { - "text": "TODO:", - "regex": { - "pattern": "TODO(\\(.*?\\))?:" - }, - "color": "orange", - "border": "1px solid orange", - "borderRadius": "2px", - "backgroundColor": "rgba(0,0,0,.2)", - "isWholeLine": true + "TODO": { + "type": "whole-line", + "icon": "check", + "foreground": "#ff9900", + "gutterIcon": true }, - { - "text": "FIXME:", - "regex": { - "pattern": "FIXME(\\(.*?\\))?:" - }, - "color": "red", - "border": "1px solid red", - "borderRadius": "2px", - "backgroundColor": "rgba(0,0,0,.2)", - "isWholeLine": true + "FIX": { + "type": "whole-line", + "icon": "bug", + "foreground": "#e63d00", + "gutterIcon": true + }, + "HACK": { + "type": "whole-line", + "icon": "tools", + "foreground": "#45da20", + "gutterIcon": true } - ], - "editor.insertSpaces": true, - "editor.tabSize": 2, - "files.exclude": { - "**/.git": true, - "**/.svn": true, - "**/.hg": true, - "**/CVS": true, - "**/.DS_Store": true, - "**/Thumbs.db": true - }, - "editor.codeActionsOnSave": { - "source.organizeImports.biome": "explicit" }, - "cSpell.language": "en-GB" + "workbench.colorCustomizations": { + "activityBar.activeBackground": "#000000", + "activityBar.background": "#000000", + "activityBar.foreground": "#ffffff", + "activityBar.inactiveForeground": "#ffffff99", + "activityBarBadge.background": "#000000", + "activityBarBadge.foreground": "#ffffff", + "commandCenter.border": "#00000099", + "sash.hoverBorder": "#000000", + "statusBar.background": "#000000", + "statusBar.foreground": "#ffffff", + "statusBarItem.hoverBackground": "#000000", + "statusBarItem.remoteBackground": "#000000", + "statusBarItem.remoteForeground": "#ffffff", + "titleBar.activeBackground": "#000000", + "titleBar.activeForeground": "#ffffff", + "titleBar.inactiveBackground": "#00000099", + "titleBar.inactiveForeground": "#ffffff99" + } } diff --git a/README.md b/README.md new file mode 100644 index 0000000..ec471b9 --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +
+

Next WS

+ Add support for WebSockets in Next.js app directory
+ npm install next-ws ws +
+ +
+ package version + total downloads +
+ next-ws repo stars + apteryxxyz followers + discord shield +
+ +## 🤔 About + +`next-ws` is a simple package that adds WebSocket support to your Next.js app directory. With `next-ws`, you no longer need to create a separate WebSocket server to handle WebSocket connections. Instead, you can handle WebSocket connections directly in your Next.js API routes. + +> [!IMPORTANT] +> Next WS is designed for use in server-based environments. It is not suitable for serverless platforms like Vercel, where WebSocket servers are not supported. Furthermore, this plugin is built for the app directory and does not support the older pages directory. + +## 🏓 Table of Contents + +- [📦 Installation](#-installation) +- [🚀 Usage](#-usage) +- [🌀 Examples](#-examples) + +## 📦 Installation + +To set up a WebSocket server with `next-ws`, you need to patch your local Next.js installation. `next-ws` simplifies this process by providing a CLI command that handles the patching for you. Follow these steps to get started: + +1. **Install Dependencies**: Use your preferred package manager to install `next-ws` and its peer dependency `ws`: + + ```bash + npm install next-ws ws + pnpm add next-ws ws + yarn add next-ws ws + ``` + +2. **Add Prepare Script**: Add the following `prepare` script to your `package.json`. The `prepare` script is a lifecycle script that runs automatically when you run `npm install`, ensuring that your Next.js installation is patched with `next-ws` every time you install it: + + ```json + { + "scripts": { + "prepare": "next-ws patch" + } + } + ``` + +## 🚀 Usage + +Using WebSocket connections in your Next.js app directory is simple with `next-ws`. You can handle WebSocket connections directly in your API routes via exported `SOCKET` functions. Here's an example of a simple WebSocket echo server: + +```js +export function SOCKET( + client: import('ws').WebSocket, + request: import('http').IncomingMessage, + server: import('ws').WebSocketServer, + context: { params: Record }, +) { + // ... +} +``` + +## 🌀 Examples + +> [!TIP] +> For more detailed examples, refer the [`examples` directory](https://github.com/apteryxxyz/next-ws/tree/main/examples). + +### Echo Server + +This example demonstrates a simple WebSocket echo server that sends back any message it receives. Create a new API route file anywhere in your app directory and export a `SOCKET` function to handle WebSocket connections: + +```ts +// app/api/ws/route.ts (can be any route file in the app directory) + +export function SOCKET( + client: import("ws").WebSocket, + request: import("http").IncomingMessage, + server: import("ws").WebSocketServer +) { + console.log("A client connected"); + + client.on("message", (message) => { + console.log("Received message:", message); + client.send(message); + }); + + client.on("close", () => { + console.log("A client disconnected"); + }); +} +``` + +You can now connect to your WebSocket server, send it a message and receive the same message back. + +### Chat Room + +See the [chat room example](https://github.com/apteryxxyz/next-ws/tree/main/examples/chat-room). diff --git a/biome.json b/biome.json index 8b2e2d7..ae72458 100644 --- a/biome.json +++ b/biome.json @@ -1,13 +1,14 @@ { - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", - "files": { - "ignore": ["node_modules/*", "dist/*", ".next/*"] - }, + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", + "files": { "ignore": ["node_modules/*", "dist/*", ".next/*"] }, + "formatter": { "enabled": true, "indentStyle": "space", + "indentWidth": 2, "lineEnding": "lf" }, + "linter": { "enabled": true, "rules": { @@ -15,15 +16,17 @@ "style": { "noNonNullAssertion": "off", "noUselessElse": "off", - "noParameterAssign": "off", - "noCommaOperator": "off" + "noParameterAssign": "off" }, "suspicious": { "noAssignInExpressions": "off", "noArrayIndexKey": "off" }, + "a11y": { + "useSemanticElements": "off", + "useFocusableInteractive": "off" + }, "nursery": { - "recommended": true, "useSortedClasses": { "level": "warn", "options": { @@ -34,11 +37,23 @@ } } }, + "organizeImports": { + "enabled": true + }, + "javascript": { "formatter": { "quoteStyle": "single", "jsxQuoteStyle": "double", "semicolons": "always" } + }, + "css": { + "formatter": { + "enabled": false + }, + "linter": { + "enabled": false + } } } diff --git a/examples/chat-room-with-custom-server/app/chat/_shared/message-list.tsx b/examples/chat-room-with-custom-server/app/chat/_shared/message-list.tsx new file mode 100644 index 0000000..e165782 --- /dev/null +++ b/examples/chat-room-with-custom-server/app/chat/_shared/message-list.tsx @@ -0,0 +1,38 @@ +import type { Message } from './message'; + +export function MessageList({ messages }: { messages: Message[] }) { + return ( + + ); +} diff --git a/examples/chat-room-with-custom-server/app/chat/_shared/message-submit.tsx b/examples/chat-room-with-custom-server/app/chat/_shared/message-submit.tsx new file mode 100644 index 0000000..799929a --- /dev/null +++ b/examples/chat-room-with-custom-server/app/chat/_shared/message-submit.tsx @@ -0,0 +1,44 @@ +import { useCallback } from 'react'; +import type { Message } from './message'; + +export function MessageSubmit({ + onMessage, +}: { + onMessage(message: Message): void; +}) { + const handleSubmit = useCallback( + (event: React.FormEvent) => { + event.preventDefault(); + const form = new FormData(event.currentTarget); + const author = form.get('author') as string; + const content = form.get('content') as string; + if (!author || !content) return; + + onMessage({ author, content }); + + // Reset the content input (only) + const contentInputElement = event.currentTarget // + .querySelector('input[name="content"]'); + contentInputElement.value = ''; + }, + [onMessage], + ); + + return ( +
+ + + +
+ ); +} diff --git a/examples/chat-room-with-custom-server/app/chat/_shared/message.ts b/examples/chat-room-with-custom-server/app/chat/_shared/message.ts new file mode 100644 index 0000000..7c72dda --- /dev/null +++ b/examples/chat-room-with-custom-server/app/chat/_shared/message.ts @@ -0,0 +1,4 @@ +export interface Message { + author: string; + content: string; +} diff --git a/examples/chat-room-with-custom-server/app/chat/dynamic/[language]/socket/route.ts b/examples/chat-room-with-custom-server/app/chat/dynamic/[language]/socket/route.ts new file mode 100644 index 0000000..f97a1e9 --- /dev/null +++ b/examples/chat-room-with-custom-server/app/chat/dynamic/[language]/socket/route.ts @@ -0,0 +1,47 @@ +export function GET() { + const headers = new Headers(); + headers.set('Connection', 'Upgrade'); + headers.set('Upgrade', 'websocket'); + return new Response('Upgrade Required', { status: 426, headers }); +} + +export function SOCKET( + client: import('ws').WebSocket, + _request: import('node:http').IncomingMessage, + server: import('ws').WebSocketServer, + { params }: { params: { language: string } }, +) { + for (const other of server.clients) + if (client !== other && other.readyState === other.OPEN) + other.send( + JSON.stringify({ + author: 'System', + content: `A new user joined the chat, their language is ${params.language}`, + }), + ); + + client.on('message', (message) => { + // Forward the message to all other clients + for (const other of server.clients) + if (client !== other && other.readyState === other.OPEN) + other.send(message); + }); + + client.send( + JSON.stringify({ + author: 'System', + content: `Welcome to the chat! Your language is ${params.language}. There ${server.clients.size - 1 === 1 ? 'is 1 other user' : `are ${server.clients.size - 1 || 'no'} other users`} online`, + }), + ); + + return () => { + for (const other of server.clients) + if (client !== other && other.readyState === other.OPEN) + other.send( + JSON.stringify({ + author: 'System', + content: 'A user left the chat', + }), + ); + }; +} diff --git a/examples/chat-room-with-custom-server/app/chat/dynamic/page.tsx b/examples/chat-room-with-custom-server/app/chat/dynamic/page.tsx new file mode 100644 index 0000000..c5339cc --- /dev/null +++ b/examples/chat-room-with-custom-server/app/chat/dynamic/page.tsx @@ -0,0 +1,47 @@ +'use client'; + +import { useCallback, useEffect, useRef, useState } from 'react'; +import type { Message } from '../_shared/message'; +import { MessageList } from '../_shared/message-list'; +import { MessageSubmit } from '../_shared/message-submit'; + +export default function Page() { + const socketRef = useRef(null); + useEffect(() => { + if (typeof window === 'undefined') return; + + const language = navigator.language; + const url = `ws://${window.location.host}/chat/dynamic/${language}/socket`; + socketRef.current = new WebSocket(url); + return () => { + socketRef.current?.close(); + socketRef.current = null; + }; + }, []); + + const [messages, setMessages] = useState([]); + const onMessage = useCallback((message: Message) => { + socketRef.current?.send(JSON.stringify(message)); + setMessages((messages) => [...messages, { ...message, author: 'You' }]); + }, []); + + useEffect(() => { + async function handleMessage(event: MessageEvent) { + const payload = + typeof event.data === 'string' ? event.data : await event.data.text(); + const message = JSON.parse(payload) as Message; + setMessages((p) => [...p, message]); + } + + socketRef.current?.addEventListener('message', handleMessage); + return () => + socketRef.current?.removeEventListener('message', handleMessage); + }, []); + + return ( +
+ + +
+ ); +} diff --git a/examples/chat-room-with-custom-server/app/chat/simple/page.tsx b/examples/chat-room-with-custom-server/app/chat/simple/page.tsx new file mode 100644 index 0000000..deb25f9 --- /dev/null +++ b/examples/chat-room-with-custom-server/app/chat/simple/page.tsx @@ -0,0 +1,46 @@ +'use client'; + +import { useCallback, useEffect, useRef, useState } from 'react'; +import type { Message } from '../_shared/message'; +import { MessageList } from '../_shared/message-list'; +import { MessageSubmit } from '../_shared/message-submit'; + +export default function Page() { + const socketRef = useRef(null); + useEffect(() => { + if (typeof window === 'undefined') return; + + const url = `ws://${window.location.host}/chat/simple/socket`; + socketRef.current = new WebSocket(url); + return () => { + socketRef.current?.close(); + socketRef.current = null; + }; + }, []); + + const [messages, setMessages] = useState([]); + const onMessage = useCallback((message: Message) => { + socketRef.current?.send(JSON.stringify(message)); + setMessages((messages) => [...messages, { ...message, author: 'You' }]); + }, []); + + useEffect(() => { + async function handleMessage(event: MessageEvent) { + const payload = + typeof event.data === 'string' ? event.data : await event.data.text(); + const message = JSON.parse(payload) as Message; + setMessages((p) => [...p, message]); + } + + socketRef.current?.addEventListener('message', handleMessage); + return () => + socketRef.current?.removeEventListener('message', handleMessage); + }, []); + + return ( +
+ + +
+ ); +} diff --git a/examples/chat-room-with-custom-server/app/chat/simple/socket/route.ts b/examples/chat-room-with-custom-server/app/chat/simple/socket/route.ts new file mode 100644 index 0000000..59a7058 --- /dev/null +++ b/examples/chat-room-with-custom-server/app/chat/simple/socket/route.ts @@ -0,0 +1,46 @@ +export function GET() { + const headers = new Headers(); + headers.set('Connection', 'Upgrade'); + headers.set('Upgrade', 'websocket'); + return new Response('Upgrade Required', { status: 426, headers }); +} + +export function SOCKET( + client: import('ws').WebSocket, + _request: import('node:http').IncomingMessage, + server: import('ws').WebSocketServer, +) { + for (const other of server.clients) + if (client !== other && other.readyState === other.OPEN) + other.send( + JSON.stringify({ + author: 'System', + content: 'A new user joined the chat', + }), + ); + + client.on('message', (message) => { + // Forward the message to all other clients + for (const other of server.clients) + if (client !== other && other.readyState === other.OPEN) + other.send(message); + }); + + client.send( + JSON.stringify({ + author: 'System', + content: `Welcome to the chat! There ${server.clients.size - 1 === 1 ? 'is 1 other user' : `are ${server.clients.size - 1 || 'no'} other users`} online`, + }), + ); + + return () => { + for (const other of server.clients) + if (client !== other && other.readyState === other.OPEN) + other.send( + JSON.stringify({ + author: 'System', + content: 'A user left the chat', + }), + ); + }; +} diff --git a/examples/with-custom-server/src/app/layout.tsx b/examples/chat-room-with-custom-server/app/layout.tsx similarity index 55% rename from examples/with-custom-server/src/app/layout.tsx rename to examples/chat-room-with-custom-server/app/layout.tsx index 8bcb3c3..6fb2199 100644 --- a/examples/with-custom-server/src/app/layout.tsx +++ b/examples/chat-room-with-custom-server/app/layout.tsx @@ -1,8 +1,4 @@ -'use client'; - -import { WebSocketProvider } from 'next-ws/client'; - -export default function Layout(p: React.PropsWithChildren) { +export default function Layout({ children }: React.PropsWithChildren) { return ( - - {p.children} - + {children} ); diff --git a/examples/with-custom-server/next-env.d.ts b/examples/chat-room-with-custom-server/next-env.d.ts similarity index 57% rename from examples/with-custom-server/next-env.d.ts rename to examples/chat-room-with-custom-server/next-env.d.ts index 4f11a03..1b3be08 100644 --- a/examples/with-custom-server/next-env.d.ts +++ b/examples/chat-room-with-custom-server/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/examples/simple-chat-app/next.config.js b/examples/chat-room-with-custom-server/next.config.js similarity index 100% rename from examples/simple-chat-app/next.config.js rename to examples/chat-room-with-custom-server/next.config.js diff --git a/examples/chat-room-with-custom-server/package.json b/examples/chat-room-with-custom-server/package.json new file mode 100644 index 0000000..e2d82c0 --- /dev/null +++ b/examples/chat-room-with-custom-server/package.json @@ -0,0 +1,19 @@ +{ + "name": "chat-room-with-custom-server", + "private": true, + "scripts": { + "build": "next build", + "start": "next start", + "dev": "next dev", + "prepare": "next-ws patch" + }, + "dependencies": { + "next": "^15.1.6", + "next-ws": "workspace:^", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@types/react": "^19.0.8" + } +} diff --git a/examples/with-custom-server/src/server.ts b/examples/chat-room-with-custom-server/server.ts similarity index 100% rename from examples/with-custom-server/src/server.ts rename to examples/chat-room-with-custom-server/server.ts diff --git a/examples/chat-room-with-custom-server/tsconfig.json b/examples/chat-room-with-custom-server/tsconfig.json new file mode 100644 index 0000000..59821be --- /dev/null +++ b/examples/chat-room-with-custom-server/tsconfig.json @@ -0,0 +1,21 @@ +{ + "include": ["app/**/*", "server.ts", ".next/types/**/*.ts"], + "exclude": ["node_modules"], + + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "noEmit": true, + "incremental": true, + "module": "esnext", + "esModuleInterop": true, + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "plugins": [{ "name": "next" }] + } +} diff --git a/examples/chat-room/.vscode/settings.json b/examples/chat-room/.vscode/settings.json new file mode 100644 index 0000000..fc0f027 --- /dev/null +++ b/examples/chat-room/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "typescript.tsdk": "..\\..\\node_modules\\.pnpm\\typescript@5.7.3\\node_modules\\typescript\\lib", + "typescript.enablePromptUseWorkspaceTsdk": true +} diff --git a/examples/chat-room/app/chat/_shared/message-list.tsx b/examples/chat-room/app/chat/_shared/message-list.tsx new file mode 100644 index 0000000..e165782 --- /dev/null +++ b/examples/chat-room/app/chat/_shared/message-list.tsx @@ -0,0 +1,38 @@ +import type { Message } from './message'; + +export function MessageList({ messages }: { messages: Message[] }) { + return ( +
    + {messages.map((message, i) => ( +
  • + {message.author}: {message.content} +
  • + ))} + + {messages.length === 0 && ( +
    +

    Waiting for messages...

    +
    + )} +
+ ); +} diff --git a/examples/chat-room/app/chat/_shared/message-submit.tsx b/examples/chat-room/app/chat/_shared/message-submit.tsx new file mode 100644 index 0000000..799929a --- /dev/null +++ b/examples/chat-room/app/chat/_shared/message-submit.tsx @@ -0,0 +1,44 @@ +import { useCallback } from 'react'; +import type { Message } from './message'; + +export function MessageSubmit({ + onMessage, +}: { + onMessage(message: Message): void; +}) { + const handleSubmit = useCallback( + (event: React.FormEvent) => { + event.preventDefault(); + const form = new FormData(event.currentTarget); + const author = form.get('author') as string; + const content = form.get('content') as string; + if (!author || !content) return; + + onMessage({ author, content }); + + // Reset the content input (only) + const contentInputElement = event.currentTarget // + .querySelector('input[name="content"]'); + contentInputElement.value = ''; + }, + [onMessage], + ); + + return ( +
+ + + +
+ ); +} diff --git a/examples/chat-room/app/chat/_shared/message.ts b/examples/chat-room/app/chat/_shared/message.ts new file mode 100644 index 0000000..7c72dda --- /dev/null +++ b/examples/chat-room/app/chat/_shared/message.ts @@ -0,0 +1,4 @@ +export interface Message { + author: string; + content: string; +} diff --git a/examples/chat-room/app/chat/dynamic/[language]/socket/route.ts b/examples/chat-room/app/chat/dynamic/[language]/socket/route.ts new file mode 100644 index 0000000..f97a1e9 --- /dev/null +++ b/examples/chat-room/app/chat/dynamic/[language]/socket/route.ts @@ -0,0 +1,47 @@ +export function GET() { + const headers = new Headers(); + headers.set('Connection', 'Upgrade'); + headers.set('Upgrade', 'websocket'); + return new Response('Upgrade Required', { status: 426, headers }); +} + +export function SOCKET( + client: import('ws').WebSocket, + _request: import('node:http').IncomingMessage, + server: import('ws').WebSocketServer, + { params }: { params: { language: string } }, +) { + for (const other of server.clients) + if (client !== other && other.readyState === other.OPEN) + other.send( + JSON.stringify({ + author: 'System', + content: `A new user joined the chat, their language is ${params.language}`, + }), + ); + + client.on('message', (message) => { + // Forward the message to all other clients + for (const other of server.clients) + if (client !== other && other.readyState === other.OPEN) + other.send(message); + }); + + client.send( + JSON.stringify({ + author: 'System', + content: `Welcome to the chat! Your language is ${params.language}. There ${server.clients.size - 1 === 1 ? 'is 1 other user' : `are ${server.clients.size - 1 || 'no'} other users`} online`, + }), + ); + + return () => { + for (const other of server.clients) + if (client !== other && other.readyState === other.OPEN) + other.send( + JSON.stringify({ + author: 'System', + content: 'A user left the chat', + }), + ); + }; +} diff --git a/examples/chat-room/app/chat/dynamic/page.tsx b/examples/chat-room/app/chat/dynamic/page.tsx new file mode 100644 index 0000000..c5339cc --- /dev/null +++ b/examples/chat-room/app/chat/dynamic/page.tsx @@ -0,0 +1,47 @@ +'use client'; + +import { useCallback, useEffect, useRef, useState } from 'react'; +import type { Message } from '../_shared/message'; +import { MessageList } from '../_shared/message-list'; +import { MessageSubmit } from '../_shared/message-submit'; + +export default function Page() { + const socketRef = useRef(null); + useEffect(() => { + if (typeof window === 'undefined') return; + + const language = navigator.language; + const url = `ws://${window.location.host}/chat/dynamic/${language}/socket`; + socketRef.current = new WebSocket(url); + return () => { + socketRef.current?.close(); + socketRef.current = null; + }; + }, []); + + const [messages, setMessages] = useState([]); + const onMessage = useCallback((message: Message) => { + socketRef.current?.send(JSON.stringify(message)); + setMessages((messages) => [...messages, { ...message, author: 'You' }]); + }, []); + + useEffect(() => { + async function handleMessage(event: MessageEvent) { + const payload = + typeof event.data === 'string' ? event.data : await event.data.text(); + const message = JSON.parse(payload) as Message; + setMessages((p) => [...p, message]); + } + + socketRef.current?.addEventListener('message', handleMessage); + return () => + socketRef.current?.removeEventListener('message', handleMessage); + }, []); + + return ( +
+ + +
+ ); +} diff --git a/examples/chat-room/app/chat/simple/page.tsx b/examples/chat-room/app/chat/simple/page.tsx new file mode 100644 index 0000000..deb25f9 --- /dev/null +++ b/examples/chat-room/app/chat/simple/page.tsx @@ -0,0 +1,46 @@ +'use client'; + +import { useCallback, useEffect, useRef, useState } from 'react'; +import type { Message } from '../_shared/message'; +import { MessageList } from '../_shared/message-list'; +import { MessageSubmit } from '../_shared/message-submit'; + +export default function Page() { + const socketRef = useRef(null); + useEffect(() => { + if (typeof window === 'undefined') return; + + const url = `ws://${window.location.host}/chat/simple/socket`; + socketRef.current = new WebSocket(url); + return () => { + socketRef.current?.close(); + socketRef.current = null; + }; + }, []); + + const [messages, setMessages] = useState([]); + const onMessage = useCallback((message: Message) => { + socketRef.current?.send(JSON.stringify(message)); + setMessages((messages) => [...messages, { ...message, author: 'You' }]); + }, []); + + useEffect(() => { + async function handleMessage(event: MessageEvent) { + const payload = + typeof event.data === 'string' ? event.data : await event.data.text(); + const message = JSON.parse(payload) as Message; + setMessages((p) => [...p, message]); + } + + socketRef.current?.addEventListener('message', handleMessage); + return () => + socketRef.current?.removeEventListener('message', handleMessage); + }, []); + + return ( +
+ + +
+ ); +} diff --git a/examples/chat-room/app/chat/simple/socket/route.ts b/examples/chat-room/app/chat/simple/socket/route.ts new file mode 100644 index 0000000..59a7058 --- /dev/null +++ b/examples/chat-room/app/chat/simple/socket/route.ts @@ -0,0 +1,46 @@ +export function GET() { + const headers = new Headers(); + headers.set('Connection', 'Upgrade'); + headers.set('Upgrade', 'websocket'); + return new Response('Upgrade Required', { status: 426, headers }); +} + +export function SOCKET( + client: import('ws').WebSocket, + _request: import('node:http').IncomingMessage, + server: import('ws').WebSocketServer, +) { + for (const other of server.clients) + if (client !== other && other.readyState === other.OPEN) + other.send( + JSON.stringify({ + author: 'System', + content: 'A new user joined the chat', + }), + ); + + client.on('message', (message) => { + // Forward the message to all other clients + for (const other of server.clients) + if (client !== other && other.readyState === other.OPEN) + other.send(message); + }); + + client.send( + JSON.stringify({ + author: 'System', + content: `Welcome to the chat! There ${server.clients.size - 1 === 1 ? 'is 1 other user' : `are ${server.clients.size - 1 || 'no'} other users`} online`, + }), + ); + + return () => { + for (const other of server.clients) + if (client !== other && other.readyState === other.OPEN) + other.send( + JSON.stringify({ + author: 'System', + content: 'A user left the chat', + }), + ); + }; +} diff --git a/examples/simple-chat-app/src/app/layout.tsx b/examples/chat-room/app/layout.tsx similarity index 55% rename from examples/simple-chat-app/src/app/layout.tsx rename to examples/chat-room/app/layout.tsx index 8bcb3c3..6fb2199 100644 --- a/examples/simple-chat-app/src/app/layout.tsx +++ b/examples/chat-room/app/layout.tsx @@ -1,8 +1,4 @@ -'use client'; - -import { WebSocketProvider } from 'next-ws/client'; - -export default function Layout(p: React.PropsWithChildren) { +export default function Layout({ children }: React.PropsWithChildren) { return ( - - {p.children} - + {children} ); diff --git a/examples/simple-chat-app/next-env.d.ts b/examples/chat-room/next-env.d.ts similarity index 53% rename from examples/simple-chat-app/next-env.d.ts rename to examples/chat-room/next-env.d.ts index 40c3d68..1b3be08 100644 --- a/examples/simple-chat-app/next-env.d.ts +++ b/examples/chat-room/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/examples/with-custom-server/next.config.js b/examples/chat-room/next.config.js similarity index 100% rename from examples/with-custom-server/next.config.js rename to examples/chat-room/next.config.js diff --git a/examples/chat-room/package.json b/examples/chat-room/package.json new file mode 100644 index 0000000..047d734 --- /dev/null +++ b/examples/chat-room/package.json @@ -0,0 +1,19 @@ +{ + "name": "chat-room", + "private": true, + "scripts": { + "build": "next build", + "start": "next start", + "dev": "next dev", + "prepare": "next-ws patch" + }, + "dependencies": { + "next": "15.1.6", + "next-ws": "workspace:^", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@types/react": "^19.0.8" + } +} diff --git a/examples/chat-room/tsconfig.json b/examples/chat-room/tsconfig.json new file mode 100644 index 0000000..3fbacb9 --- /dev/null +++ b/examples/chat-room/tsconfig.json @@ -0,0 +1,25 @@ +{ + "include": ["app/**/*", ".next/types/**/*.ts"], + "exclude": ["node_modules"], + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "noEmit": true, + "incremental": true, + "module": "esnext", + "esModuleInterop": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "plugins": [ + { + "name": "next" + } + ], + "forceConsistentCasingInFileNames": true + } +} diff --git a/examples/simple-chat-app/package-lock.json b/examples/simple-chat-app/package-lock.json deleted file mode 100644 index 8939cca..0000000 --- a/examples/simple-chat-app/package-lock.json +++ /dev/null @@ -1,985 +0,0 @@ -{ - "name": "simple-chat-app", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "simple-chat-app", - "dependencies": { - "next": "^15.0.1", - "next-ws": "^1.1.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "ws": "^8.16.0" - }, - "devDependencies": { - "@types/node": "^20.14.9", - "@types/react": "18.3.3", - "@types/react-dom": "^18.3.0", - "@types/ws": "^8.5.10" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", - "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", - "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", - "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", - "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", - "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", - "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", - "cpu": [ - "arm" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", - "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", - "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", - "cpu": [ - "s390x" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", - "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", - "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", - "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", - "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", - "cpu": [ - "arm" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.5" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", - "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", - "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", - "cpu": [ - "s390x" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", - "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", - "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", - "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", - "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", - "cpu": [ - "wasm32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.2.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", - "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", - "cpu": [ - "ia32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@next/env": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.0.1.tgz", - "integrity": "sha512-lc4HeDUKO9gxxlM5G2knTRifqhsY6yYpwuHspBZdboZe0Gp+rZHBNNSIjmQKDJIdRXiXGyVnSD6gafrbQPvILQ==", - "license": "MIT" - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.0.1.tgz", - "integrity": "sha512-C9k/Xv4sxkQRTA37Z6MzNq3Yb1BJMmSqjmwowoWEpbXTkAdfOwnoKOpAb71ItSzoA26yUTIo6ZhN8rKGu4ExQw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.0.1.tgz", - "integrity": "sha512-uHl13HXOuq1G7ovWFxCACDJHTSDVbn/sbLv8V1p+7KIvTrYQ5HNoSmKBdYeEKRRCbEmd+OohOgg9YOp8Ux3MBg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.0.1.tgz", - "integrity": "sha512-LvyhvxHOihFTEIbb35KxOc3q8w8G4xAAAH/AQnsYDEnOvwawjL2eawsB59AX02ki6LJdgDaHoTEnC54Gw+82xw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.0.1.tgz", - "integrity": "sha512-vFmCGUFNyk/A5/BYcQNhAQqPIw01RJaK6dRO+ZEhz0DncoW+hJW1kZ8aH2UvTX27zPq3m85zN5waMSbZEmANcQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.0.1.tgz", - "integrity": "sha512-5by7IYq0NCF8rouz6Qg9T97jYU68kaClHPfGpQG2lCZpSYHtSPQF1kjnqBTd34RIqPKMbCa4DqCufirgr8HM5w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.0.1.tgz", - "integrity": "sha512-lmYr6H3JyDNBJLzklGXLfbehU3ay78a+b6UmBGlHls4xhDXBNZfgb0aI67sflrX+cGBnv1LgmWzFlYrAYxS1Qw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.0.1.tgz", - "integrity": "sha512-DS8wQtl6diAj0eZTdH0sefykm4iXMbHT4MOvLwqZiIkeezKpkgPFcEdFlz3vKvXa2R/2UEgMh48z1nEpNhjeOQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.0.1.tgz", - "integrity": "sha512-4Ho2ggvDdMKlZ/0e9HNdZ9ngeaBwtc+2VS5oCeqrbXqOgutX6I4U2X/42VBw0o+M5evn4/7v3zKgGHo+9v/VjA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "license": "Apache-2.0" - }, - "node_modules/@swc/helpers": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.13.tgz", - "integrity": "sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/node": { - "version": "20.14.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", - "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/prop-types": { - "version": "15.7.12", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", - "dev": true - }, - "node_modules/@types/react": { - "version": "18.3.3", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", - "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "dev": true, - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", - "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001599", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz", - "integrity": "sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "license": "MIT" - }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT", - "optional": true - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true - }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT", - "optional": true - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/next": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/next/-/next-15.0.1.tgz", - "integrity": "sha512-PSkFkr/w7UnFWm+EP8y/QpHrJXMqpZzAXpergB/EqLPOh4SGPJXv1wj4mslr2hUZBAS9pX7/9YLIdxTv6fwytw==", - "license": "MIT", - "dependencies": { - "@next/env": "15.0.1", - "@swc/counter": "0.1.3", - "@swc/helpers": "0.5.13", - "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001579", - "postcss": "8.4.31", - "styled-jsx": "5.1.6" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": ">=18.18.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "15.0.1", - "@next/swc-darwin-x64": "15.0.1", - "@next/swc-linux-arm64-gnu": "15.0.1", - "@next/swc-linux-arm64-musl": "15.0.1", - "@next/swc-linux-x64-gnu": "15.0.1", - "@next/swc-linux-x64-musl": "15.0.1", - "@next/swc-win32-arm64-msvc": "15.0.1", - "@next/swc-win32-x64-msvc": "15.0.1", - "sharp": "^0.33.5" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", - "babel-plugin-react-compiler": "*", - "react": "^18.2.0 || 19.0.0-rc-69d4b800-20241021", - "react-dom": "^18.2.0 || 19.0.0-rc-69d4b800-20241021", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@playwright/test": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "node_modules/next-ws": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/next-ws/-/next-ws-1.1.1.tgz", - "integrity": "sha512-J/wNgcd1lXIkrFbRNP60lNVeAVieIDlxahkQ5jpfX3QJhuLPYLBh/CFvX82nkLUBlXANQbTqg9F9yU4XxjmUIg==", - "license": "MIT", - "peerDependencies": { - "next": ">=13.1.1", - "react": "*", - "ws": "*" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, - "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sharp": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", - "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", - "hasInstallScript": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.3" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.5", - "@img/sharp-darwin-x64": "0.33.5", - "@img/sharp-libvips-darwin-arm64": "1.0.4", - "@img/sharp-libvips-darwin-x64": "1.0.4", - "@img/sharp-libvips-linux-arm": "1.0.5", - "@img/sharp-libvips-linux-arm64": "1.0.4", - "@img/sharp-libvips-linux-s390x": "1.0.4", - "@img/sharp-libvips-linux-x64": "1.0.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", - "@img/sharp-libvips-linuxmusl-x64": "1.0.4", - "@img/sharp-linux-arm": "0.33.5", - "@img/sharp-linux-arm64": "0.33.5", - "@img/sharp-linux-s390x": "0.33.5", - "@img/sharp-linux-x64": "0.33.5", - "@img/sharp-linuxmusl-arm64": "0.33.5", - "@img/sharp-linuxmusl-x64": "0.33.5", - "@img/sharp-wasm32": "0.33.5", - "@img/sharp-win32-ia32": "0.33.5", - "@img/sharp-win32-x64": "0.33.5" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "license": "MIT", - "optional": true, - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/styled-jsx": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", - "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", - "license": "MIT", - "dependencies": { - "client-only": "0.0.1" - }, - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", - "license": "0BSD" - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, - "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", - "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 - } - } - } - } -} diff --git a/examples/simple-chat-app/package.json b/examples/simple-chat-app/package.json deleted file mode 100644 index 5604d24..0000000 --- a/examples/simple-chat-app/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "simple-chat-app", - "private": true, - "dependencies": { - "next": "^15.0.1", - "next-ws": "^1.1.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "ws": "^8.16.0" - }, - "devDependencies": { - "@types/node": "^20.14.9", - "@types/react": "18.3.3", - "@types/react-dom": "^18.3.0", - "@types/ws": "^8.5.10" - } -} diff --git a/examples/simple-chat-app/src/app/api/ws/route.ts b/examples/simple-chat-app/src/app/api/ws/route.ts deleted file mode 100644 index 36dc6e9..0000000 --- a/examples/simple-chat-app/src/app/api/ws/route.ts +++ /dev/null @@ -1,40 +0,0 @@ -export function GET() { - const headers = new Headers(); - headers.set('Connection', 'Upgrade'); - headers.set('Upgrade', 'websocket'); - return new Response('Upgrade Required', { status: 426, headers }); -} - -export function SOCKET( - client: import('ws').WebSocket, - _request: import('node:http').IncomingMessage, - server: import('ws').WebSocketServer, -) { - const { send, broadcast } = createHelpers(client, server); - - // When a new client connects broadcast a connect message - broadcast({ author: 'Server', content: 'A new client has connected.' }); - send({ author: 'Server', content: 'Welcome!' }); - - // Relay any message back to other clients - client.on('message', broadcast); - - // When this client disconnects broadcast a disconnect message - client.on('close', () => { - broadcast({ author: 'Server', content: 'A client has disconnected.' }); - }); -} - -function createHelpers( - client: import('ws').WebSocket, - server: import('ws').WebSocketServer, -) { - const send = (payload: unknown) => client.send(JSON.stringify(payload)); - const broadcast = (payload: unknown) => { - if (payload instanceof Buffer) payload = payload.toString(); - if (typeof payload !== 'string') payload = JSON.stringify(payload); - for (const other of server.clients) - if (other !== client) other.send(String(payload)); - }; - return { send, broadcast }; -} diff --git a/examples/simple-chat-app/src/app/page.tsx b/examples/simple-chat-app/src/app/page.tsx deleted file mode 100644 index 02c7dee..0000000 --- a/examples/simple-chat-app/src/app/page.tsx +++ /dev/null @@ -1,89 +0,0 @@ -'use client'; - -import { useWebSocket } from 'next-ws/client'; -import { useCallback, useEffect, useState } from 'react'; - -export default function Page() { - const ws = useWebSocket(); - - type Message = { author: string; content: string }; - const [messages, setMessages] = useState([]); - - type HandleSubmit = (ev: React.FormEvent) => void; - const handleSubmit = useCallback( - (ev) => { - ev.preventDefault(); - const form = new FormData(ev.currentTarget); - const author = form.get('author') as string; - const content = form.get('content') as string; - if (!author || !content) return; - - ws?.send(JSON.stringify({ author, content })); - setMessages((p) => [...p, { author: 'You', content }]); - - // Reset the content input (only) - const contentInputElement = ev.currentTarget.querySelector( - 'input[name="content"]', - ) as HTMLInputElement; - contentInputElement.value = ''; - }, - [ws], - ); - - useEffect(() => { - async function onMessage(event: MessageEvent) { - const payload = - typeof event.data === 'string' ? event.data : await event.data.text(); - const message = JSON.parse(payload) as Message; - setMessages((p) => [...p, message]); - } - - ws?.addEventListener('message', onMessage); - return () => ws?.removeEventListener('message', onMessage); - }, [ws]); - - return ( -
-
- {messages.map((message, i) => ( -
- {message.author}: {message.content} -
- ))} - - {messages.length === 0 && ( -
-

Waiting for messages...

-
- )} -
- -
- - - -
-
- ); -} diff --git a/examples/simple-chat-app/tsconfig.json b/examples/simple-chat-app/tsconfig.json deleted file mode 100644 index 0fd2353..0000000 --- a/examples/simple-chat-app/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "include": ["src/**/*", ".next/types/**/*.ts"], - "exclude": ["node_modules"], - "compilerOptions": { - "baseUrl": ".", - "outDir": "dist", - "paths": { "~/*": ["src/*"] }, - "jsx": "preserve", - "allowJs": true, - "skipLibCheck": true, - "noEmit": true, - "incremental": true, - "plugins": [{ "name": "next" }], - "lib": ["dom", "es2023"], - "strictNullChecks": true - } -} diff --git a/examples/with-custom-server/package-lock.json b/examples/with-custom-server/package-lock.json deleted file mode 100644 index 536cb89..0000000 --- a/examples/with-custom-server/package-lock.json +++ /dev/null @@ -1,502 +0,0 @@ -{ - "name": "with-custom-server", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "with-custom-server", - "dependencies": { - "next": "^14.2.4", - "next-ws": "file:../../packages/core", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "ws": "^8.16.0" - }, - "devDependencies": { - "@types/node": "^20.14.9", - "@types/react": "18.3.3", - "@types/react-dom": "^18.3.0", - "@types/ws": "^8.5.10" - } - }, - "../../packages/core": { - "name": "next-ws", - "version": "1.1.0", - "license": "MIT", - "devDependencies": { - "@configs/tsup": "workspace:^", - "@types/react": "^18.3.3", - "@types/ws": "^8.5.10", - "next": "^14.2.4", - "react": "^18.3.1", - "ws": "^8.17.1" - }, - "peerDependencies": { - "next": ">=13.1.1", - "react": "*", - "ws": "*" - } - }, - "node_modules/@next/env": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.4.tgz", - "integrity": "sha512-3EtkY5VDkuV2+lNmKlbkibIJxcO4oIHEhBWne6PaAp+76J9KoSsGvNikp6ivzAT8dhhBMYrm6op2pS1ApG0Hzg==" - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.4.tgz", - "integrity": "sha512-AH3mO4JlFUqsYcwFUHb1wAKlebHU/Hv2u2kb1pAuRanDZ7pD/A/KPD98RHZmwsJpdHQwfEc/06mgpSzwrJYnNg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.4.tgz", - "integrity": "sha512-QVadW73sWIO6E2VroyUjuAxhWLZWEpiFqHdZdoQ/AMpN9YWGuHV8t2rChr0ahy+irKX5mlDU7OY68k3n4tAZTg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.4.tgz", - "integrity": "sha512-KT6GUrb3oyCfcfJ+WliXuJnD6pCpZiosx2X3k66HLR+DMoilRb76LpWPGb4tZprawTtcnyrv75ElD6VncVamUQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.4.tgz", - "integrity": "sha512-Alv8/XGSs/ytwQcbCHwze1HmiIkIVhDHYLjczSVrf0Wi2MvKn/blt7+S6FJitj3yTlMwMxII1gIJ9WepI4aZ/A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.4.tgz", - "integrity": "sha512-ze0ShQDBPCqxLImzw4sCdfnB3lRmN3qGMB2GWDRlq5Wqy4G36pxtNOo2usu/Nm9+V2Rh/QQnrRc2l94kYFXO6Q==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.4.tgz", - "integrity": "sha512-8dwC0UJoc6fC7PX70csdaznVMNr16hQrTDAMPvLPloazlcaWfdPogq+UpZX6Drqb1OBlwowz8iG7WR0Tzk/diQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.4.tgz", - "integrity": "sha512-jxyg67NbEWkDyvM+O8UDbPAyYRZqGLQDTPwvrBBeOSyVWW/jFQkQKQ70JDqDSYg1ZDdl+E3nkbFbq8xM8E9x8A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.4.tgz", - "integrity": "sha512-twrmN753hjXRdcrZmZttb/m5xaCBFa48Dt3FbeEItpJArxriYDunWxJn+QFXdJ3hPkm4u7CKxncVvnmgQMY1ag==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.4.tgz", - "integrity": "sha512-tkLrjBzqFTP8DVrAAQmZelEahfR9OxWpFR++vAI9FBhCiIxtwHwBHC23SBHCTURBtwB4kc/x44imVOnkKGNVGg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" - }, - "node_modules/@swc/helpers": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", - "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", - "dependencies": { - "@swc/counter": "^0.1.3", - "tslib": "^2.4.0" - } - }, - "node_modules/@types/node": { - "version": "20.14.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", - "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/prop-types": { - "version": "15.7.12", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", - "dev": true - }, - "node_modules/@types/react": { - "version": "18.3.3", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", - "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "dev": true, - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", - "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001599", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz", - "integrity": "sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/next": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.4.tgz", - "integrity": "sha512-R8/V7vugY+822rsQGQCjoLhMuC9oFj9SOi4Cl4b2wjDrseD0LRZ10W7R6Czo4w9ZznVSshKjuIomsRjvm9EKJQ==", - "dependencies": { - "@next/env": "14.2.4", - "@swc/helpers": "0.5.5", - "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001579", - "graceful-fs": "^4.2.11", - "postcss": "8.4.31", - "styled-jsx": "5.1.1" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": ">=18.17.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.4", - "@next/swc-darwin-x64": "14.2.4", - "@next/swc-linux-arm64-gnu": "14.2.4", - "@next/swc-linux-arm64-musl": "14.2.4", - "@next/swc-linux-x64-gnu": "14.2.4", - "@next/swc-linux-x64-musl": "14.2.4", - "@next/swc-win32-arm64-msvc": "14.2.4", - "@next/swc-win32-ia32-msvc": "14.2.4", - "@next/swc-win32-x64-msvc": "14.2.4" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@playwright/test": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "node_modules/next-ws": { - "resolved": "../../packages/core", - "link": true - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, - "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/styled-jsx": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", - "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", - "dependencies": { - "client-only": "0.0.1" - }, - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, - "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", - "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 - } - } - } - } -} diff --git a/examples/with-custom-server/package.json b/examples/with-custom-server/package.json deleted file mode 100644 index bf8c16e..0000000 --- a/examples/with-custom-server/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "with-custom-server", - "private": true, - "dependencies": { - "next": "^14.2.4", - "next-ws": "file:../../packages/core", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "ws": "^8.16.0" - }, - "devDependencies": { - "@types/node": "^20.14.9", - "@types/react": "18.3.3", - "@types/react-dom": "^18.3.0", - "@types/ws": "^8.5.10" - } -} diff --git a/examples/with-custom-server/src/app/api/stats/route.ts b/examples/with-custom-server/src/app/api/stats/route.ts deleted file mode 100644 index f8a45bf..0000000 --- a/examples/with-custom-server/src/app/api/stats/route.ts +++ /dev/null @@ -1,8 +0,0 @@ -// NOTE: Because we are using a custom server, this is possible - -import { getWebSocketServer } from 'next-ws/server'; - -export function GET() { - const wsServer = getWebSocketServer(); - return Response.json({ count: wsServer.clients.size }); -} diff --git a/examples/with-custom-server/src/app/api/ws/route.ts b/examples/with-custom-server/src/app/api/ws/route.ts deleted file mode 100644 index 36dc6e9..0000000 --- a/examples/with-custom-server/src/app/api/ws/route.ts +++ /dev/null @@ -1,40 +0,0 @@ -export function GET() { - const headers = new Headers(); - headers.set('Connection', 'Upgrade'); - headers.set('Upgrade', 'websocket'); - return new Response('Upgrade Required', { status: 426, headers }); -} - -export function SOCKET( - client: import('ws').WebSocket, - _request: import('node:http').IncomingMessage, - server: import('ws').WebSocketServer, -) { - const { send, broadcast } = createHelpers(client, server); - - // When a new client connects broadcast a connect message - broadcast({ author: 'Server', content: 'A new client has connected.' }); - send({ author: 'Server', content: 'Welcome!' }); - - // Relay any message back to other clients - client.on('message', broadcast); - - // When this client disconnects broadcast a disconnect message - client.on('close', () => { - broadcast({ author: 'Server', content: 'A client has disconnected.' }); - }); -} - -function createHelpers( - client: import('ws').WebSocket, - server: import('ws').WebSocketServer, -) { - const send = (payload: unknown) => client.send(JSON.stringify(payload)); - const broadcast = (payload: unknown) => { - if (payload instanceof Buffer) payload = payload.toString(); - if (typeof payload !== 'string') payload = JSON.stringify(payload); - for (const other of server.clients) - if (other !== client) other.send(String(payload)); - }; - return { send, broadcast }; -} diff --git a/examples/with-custom-server/src/app/page.tsx b/examples/with-custom-server/src/app/page.tsx deleted file mode 100644 index 6012216..0000000 --- a/examples/with-custom-server/src/app/page.tsx +++ /dev/null @@ -1,88 +0,0 @@ -'use client'; - -import { useWebSocket } from 'next-ws/client'; -import { useCallback, useEffect, useState } from 'react'; - -export default function Page() { - const ws = useWebSocket(); - - type Message = { author: string; content: string }; - const [messages, setMessages] = useState([]); - - type HandleSubmit = (ev: React.FormEvent) => void; - const handleSubmit = useCallback( - (ev) => { - ev.preventDefault(); - const form = new FormData(ev.currentTarget); - const author = form.get('author') as string; - const content = form.get('content') as string; - if (!author || !content) return; - - ws?.send(JSON.stringify({ author, content })); - setMessages((p) => [...p, { author: 'You', content }]); - // Reset the content input (only) - const contentInputElement = ev.currentTarget.querySelector( - 'input[name="content"]', - ) as HTMLInputElement; - contentInputElement.value = ''; - }, - [ws], - ); - - useEffect(() => { - async function onMessage(event: MessageEvent) { - const payload = - typeof event.data === 'string' ? event.data : await event.data.text(); - const message = JSON.parse(payload) as Message; - setMessages((p) => [...p, message]); - } - - ws?.addEventListener('message', onMessage); - return () => ws?.removeEventListener('message', onMessage); - }, [ws]); - - return ( -
-
- {messages.map((message, i) => ( -
- {message.author}: {message.content} -
- ))} - - {messages.length === 0 && ( -
-

Waiting for messages...

-
- )} -
- -
- - - -
-
- ); -} diff --git a/examples/with-custom-server/tsconfig.json b/examples/with-custom-server/tsconfig.json deleted file mode 100644 index 0fd2353..0000000 --- a/examples/with-custom-server/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "include": ["src/**/*", ".next/types/**/*.ts"], - "exclude": ["node_modules"], - "compilerOptions": { - "baseUrl": ".", - "outDir": "dist", - "paths": { "~/*": ["src/*"] }, - "jsx": "preserve", - "allowJs": true, - "skipLibCheck": true, - "noEmit": true, - "incremental": true, - "plugins": [{ "name": "next" }], - "lib": ["dom", "es2023"], - "strictNullChecks": true - } -} diff --git a/package.json b/package.json index b225a14..4b4e5d4 100644 --- a/package.json +++ b/package.json @@ -1,27 +1,69 @@ { - "name": "root", - "private": true, - "packageManager": "pnpm@9.12.1", - "workspaces": ["packages/*"], + "name": "next-ws", + "version": "1.2.0", + "packageManager": "pnpm@9.15.4", + "type": "module", + "description": "Add support for WebSockets in Next.js 13 app directory", + "license": "MIT", + "keywords": ["next", "websocket", "ws", "server", "client"], + "homepage": "https://github.com/apteryxxyz/next-ws#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/apteryxxyz/next-ws.git" + }, + "bugs": { + "url": "https://github.com/apteryxxyz/next-ws/issues" + }, + "bin": { + "next-ws": "./dist/cli.cjs" + }, + "files": ["dist"], + "exports": { + "./client": { + "require": "./dist/client/index.cjs", + "import": "./dist/client/index.js" + }, + "./server": { + "require": "./dist/server/index.cjs", + "import": "./dist/server/index.js" + }, + "./package.json": "./package.json" + }, "scripts": { - "lint": "biome lint . --write", - "format": "biome format . --write", - "pretty": "biome check . --write", - "check": "turbo check", - "build": "turbo build", - "dev": "turbo dev", - "change": "changeset add", - "publish": "changeset version && pnpm biome format . --write && turbo build && changeset publish", - "publish:snapshot": "changeset version --snapshot beta && pnpm turbo build && changeset publish --tag beta --no-git-tag", - "graph": "npx nx@18 graph" + "lint": "biome ci .", + "format": "biome check . --write", + "check": "tsc --noEmit", + "build": "tsup", + "dev": "tsup --watch", + "test": "playwright test", + "prepare": "husky", + "postinstall": "pnpm build", + "prepack": "pinst --disable && pnpm biome format package.json --write", + "postpack": "pinst --enable && pnpm biome format package.json --write", + "change": "changeset", + "release": "changeset version && pnpm format && pnpm build && changeset publish", + "release:snapshot": "changeset version --snapshot beta && pnpm format && pnpm build && changeset publish --tag beta --no-git-tag" + }, + "peerDependencies": { + "next": ">=13.1.1", + "react": "*", + "ws": "*" }, "devDependencies": { - "@biomejs/biome": "1.8.3", - "@changesets/changelog-git": "^0.2.0", - "@changesets/cli": "^2.27.9", - "tsup": "^8.1.0", - "tsx": "^4.16.0", - "turbo": "^2.0.6", - "typescript": "^5.5.2" + "@biomejs/biome": "^1.9.4", + "@changesets/changelog-github": "^0.5.0", + "@changesets/cli": "^2.27.12", + "@playwright/test": "^1.50.1", + "@types/node": "^22.13.1", + "@types/react": "^19.0.8", + "@types/semver": "^7.5.8", + "@types/ws": "^8.5.14", + "chalk": "^5.4.1", + "commander": "^13.1.0", + "husky": "^9.1.7", + "pinst": "^3.0.0", + "semver": "^7.7.1", + "tsup": "^8.3.6", + "typescript": "^5.7.3" } } diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md deleted file mode 100644 index 59478de..0000000 --- a/packages/cli/CHANGELOG.md +++ /dev/null @@ -1,31 +0,0 @@ -# next-ws-cli - -## 1.1.8 - -### Patch Changes - -- 9aae27f: Bump `patch-3` support range to `15.1.6` - -## 1.1.7 - -### Patch Changes - -- 5b40f30: Bump `patch-3` support range to `15.1.5` - -## 1.1.6 - -### Patch Changes - -- ba662cc: Bump `patch-3` supported range - -## 1.1.5 - -### Patch Changes - -- 0f05c2d: Fix cli package bin file path - -## 1.1.4 - -### Patch Changes - -- 46db458: Add support for Next.js version 15 diff --git a/packages/cli/package.json b/packages/cli/package.json deleted file mode 100644 index 7d183ec..0000000 --- a/packages/cli/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "next-ws-cli", - "version": "1.1.8", - "type": "commonjs", - "description": "CLI tool for Next WS, a library for adding support for WebSockets to Next.js 13", - "license": "MIT", - "keywords": ["next", "websocket", "ws", "server", "client", "cli", "patch"], - "homepage": "https://github.com/apteryxxyz/next-ws#readme", - "repository": { - "type": "git", - "url": "git+https://github.com/apteryxxyz/next-ws.git", - "directory": "packages/cli" - }, - "bugs": { - "url": "https://github.com/apteryxxyz/next-ws/issues" - }, - "files": ["dist"], - "bin": "./dist/program.js", - "scripts": { - "check": "tsc --noEmit", - "build": "cp ../../readme.md . && tsup", - "dev": "pnpm build --watch" - }, - "dependencies": { - "@babel/generator": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7", - "@inquirer/confirm": "^3.1.12", - "chalk": "^4", - "commander": "^12.1.0", - "semver": "^7.6.2" - }, - "devDependencies": { - "@types/babel__generator": "^7.6.8", - "@types/babel__template": "^7.4.4", - "@types/inquirer": "^9.0.7", - "@types/node": "^20.14.9", - "@types/semver": "^7.5.8" - } -} diff --git a/packages/cli/readme.md b/packages/cli/readme.md deleted file mode 100644 index bb82c61..0000000 --- a/packages/cli/readme.md +++ /dev/null @@ -1,246 +0,0 @@ -
-

Next WS

- Add support for WebSockets in Next.js app directory
- npm install next-ws ws -
- -
- package version - total downloads -
- next-ws repo stars - apteryxxyz followers - discord shield -
- -## 🤔 About - -Next WS (`next-ws`) is an advanced Next.js plugin that seamlessly integrates WebSocket server capabilities directly into routes located in the **app directory**. With Next WS, you no longer require a separate server for WebSocket functionality. - -> [!IMPORTANT] -> Next WS is designed for use in server-based environments. It is not suitable for serverless platforms like Vercel, where WebSocket servers are not supported. Furthermore, this plugin is built for the app directory and does not support the older pages directory. - -This module is inspired by the now outdated `next-plugin-websocket`, if you are using an older version of Next.js, that module may work for you. - -## 🏓 Table of Contents - -- [📦 Installation](#-installation) -- [🚀 Usage](#-usage) -- [🌀 Examples](#-examples) - - [Creating a Socket](#creating-a-socket) - - [Using a Custom Server](#using-a-custom-server) - - [Accessing the WebSocket Server](#accessing-the-websocket-server) - - [Client-Side Utilities](#client-side-utilities) - -## 📦 Installation - -Setting up a WebSocket server with Next WS involves patching your local Next.js installation. Next WS simplifies this process with a CLI command that automatically detects and patches your Next.js version, ensuring compatibility. Note that Next.js version 13.1.1 or higher is required. - -```sh -npx next-ws-cli@latest patch -``` - -> [!NOTE] -> If at any point your local Next.js installation is changed or updated you will need to re-run the patch command. - -After successfully patching Next.js, install the Next WS package along with its peer dependency, ws, into your project: - -```sh -npm install next-ws ws -``` - -## 🚀 Usage - -Using WebSocket functionality in your Next.js application with Next WS is simple and requires no additional configuration. Simply export a `SOCKET` function from any route file. This function will be invoked whenever a client establishes a WebSocket connection to that specific route. - -The `SOCKET` function receives three arguments: the WebSocket client instance, the incoming HTTP request - which you can use to get the URL path, query parameters, and headers - and the WebSocket server instance. - -```ts -export function SOCKET( - client: import('ws').WebSocket, - request: import('http').IncomingMessage, - server: import('ws').WebSocketServer -) { - // ... -} -``` - -### With a Custom Server - -In production, Next.js uses a worker process for routes, which can make it difficult to access the WebSocket server from outside a `SOCKET` handler, especially when the WebSocket server exists on the main process. For those needing to overcome this challenge or preferring a custom server setup, Next WS provides a solution. - -The `next-ws/server` module offers functions for setting the HTTP and WebSocket servers. You use these functions to tell Next WS to use your server instances instead of creating its own. This allows you to then access the instances you created yourself from anywhere in your app. Refer to the [example below](#using-a-custom-server). - -## 🌀 Examples - -For more detailed examples, refer the [`examples` directory](https://github.com/apteryxxyz/next-ws/tree/main/examples). - -### Creating a Socket - -Creating an API route anywhere within the app directory and exporting a `SOCKET` function from it is all that is required. Below is an example of a simple echo server, which sends back any message it receives. - -```ts -// app/api/ws/route.ts (can be any route file in the app directory) - -export function SOCKET( - client: import('ws').WebSocket, - request: import('http').IncomingMessage, - server: import('ws').WebSocketServer -) { - console.log('A client connected'); - - client.on('message', (message) => { - console.log('Received message:', message); - client.send(message); - }); - - client.on('close', () => { - console.log('A client disconnected'); - }); -} -``` - -### Using a Custom Server - -> [!IMPORTANT] -> Next WS was made to avoid the need for a custom server, if you are using one, you don't need this package and can just use a websocket server directly. - -To use a custom server, all you need to do is tell Next WS to use your server instead of creating its own. This can be done by calling the `setHttpServer` and `setWebSocketServer` functions from `next-ws/server` and passing your server instances. - -```ts -// server.js - -const { setHttpServer, setWebSocketServer } = require('next-ws/server'); -const { Server } = require('node:http'); -const { parse } = require('node:url'); -const next = require('next'); -const { WebSocketServer } = require('ws'); - -const dev = process.env.NODE_ENV !== 'production'; -const hostname = 'localhost'; -const port = 3000; - -const httpServer = new Server(); -const webSocketServer = new WebSocketServer({ noServer: true }); -// Tell Next WS about the HTTP and WebSocket servers before starting the custom server -setHttpServer(httpServer); -setWebSocketServer(webSocketServer); - -const app = next({ dev, hostname, port, customServer: true }); -const handle = app.getRequestHandler(); - -app.prepare().then(() => { - httpServer - .on('request', async (req, res) => { - const parsedUrl = parse(req.url, true); - await handle(req, res, parsedUrl); - }) - .listen(port, () => { - console.log(` ▲ Ready on http://${hostname}:${port}`); - }); -}); -``` - -### Accessing the WebSocket Server - -Along with setters, Next WS also provides getters for the HTTP and WebSocket servers. These functions can be used to access the servers from anywhere in your app. - -> [!IMPORTANT] -> In order to use the `getWebSocketServer` and `getHttpServer` functions, you must be using a [custom server](https://nextjs.org/docs/advanced-features/custom-server), this is due to a limitation in Next.js. Refer to the [With a Custom Server](#with-a-custom-server). - -```ts -// app/api/stats/route.ts - -import { getWebSocketServer } from 'next-ws/server'; -// There is also a `getHttpServer` function available - -export function GET() { - const wsServer = getWebSocketServer(); - // Response with the number of connected clients - return Response.json({ count: wsServer.clients.size }); -} -``` - -### Client-Side Utilities - -To make it easier to connect to your new WebSocket server, Next WS also provides some client-side utilities. These are completely optional, you can use your own implementation if you wish. - -```tsx -// layout.tsx -'use client'; - -import { WebSocketProvider } from 'next-ws/client'; - -export default function Layout({ children }: React.PropsWithChildren) { - return ( - - - - {children} - - - - ); -} -``` - -The following is the props interface for the `WebSocketProvider` component, containing all the available options. - -```ts -interface WebSocketProviderProps { - children: React.ReactNode; - - /** The URL for the WebSocket to connect to. */ - url: string; - /** The subprotocols to use. */ - protocols?: string[] | string; - /** The binary type to use. */ - binaryType?: BinaryType; -} -``` - -Now you can use the `useWebSocket` hook to get the WebSocket instance, and send and receive messages. - -```tsx -// page.tsx -'use client'; - -import { useCallback, useEffect, useRef, useState } from 'react'; -import { useWebSocket } from 'next-ws/client'; - -export default function Page() { - const ws = useWebSocket(); - // ^? WebSocket on the client, null on the server - - const inputRef = useRef(null); - const [message, setMessage] = useState(null); - - useEffect(() => { - async function onMessage(event: MessageEvent) { - const payload = - typeof event.data === 'string' ? event.data : await event.data.text(); - const message = JSON.parse(payload) as Message; - setMessages((p) => [...p, message]); - } - - ws?.addEventListener('message', onMessage); - return () => ws?.removeEventListener('message', onMessage); - }, [ws]); - - return ( - <> - - - - -

- {message === null - ? 'Waiting to receive message...' - : `Got message: ${message}`} -

- - ); -} -``` diff --git a/packages/cli/src/commands/patch.ts b/packages/cli/src/commands/patch.ts deleted file mode 100644 index 512ec41..0000000 --- a/packages/cli/src/commands/patch.ts +++ /dev/null @@ -1,68 +0,0 @@ -import confirm from '@inquirer/confirm'; -import { Command } from 'commander'; -import logger from '~/helpers/logger'; -import { getNextVersion } from '~/helpers/next'; -import { setTrace } from '~/helpers/trace'; -import patches from '~/patches'; -import * as semver from '../helpers/semver'; - -export default new Command('patch') - .description('Patch the local Next.js installation to support WebSockets') - .option('-y, --yes', 'Skip confirmation prompt for unsupported versions') - .action(async (options: { yes: boolean }) => { - const supported = patches.map((p) => p.supported).join(' || '); - const minimum = semver.minVersion(supported)?.version ?? supported; - const maximum = semver.maxVersion(supported)?.version ?? supported; - const current = getNextVersion(); - - if (semver.ltr(current, minimum)) { - logger.error(`Next.js v${current} is not supported, - a minimum of v${minimum} is required`); - process.exit(1); - } - - let patch = patches.find((p) => semver.satisfies(current, p.supported)); - if (semver.gtr(current, maximum)) { - logger.warn(`Next WS has not yet been tested with Next.js v${current}, - it may or may not work, are you sure you want to continue?`); - - const continueY = - Boolean(options.yes) || - (await confirm({ - message: 'Continue?', - default: false, - })); - - if (continueY) { - patch = patches[patches.length - 1]; - logger.info('Continuing with the latest patch'); - logger.info(`If you encounter any issues please report them at - https://github.com/apteryxxyz/next-ws/issues`); - } else { - logger.info('Aborting'); - process.exit(1); - } - } - - if (!patch) { - logger.error(`Next WS does not have a patch for Next.js v${current}, - please install a supported version of Next.js`); - logger.info(`Supported ranges: ${supported}`); - process.exit(1); - } - - logger.info( - `Patching Next.js v${current} with patch "${patch.supported}"...`, - ); - await patch().catch((e) => { - logger.error(e); - process.exit(1); - }); - - logger.info('Saving patch information file...'); - setTrace({ patch: patch.supported, version: current }); - - logger.info( - "All done! You can now install the core Next WS package if you haven't already", - ); - }); diff --git a/packages/cli/src/commands/verify.ts b/packages/cli/src/commands/verify.ts deleted file mode 100644 index dac1e56..0000000 --- a/packages/cli/src/commands/verify.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Command } from 'commander'; -import logger from '~/helpers/logger'; -import { getNextVersion } from '~/helpers/next'; -import { getTrace } from '~/helpers/trace'; -import patchCommand from './patch'; - -export default new Command('verify') - .description('Verify that the local Next.js installation has been patched') - .option('-e, --ensure', 'If not patched, then run the patch command') - .action((options: { ensure: boolean }) => { - const trace = getTrace(); - if (!trace) { - if (options.ensure) { - logger.warn( - 'Next.js has not been patched, running `npx next-ws-cli@latest patch`...', - ); - - const action = Reflect.get(patchCommand, '_actionHandler'); - return action(['-y']); - } else { - logger.error( - 'Next.js has not been patched, try running `npx next-ws-cli@latest patch`', - ); - process.exit(1); - } - } - - const current = getNextVersion(); - if (trace.version !== current) { - logger.error( - `Next.js version mismatch, expected v${trace.version} but found v${current}, try running \`npx next-ws-cli@latest patch\``, - ); - process.exit(1); - } - - logger.info(`Next.js has been patched with "${trace.patch}"`); - }); diff --git a/packages/cli/src/helpers/logger.ts b/packages/cli/src/helpers/logger.ts deleted file mode 100644 index 12fa646..0000000 --- a/packages/cli/src/helpers/logger.ts +++ /dev/null @@ -1,19 +0,0 @@ -import chalk from 'chalk'; - -function line(content: string) { - return content.replaceAll(/(?:\n\s*)+/g, ' '); -} - -function error(message: string) { - console.error(chalk.red(`[next-ws] ${line(message)}`)); -} - -function warn(message: string) { - console.warn(chalk.yellow(`[next-ws] ${line(message)}`)); -} - -function info(message: string) { - console.info(chalk.cyan(`[next-ws] ${line(message)}`)); -} - -export default { error, warn, info }; diff --git a/packages/cli/src/helpers/next.ts b/packages/cli/src/helpers/next.ts deleted file mode 100644 index a766dd1..0000000 --- a/packages/cli/src/helpers/next.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { readFileSync } from 'node:fs'; -import path from 'node:path'; - -export function findNextDirectory() { - return path.dirname( - require.resolve('next/package.json', { paths: [process.cwd()] }), - ); -} - -export function getNextVersion() { - const packagePath = path.join(findNextDirectory(), 'package.json'); - const packageContent = readFileSync(packagePath, 'utf8'); - const packageJson = JSON.parse(packageContent) as { version: string }; - return packageJson.version.split('-')[0]!; -} diff --git a/packages/cli/src/helpers/semver.ts b/packages/cli/src/helpers/semver.ts deleted file mode 100644 index cde97a6..0000000 --- a/packages/cli/src/helpers/semver.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { type Options, Range, SemVer, gt } from 'semver'; - -function maxVersion(range_: Range | string, loose?: Options | boolean) { - const range = new Range(range_, loose); - let maximumVersion: SemVer | null = null; - - for (const comparators of range.set) { - for (const { - operator, - semver: { version: version_ }, - } of comparators) { - if (operator === '>' || operator === '>=') continue; - - const version = new SemVer(version_); - - if (operator === '<') { - version.patch--; - version.raw = version.format(); - } - - if (!maximumVersion || gt(version, maximumVersion)) - maximumVersion = version; - } - } - - return maximumVersion; -} - -export * from 'semver'; -export { maxVersion }; diff --git a/packages/cli/src/helpers/trace.ts b/packages/cli/src/helpers/trace.ts deleted file mode 100644 index 9c7c116..0000000 --- a/packages/cli/src/helpers/trace.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { readFileSync, writeFileSync } from 'node:fs'; -import path from 'node:path'; -import { findNextDirectory } from './next'; - -const TracePath = path.join(findNextDirectory(), '.next-ws-trace.json'); - -interface Trace { - patch: string; - version: string; -} - -export function getTrace() { - try { - return JSON.parse(readFileSync(TracePath, 'utf8')) as Trace; - } catch { - return null; - } -} - -export function setTrace(trace: Trace) { - try { - writeFileSync(TracePath, JSON.stringify(trace, null, 2)); - return true; - } catch { - return false; - } -} diff --git a/packages/cli/src/patches/index.ts b/packages/cli/src/patches/index.ts deleted file mode 100644 index 4f022c8..0000000 --- a/packages/cli/src/patches/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import patch1 from './patch-1'; -import patch2 from './patch-2'; -import patch3 from './patch-3'; - -export default [patch1, patch2, patch3] as const; diff --git a/packages/cli/src/patches/patch-1.ts b/packages/cli/src/patches/patch-1.ts deleted file mode 100644 index 9f824d0..0000000 --- a/packages/cli/src/patches/patch-1.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { readFile, writeFile } from 'node:fs/promises'; -import { join } from 'node:path'; -import generate from '@babel/generator'; -import * as parser from '@babel/parser'; -import template from '@babel/template'; -import type { ClassDeclaration, ClassMethod } from '@babel/types'; -import logger from '~/helpers/logger'; -import { findNextDirectory } from '~/helpers/next'; - -// Add `require('next-ws/server').setupWebSocketServer(this)` to the constructor of -// NextNodeServer in next/dist/server/next-server.js -// REMARK: Starting the server and handling connections is part of the core package - -const NextServerFilePath = join( - findNextDirectory(), - 'dist/server/next-server.js', -); - -export async function patchNextNodeServer() { - logger.info( - 'Adding WebSocket server setup script to NextNodeServer constructor...', - ); - - const source = await readFile(NextServerFilePath, 'utf8'); - if (source.includes('require("next-ws/server")')) - return logger.warn( - 'WebSocket server setup script already exists, skipping.', - ); - - const tree = parser.parse(source); - - const classDeclaration = tree.program.body.find( - (n): n is ClassDeclaration => - n.type === 'ClassDeclaration' && n.id?.name === 'NextNodeServer', - ); - if (!classDeclaration) throw 'NextNodeServer class declaration not found.'; - - const constructorMethod = classDeclaration.body.body.find( - (n): n is ClassMethod => - n.type === 'ClassMethod' && n.kind === 'constructor', - ); - if (!constructorMethod) throw 'NextNodeServer constructor method not found.'; - - const statement = template.statement - .ast`require("next-ws/server").setupWebSocketServer(this)`; - constructorMethod.body.body.push(statement); - - const trueGenerate = Reflect.get(generate, 'default') ?? generate; - const newSource = trueGenerate(tree).code; - - await writeFile(NextServerFilePath, newSource); - logger.info('WebSocket server setup script added.'); -} - -// Add `SOCKET?: Function` to the page module interface check field thing in -// next/dist/build/webpack/plugins/next-types-plugin.js - -const NextTypesFilePath = join( - findNextDirectory(), - 'dist/build/webpack/plugins/next-types-plugin.js', -); - -export async function patchNextTypesPlugin() { - logger.info("Adding 'SOCKET' to the page module interface type..."); - - const source = await readFile(NextTypesFilePath, 'utf8'); - if (source.includes('SOCKET?: Function')) - return logger.warn( - "'SOCKET' already exists in page module interface, skipping.", - ); - - const toFind = '.map((method)=>`${method}?: Function`).join("\\n ")'; - const replaceWith = `${toFind} + "; SOCKET?: Function"`; - const newSource = source.replace(toFind, replaceWith); - await writeFile(NextTypesFilePath, newSource); - logger.info("'SOCKET' added to page module interface type."); -} - -// - -export default Object.assign( - async () => { - await patchNextNodeServer(); - await patchNextTypesPlugin(); - }, - { - date: '2023-06-16' as const, - supported: '>=13.1.1 <=13.4.8' as const, - }, -); diff --git a/packages/cli/src/patches/patch-2.ts b/packages/cli/src/patches/patch-2.ts deleted file mode 100644 index 94a95e2..0000000 --- a/packages/cli/src/patches/patch-2.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { readFile, writeFile } from 'node:fs/promises'; -import { join } from 'node:path'; -import logger from '~/helpers/logger'; -import { findNextDirectory } from '~/helpers/next'; -import { patchNextNodeServer } from './patch-1'; - -// Add `SOCKET?: Function` to the page module interface check field thing in -// next/dist/build/webpack/plugins/next-types-plugin.js -// REMARK: The file for 'next-types-plugin' was moved in 13.4.9 - -const NextTypesFilePath = join( - findNextDirectory(), - 'dist/build/webpack/plugins/next-types-plugin/index.js', -); - -export async function patchNextTypesPlugin() { - logger.info("Adding 'SOCKET' to the page module interface type..."); - - const source = await readFile(NextTypesFilePath, 'utf8'); - if (source.includes('SOCKET?: Function')) - return logger.warn( - "'SOCKET' already exists in page module interface, skipping.", - ); - - const toFind = - /\.map\(\(method\)=>`\${method}\?: Function`\).join\(['"]\\n +['"]\)/; - const replaceWith = `.map((method)=>\`\${method}?: Function\`).join('\\n ') + "; SOCKET?: Function"`; - const newSource = source.replace(toFind, replaceWith); - if (!newSource.includes('SOCKET?: Function')) - throw 'Failed to add SOCKET to page module interface type.'; - - await writeFile(NextTypesFilePath, newSource); - logger.info("'SOCKET' added to page module interface type."); -} - -// - -export default Object.assign( - async () => { - await patchNextNodeServer(); - await patchNextTypesPlugin(); - }, - { - date: '2023-06-16' as const, - supported: '>=13.1.1 <=13.4.8' as const, - }, -); diff --git a/packages/cli/src/patches/patch-3.ts b/packages/cli/src/patches/patch-3.ts deleted file mode 100644 index 3d5f498..0000000 --- a/packages/cli/src/patches/patch-3.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { readFile, writeFile } from 'node:fs/promises'; -import { join } from 'node:path'; -import logger from '~/helpers/logger'; -import { findNextDirectory } from '~/helpers/next'; -import { patchNextNodeServer } from './patch-1'; -import { patchNextTypesPlugin } from './patch-2'; - -// If Next.js receives a WebSocket connection on a matched route, it will -// close it immediately. This patch prevents that from happening. -// REMARK: This patch is only necessary for Next.js versions greater than 13.5.1 - -const RouterServerFilePath = join( - findNextDirectory(), - 'dist/server/lib/router-server.js', -); - -export async function patchRouterServer() { - logger.info( - 'Preventing Next.js from immediately closing WebSocket connections...', - ); - - const source = await readFile(RouterServerFilePath, 'utf8'); - const newSource = source - .replace('return socket.end();', '') - .replace(/(\/\/ [a-zA-Z .]+\s+)socket\.end\(\);/, ''); - - await writeFile(RouterServerFilePath, newSource); - logger.info('WebSocket connection closing prevention patch applied.'); -} - -export default Object.assign( - async () => { - await patchNextNodeServer(); - await patchRouterServer(); - await patchNextTypesPlugin(); - }, - { - date: '2023-11-01' as const, - supported: '>=13.5.1 <=15.1.6' as const, - }, -); diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json deleted file mode 100644 index f169477..0000000 --- a/packages/cli/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { "paths": { "~/*": ["./src/*"] } } -} diff --git a/packages/cli/tsup.config.ts b/packages/cli/tsup.config.ts deleted file mode 100644 index 5d1a383..0000000 --- a/packages/cli/tsup.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineConfig } from 'tsup'; - -export default defineConfig({ - entry: ['src/program.ts'], - format: ['cjs'], - target: 'es2022', - clean: true, - splitting: true, - bundle: true, - treeshake: true, - keepNames: true, - minifySyntax: true, -}); diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md deleted file mode 100644 index 5f5fe82..0000000 --- a/packages/core/CHANGELOG.md +++ /dev/null @@ -1,7 +0,0 @@ -# next-ws - -## 1.2.0 - -### Minor Changes - -- e72b519: Pass request params as fourth parameter to socket handler diff --git a/packages/core/package.json b/packages/core/package.json deleted file mode 100644 index 6aab469..0000000 --- a/packages/core/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "next-ws", - "version": "1.2.0", - "type": "module", - "description": "Add support for WebSockets in Next.js 13 app directory", - "license": "MIT", - "keywords": ["next", "websocket", "ws", "server", "client"], - "homepage": "https://github.com/apteryxxyz/next-ws#readme", - "repository": { - "type": "git", - "url": "git+https://github.com/apteryxxyz/next-ws.git", - "directory": "packages/core" - }, - "bugs": { - "url": "https://github.com/apteryxxyz/next-ws/issues" - }, - "files": ["dist"], - "exports": { - "./client": { - "import": "./dist/client/index.js", - "require": "./dist/client/index.cjs", - "types": "./dist/client/index.d.ts" - }, - "./server": { - "import": "./dist/server/index.js", - "require": "./dist/server/index.cjs", - "types": "./dist/server/index.d.ts" - } - }, - "scripts": { - "check": "tsc --noEmit", - "build": "cp ../../readme.md . && tsup", - "dev": "pnpm build --watch" - }, - "peerDependencies": { - "next": ">=13.1.1", - "react": "*", - "ws": "*" - }, - "devDependencies": { - "@types/react": "^18.3.3", - "@types/ws": "^8.5.10", - "next": "^14.2.4", - "react": "^18.3.1", - "ws": "^8.17.1" - } -} diff --git a/packages/core/readme.md b/packages/core/readme.md deleted file mode 100644 index bb82c61..0000000 --- a/packages/core/readme.md +++ /dev/null @@ -1,246 +0,0 @@ -
-

Next WS

- Add support for WebSockets in Next.js app directory
- npm install next-ws ws -
- -
- package version - total downloads -
- next-ws repo stars - apteryxxyz followers - discord shield -
- -## 🤔 About - -Next WS (`next-ws`) is an advanced Next.js plugin that seamlessly integrates WebSocket server capabilities directly into routes located in the **app directory**. With Next WS, you no longer require a separate server for WebSocket functionality. - -> [!IMPORTANT] -> Next WS is designed for use in server-based environments. It is not suitable for serverless platforms like Vercel, where WebSocket servers are not supported. Furthermore, this plugin is built for the app directory and does not support the older pages directory. - -This module is inspired by the now outdated `next-plugin-websocket`, if you are using an older version of Next.js, that module may work for you. - -## 🏓 Table of Contents - -- [📦 Installation](#-installation) -- [🚀 Usage](#-usage) -- [🌀 Examples](#-examples) - - [Creating a Socket](#creating-a-socket) - - [Using a Custom Server](#using-a-custom-server) - - [Accessing the WebSocket Server](#accessing-the-websocket-server) - - [Client-Side Utilities](#client-side-utilities) - -## 📦 Installation - -Setting up a WebSocket server with Next WS involves patching your local Next.js installation. Next WS simplifies this process with a CLI command that automatically detects and patches your Next.js version, ensuring compatibility. Note that Next.js version 13.1.1 or higher is required. - -```sh -npx next-ws-cli@latest patch -``` - -> [!NOTE] -> If at any point your local Next.js installation is changed or updated you will need to re-run the patch command. - -After successfully patching Next.js, install the Next WS package along with its peer dependency, ws, into your project: - -```sh -npm install next-ws ws -``` - -## 🚀 Usage - -Using WebSocket functionality in your Next.js application with Next WS is simple and requires no additional configuration. Simply export a `SOCKET` function from any route file. This function will be invoked whenever a client establishes a WebSocket connection to that specific route. - -The `SOCKET` function receives three arguments: the WebSocket client instance, the incoming HTTP request - which you can use to get the URL path, query parameters, and headers - and the WebSocket server instance. - -```ts -export function SOCKET( - client: import('ws').WebSocket, - request: import('http').IncomingMessage, - server: import('ws').WebSocketServer -) { - // ... -} -``` - -### With a Custom Server - -In production, Next.js uses a worker process for routes, which can make it difficult to access the WebSocket server from outside a `SOCKET` handler, especially when the WebSocket server exists on the main process. For those needing to overcome this challenge or preferring a custom server setup, Next WS provides a solution. - -The `next-ws/server` module offers functions for setting the HTTP and WebSocket servers. You use these functions to tell Next WS to use your server instances instead of creating its own. This allows you to then access the instances you created yourself from anywhere in your app. Refer to the [example below](#using-a-custom-server). - -## 🌀 Examples - -For more detailed examples, refer the [`examples` directory](https://github.com/apteryxxyz/next-ws/tree/main/examples). - -### Creating a Socket - -Creating an API route anywhere within the app directory and exporting a `SOCKET` function from it is all that is required. Below is an example of a simple echo server, which sends back any message it receives. - -```ts -// app/api/ws/route.ts (can be any route file in the app directory) - -export function SOCKET( - client: import('ws').WebSocket, - request: import('http').IncomingMessage, - server: import('ws').WebSocketServer -) { - console.log('A client connected'); - - client.on('message', (message) => { - console.log('Received message:', message); - client.send(message); - }); - - client.on('close', () => { - console.log('A client disconnected'); - }); -} -``` - -### Using a Custom Server - -> [!IMPORTANT] -> Next WS was made to avoid the need for a custom server, if you are using one, you don't need this package and can just use a websocket server directly. - -To use a custom server, all you need to do is tell Next WS to use your server instead of creating its own. This can be done by calling the `setHttpServer` and `setWebSocketServer` functions from `next-ws/server` and passing your server instances. - -```ts -// server.js - -const { setHttpServer, setWebSocketServer } = require('next-ws/server'); -const { Server } = require('node:http'); -const { parse } = require('node:url'); -const next = require('next'); -const { WebSocketServer } = require('ws'); - -const dev = process.env.NODE_ENV !== 'production'; -const hostname = 'localhost'; -const port = 3000; - -const httpServer = new Server(); -const webSocketServer = new WebSocketServer({ noServer: true }); -// Tell Next WS about the HTTP and WebSocket servers before starting the custom server -setHttpServer(httpServer); -setWebSocketServer(webSocketServer); - -const app = next({ dev, hostname, port, customServer: true }); -const handle = app.getRequestHandler(); - -app.prepare().then(() => { - httpServer - .on('request', async (req, res) => { - const parsedUrl = parse(req.url, true); - await handle(req, res, parsedUrl); - }) - .listen(port, () => { - console.log(` ▲ Ready on http://${hostname}:${port}`); - }); -}); -``` - -### Accessing the WebSocket Server - -Along with setters, Next WS also provides getters for the HTTP and WebSocket servers. These functions can be used to access the servers from anywhere in your app. - -> [!IMPORTANT] -> In order to use the `getWebSocketServer` and `getHttpServer` functions, you must be using a [custom server](https://nextjs.org/docs/advanced-features/custom-server), this is due to a limitation in Next.js. Refer to the [With a Custom Server](#with-a-custom-server). - -```ts -// app/api/stats/route.ts - -import { getWebSocketServer } from 'next-ws/server'; -// There is also a `getHttpServer` function available - -export function GET() { - const wsServer = getWebSocketServer(); - // Response with the number of connected clients - return Response.json({ count: wsServer.clients.size }); -} -``` - -### Client-Side Utilities - -To make it easier to connect to your new WebSocket server, Next WS also provides some client-side utilities. These are completely optional, you can use your own implementation if you wish. - -```tsx -// layout.tsx -'use client'; - -import { WebSocketProvider } from 'next-ws/client'; - -export default function Layout({ children }: React.PropsWithChildren) { - return ( - - - - {children} - - - - ); -} -``` - -The following is the props interface for the `WebSocketProvider` component, containing all the available options. - -```ts -interface WebSocketProviderProps { - children: React.ReactNode; - - /** The URL for the WebSocket to connect to. */ - url: string; - /** The subprotocols to use. */ - protocols?: string[] | string; - /** The binary type to use. */ - binaryType?: BinaryType; -} -``` - -Now you can use the `useWebSocket` hook to get the WebSocket instance, and send and receive messages. - -```tsx -// page.tsx -'use client'; - -import { useCallback, useEffect, useRef, useState } from 'react'; -import { useWebSocket } from 'next-ws/client'; - -export default function Page() { - const ws = useWebSocket(); - // ^? WebSocket on the client, null on the server - - const inputRef = useRef(null); - const [message, setMessage] = useState(null); - - useEffect(() => { - async function onMessage(event: MessageEvent) { - const payload = - typeof event.data === 'string' ? event.data : await event.data.text(); - const message = JSON.parse(payload) as Message; - setMessages((p) => [...p, message]); - } - - ws?.addEventListener('message', onMessage); - return () => ws?.removeEventListener('message', onMessage); - }, [ws]); - - return ( - <> - - - - -

- {message === null - ? 'Waiting to receive message...' - : `Got message: ${message}`} -

- - ); -} -``` diff --git a/packages/core/src/server/index.ts b/packages/core/src/server/index.ts deleted file mode 100644 index dede1d2..0000000 --- a/packages/core/src/server/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -export * from './setup'; -export { - setHttpServer, - getHttpServer, - setWebSocketServer, - getWebSocketServer, -} from './helpers/persistent'; - -/** - * @deprecated - */ -export function verifyPatch() { - throw new Error( - "The 'verifyPatch' function has been deprecated in favour of the `npx next-ws-cli@latest verify` command.", - ); -} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json deleted file mode 100644 index 059de56..0000000 --- a/packages/core/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "jsx": "preserve", - "paths": { "~/*": ["./src/*"] } - } -} diff --git a/packages/core/tsup.config.ts b/packages/core/tsup.config.ts deleted file mode 100644 index aaee757..0000000 --- a/packages/core/tsup.config.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { defineConfig } from 'tsup'; - -export default defineConfig({ - entry: ['src/*/index.ts'], - format: ['esm', 'cjs'], - target: 'es2022', - clean: true, - splitting: true, - bundle: true, - treeshake: true, - keepNames: true, - minifySyntax: true, - dts: true, - sourcemap: true, -}); diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..88aafa7 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from '@playwright/test'; + +export default defineConfig({ + testDir: 'tests', + reporter: [['html', { outputFolder: 'tests/.reports' }]], + retries: 1, + use: { trace: 'on-first-retry' }, + outputDir: 'tests/.results', + webServer: [ + { + cwd: 'examples/chat-room', + command: 'pnpm dev --port 3001', + port: 3001, + reuseExistingServer: !process.env.CI, + }, + { + cwd: 'examples/chat-room-with-custom-server', + command: 'pnpm dev --port 3002', + port: 3002, + reuseExistingServer: !process.env.CI, + }, + ], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 342b044..4455a30 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,197 +7,178 @@ settings: importers: .: + dependencies: + next: + specifier: '>=13.1.1' + version: 15.1.6(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: + specifier: '*' + version: 19.0.0 + ws: + specifier: '*' + version: 8.18.0 devDependencies: '@biomejs/biome': - specifier: 1.8.3 - version: 1.8.3 - '@changesets/changelog-git': - specifier: ^0.2.0 - version: 0.2.0 + specifier: ^1.9.4 + version: 1.9.4 + '@changesets/changelog-github': + specifier: ^0.5.0 + version: 0.5.0 '@changesets/cli': - specifier: ^2.27.9 - version: 2.27.9 - tsup: - specifier: ^8.1.0 - version: 8.1.0(postcss@8.4.39)(typescript@5.5.2) - tsx: - specifier: ^4.16.0 - version: 4.16.0 - turbo: - specifier: ^2.0.6 - version: 2.0.6 - typescript: - specifier: ^5.5.2 - version: 5.5.2 - - packages/cli: - dependencies: - '@babel/generator': - specifier: ^7.24.7 - version: 7.24.7 - '@babel/parser': - specifier: ^7.24.7 - version: 7.24.7 - '@babel/template': - specifier: ^7.24.7 - version: 7.24.7 - '@babel/types': - specifier: ^7.24.7 - version: 7.24.7 - '@inquirer/confirm': - specifier: ^3.1.12 - version: 3.1.12 - chalk: - specifier: ^4 - version: 4.1.2 - commander: - specifier: ^12.1.0 - version: 12.1.0 - semver: - specifier: ^7.6.2 - version: 7.6.2 - devDependencies: - '@types/babel__generator': - specifier: ^7.6.8 - version: 7.6.8 - '@types/babel__template': - specifier: ^7.4.4 - version: 7.4.4 - '@types/inquirer': - specifier: ^9.0.7 - version: 9.0.7 + specifier: ^2.27.12 + version: 2.27.12 + '@playwright/test': + specifier: ^1.50.1 + version: 1.50.1 '@types/node': - specifier: ^20.14.9 - version: 20.14.9 + specifier: ^22.13.1 + version: 22.13.1 + '@types/react': + specifier: ^19.0.8 + version: 19.0.8 '@types/semver': specifier: ^7.5.8 version: 7.5.8 + '@types/ws': + specifier: ^8.5.14 + version: 8.5.14 + chalk: + specifier: ^5.4.1 + version: 5.4.1 + commander: + specifier: ^13.1.0 + version: 13.1.0 + husky: + specifier: ^9.1.7 + version: 9.1.7 + pinst: + specifier: ^3.0.0 + version: 3.0.0 + semver: + specifier: ^7.7.1 + version: 7.7.1 + tsup: + specifier: ^8.3.6 + version: 8.3.6(postcss@8.4.31)(typescript@5.7.3) + typescript: + specifier: ^5.7.3 + version: 5.7.3 - packages/core: + examples/chat-room: + dependencies: + next: + specifier: 15.1.6 + version: 15.1.6(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next-ws: + specifier: workspace:^ + version: link:../.. + react: + specifier: ^19.0.0 + version: 19.0.0 + react-dom: + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) devDependencies: '@types/react': - specifier: ^18.3.3 - version: 18.3.3 - '@types/ws': - specifier: ^8.5.10 - version: 8.5.10 + specifier: ^19.0.8 + version: 19.0.8 + + examples/chat-room-with-custom-server: + dependencies: next: - specifier: ^14.2.4 - version: 14.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^15.1.6 + version: 15.1.6(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next-ws: + specifier: workspace:^ + version: link:../.. react: - specifier: ^18.3.1 - version: 18.3.1 - ws: - specifier: ^8.17.1 - version: 8.17.1 + specifier: ^19.0.0 + version: 19.0.0 + react-dom: + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) + devDependencies: + '@types/react': + specifier: ^19.0.8 + version: 19.0.8 packages: - '@babel/code-frame@7.24.7': - resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.24.7': - resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.24.7': - resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.24.7': - resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} - engines: {node: '>=6.9.0'} - - '@babel/highlight@7.24.7': - resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.24.7': - resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/runtime@7.26.0': - resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} - engines: {node: '>=6.9.0'} - - '@babel/template@7.24.7': - resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.24.7': - resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} + '@babel/runtime@7.26.7': + resolution: {integrity: sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==} engines: {node: '>=6.9.0'} - '@biomejs/biome@1.8.3': - resolution: {integrity: sha512-/uUV3MV+vyAczO+vKrPdOW0Iaet7UnJMU4bNMinggGJTAnBPjCoLEYcyYtYHNnUNYlv4xZMH6hVIQCAozq8d5w==} + '@biomejs/biome@1.9.4': + resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@1.8.3': - resolution: {integrity: sha512-9DYOjclFpKrH/m1Oz75SSExR8VKvNSSsLnVIqdnKexj6NwmiMlKk94Wa1kZEdv6MCOHGHgyyoV57Cw8WzL5n3A==} + '@biomejs/cli-darwin-arm64@1.9.4': + resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@1.8.3': - resolution: {integrity: sha512-UeW44L/AtbmOF7KXLCoM+9PSgPo0IDcyEUfIoOXYeANaNXXf9mLUwV1GeF2OWjyic5zj6CnAJ9uzk2LT3v/wAw==} + '@biomejs/cli-darwin-x64@1.9.4': + resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@1.8.3': - resolution: {integrity: sha512-9yjUfOFN7wrYsXt/T/gEWfvVxKlnh3yBpnScw98IF+oOeCYb5/b/+K7YNqKROV2i1DlMjg9g/EcN9wvj+NkMuQ==} + '@biomejs/cli-linux-arm64-musl@1.9.4': + resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@1.8.3': - resolution: {integrity: sha512-fed2ji8s+I/m8upWpTJGanqiJ0rnlHOK3DdxsyVLZQ8ClY6qLuPc9uehCREBifRJLl/iJyQpHIRufLDeotsPtw==} + '@biomejs/cli-linux-arm64@1.9.4': + resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@1.8.3': - resolution: {integrity: sha512-UHrGJX7PrKMKzPGoEsooKC9jXJMa28TUSMjcIlbDnIO4EAavCoVmNQaIuUSH0Ls2mpGMwUIf+aZJv657zfWWjA==} + '@biomejs/cli-linux-x64-musl@1.9.4': + resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@1.8.3': - resolution: {integrity: sha512-I8G2QmuE1teISyT8ie1HXsjFRz9L1m5n83U1O6m30Kw+kPMPSKjag6QGUn+sXT8V+XWIZxFFBoTDEDZW2KPDDw==} + '@biomejs/cli-linux-x64@1.9.4': + resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@1.8.3': - resolution: {integrity: sha512-J+Hu9WvrBevfy06eU1Na0lpc7uR9tibm9maHynLIoAjLZpQU3IW+OKHUtyL8p6/3pT2Ju5t5emReeIS2SAxhkQ==} + '@biomejs/cli-win32-arm64@1.9.4': + resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@1.8.3': - resolution: {integrity: sha512-/PJ59vA1pnQeKahemaQf4Nyj7IKUvGQSc3Ze1uIGi+Wvr1xF7rGobSrAAG01T/gUDG21vkDsZYM03NAmPiVkqg==} + '@biomejs/cli-win32-x64@1.9.4': + resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] - '@changesets/apply-release-plan@7.0.5': - resolution: {integrity: sha512-1cWCk+ZshEkSVEZrm2fSj1Gz8sYvxgUL4Q78+1ZZqeqfuevPTPk033/yUZ3df8BKMohkqqHfzj0HOOrG0KtXTw==} + '@changesets/apply-release-plan@7.0.8': + resolution: {integrity: sha512-qjMUj4DYQ1Z6qHawsn7S71SujrExJ+nceyKKyI9iB+M5p9lCL55afuEd6uLBPRpLGWQwkwvWegDHtwHJb1UjpA==} - '@changesets/assemble-release-plan@6.0.4': - resolution: {integrity: sha512-nqICnvmrwWj4w2x0fOhVj2QEGdlUuwVAwESrUo5HLzWMI1rE5SWfsr9ln+rDqWB6RQ2ZyaMZHUcU7/IRaUJS+Q==} + '@changesets/assemble-release-plan@6.0.5': + resolution: {integrity: sha512-IgvBWLNKZd6k4t72MBTBK3nkygi0j3t3zdC1zrfusYo0KpdsvnDjrMM9vPnTCLCMlfNs55jRL4gIMybxa64FCQ==} '@changesets/changelog-git@0.2.0': resolution: {integrity: sha512-bHOx97iFI4OClIT35Lok3sJAwM31VbUM++gnMBV16fdbtBhgYu4dxsphBF/0AZZsyAHMrnM0yFcj5gZM1py6uQ==} - '@changesets/cli@2.27.9': - resolution: {integrity: sha512-q42a/ZbDnxPpCb5Wkm6tMVIxgeI9C/bexntzTeCFBrQEdpisQqk8kCHllYZMDjYtEc1ZzumbMJAG8H0Z4rdvjg==} + '@changesets/changelog-github@0.5.0': + resolution: {integrity: sha512-zoeq2LJJVcPJcIotHRJEEA2qCqX0AQIeFE+L21L8sRLPVqDhSXY8ZWAt2sohtBpFZkBwu+LUwMSKRr2lMy3LJA==} + + '@changesets/cli@2.27.12': + resolution: {integrity: sha512-9o3fOfHYOvBnyEn0mcahB7wzaA3P4bGJf8PNqGit5PKaMEFdsRixik+txkrJWd2VX+O6wRFXpxQL8j/1ANKE9g==} hasBin: true - '@changesets/config@3.0.3': - resolution: {integrity: sha512-vqgQZMyIcuIpw9nqFIpTSNyc/wgm/Lu1zKN5vECy74u95Qx/Wa9g27HdgO4NkVAaq+BGA8wUc/qvbvVNs93n6A==} + '@changesets/config@3.0.5': + resolution: {integrity: sha512-QyXLSSd10GquX7hY0Mt4yQFMEeqnO5z/XLpbIr4PAkNNoQNKwDyiSrx4yd749WddusH1v3OSiA0NRAYmH/APpQ==} '@changesets/errors@0.2.0': resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} @@ -205,14 +186,17 @@ packages: '@changesets/get-dependents-graph@2.1.2': resolution: {integrity: sha512-sgcHRkiBY9i4zWYBwlVyAjEM9sAzs4wYVwJUdnbDLnVG3QwAaia1Mk5P8M7kraTOZN+vBET7n8KyB0YXCbFRLQ==} - '@changesets/get-release-plan@4.0.4': - resolution: {integrity: sha512-SicG/S67JmPTrdcc9Vpu0wSQt7IiuN0dc8iR5VScnnTVPfIaLvKmEGRvIaF0kcn8u5ZqLbormZNTO77bCEvyWw==} + '@changesets/get-github-info@0.6.0': + resolution: {integrity: sha512-v/TSnFVXI8vzX9/w3DU2Ol+UlTZcu3m0kXTjTT4KlAdwSvwutcByYwyYn9hwerPWfPkT2JfpoX0KgvCEi8Q/SA==} + + '@changesets/get-release-plan@4.0.6': + resolution: {integrity: sha512-FHRwBkY7Eili04Y5YMOZb0ezQzKikTka4wL753vfUA5COSebt7KThqiuCN9BewE4/qFGgF/5t3AuzXx1/UAY4w==} '@changesets/get-version-range-type@0.4.0': resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} - '@changesets/git@3.0.1': - resolution: {integrity: sha512-pdgHcYBLCPcLd82aRcuO0kxCDbw/yISlOtkmwmE8Odo1L6hSiZrBOsRl84eYG7DRCab/iHnOkWqExqc4wxk2LQ==} + '@changesets/git@3.0.2': + resolution: {integrity: sha512-r1/Kju9Y8OxRRdvna+nxpQIsMsRQn9dhhAZt94FLDeu0Hij2hnOozW8iqnHBgvu+KdnJppCveQwK4odwfw/aWQ==} '@changesets/logger@0.1.1': resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} @@ -223,8 +207,8 @@ packages: '@changesets/pre@2.0.1': resolution: {integrity: sha512-vvBJ/If4jKM4tPz9JdY2kGOgWmCowUYOi5Ycv8dyLnEE8FgpYYUo1mgJZxcdtGGP3aG8rAQulGLyyXGSLkIMTQ==} - '@changesets/read@0.6.1': - resolution: {integrity: sha512-jYMbyXQk3nwP25nRzQQGa1nKLY0KfoOV7VLgwucI0bUO8t8ZLCr6LZmgjXsiKuRDc+5A6doKPr9w2d+FEJ55zQ==} + '@changesets/read@0.6.2': + resolution: {integrity: sha512-wjfQpJvryY3zD61p8jR87mJdyx2FIhEcdXhKUqkja87toMrP/3jtg/Yg29upN+N4Ckf525/uvV7a4tzBlpk6gg==} '@changesets/should-skip-package@0.1.1': resolution: {integrity: sha512-H9LjLbF6mMHLtJIc/eHR9Na+MifJ3VxtgP/Y+XLn4BF7tDTEN1HNYtH6QMcjP1uxp9sjaFYmW8xqloaCi/ckTg==} @@ -238,166 +222,270 @@ packages: '@changesets/write@0.3.2': resolution: {integrity: sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==} - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} + '@emnapi/runtime@1.3.1': + resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + + '@esbuild/aix-ppc64@0.24.2': + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} + '@esbuild/android-arm64@0.24.2': + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} + '@esbuild/android-arm@0.24.2': + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} + '@esbuild/android-x64@0.24.2': + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} + '@esbuild/darwin-arm64@0.24.2': + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} + '@esbuild/darwin-x64@0.24.2': + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} + '@esbuild/freebsd-arm64@0.24.2': + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} + '@esbuild/freebsd-x64@0.24.2': + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} + '@esbuild/linux-arm64@0.24.2': + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} + '@esbuild/linux-arm@0.24.2': + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} + '@esbuild/linux-ia32@0.24.2': + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} + '@esbuild/linux-loong64@0.24.2': + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} + '@esbuild/linux-mips64el@0.24.2': + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} + '@esbuild/linux-ppc64@0.24.2': + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} + '@esbuild/linux-riscv64@0.24.2': + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} + '@esbuild/linux-s390x@0.24.2': + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} + '@esbuild/linux-x64@0.24.2': + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} + '@esbuild/netbsd-arm64@0.24.2': + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.24.2': + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} + '@esbuild/openbsd-arm64@0.24.2': + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.24.2': + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} + '@esbuild/sunos-x64@0.24.2': + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} + '@esbuild/win32-arm64@0.24.2': + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} + '@esbuild/win32-ia32@0.24.2': + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} + '@esbuild/win32-x64@0.24.2': + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + engines: {node: '>=18'} cpu: [x64] os: [win32] - '@inquirer/confirm@3.1.12': - resolution: {integrity: sha512-s5Sod79QsBBi5Qm7zxCq9DcAD0i7WRcjd/LzsiIAWqWZKW4+OJTGrCgVSLGIHTulwbZgdxM4AAxpCXe86hv4/Q==} - engines: {node: '>=18'} + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] - '@inquirer/core@9.0.0': - resolution: {integrity: sha512-y3q+fkCTGmvwk9Wf6yZlI3QGlLXbEm5M7Y7Eh8abaUbv+ffvmw2aB4FxSUrWaoaozwvEJSG60raHbCaUorXEzA==} - engines: {node: '>=18'} + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] - '@inquirer/figures@1.0.3': - resolution: {integrity: sha512-ErXXzENMH5pJt5/ssXV0DfWUZqly8nGzf0UcBV9xTnP+KyffE2mqyxIMBrZ8ijQck2nU0TQm40EQB53YreyWHw==} - engines: {node: '>=18'} + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] - '@inquirer/type@1.4.0': - resolution: {integrity: sha512-AjOqykVyjdJQvtfkNDGUyMYGF8xN50VUxftCQWsOyIo4DFRLr6VQhW0VItGI1JIyQGCGgIpKa7hMMwNhZb4OIw==} - engines: {node: '>=18'} + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@jridgewell/gen-mapping@0.3.5': - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} '@jridgewell/resolve-uri@3.1.2': @@ -408,8 +496,8 @@ packages: resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.4.15': - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} @@ -420,59 +508,53 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} - '@next/env@14.2.4': - resolution: {integrity: sha512-3EtkY5VDkuV2+lNmKlbkibIJxcO4oIHEhBWne6PaAp+76J9KoSsGvNikp6ivzAT8dhhBMYrm6op2pS1ApG0Hzg==} + '@next/env@15.1.6': + resolution: {integrity: sha512-d9AFQVPEYNr+aqokIiPLNK/MTyt3DWa/dpKveiAaVccUadFbhFEvY6FXYX2LJO2Hv7PHnLBu2oWwB4uBuHjr/w==} - '@next/swc-darwin-arm64@14.2.4': - resolution: {integrity: sha512-AH3mO4JlFUqsYcwFUHb1wAKlebHU/Hv2u2kb1pAuRanDZ7pD/A/KPD98RHZmwsJpdHQwfEc/06mgpSzwrJYnNg==} + '@next/swc-darwin-arm64@15.1.6': + resolution: {integrity: sha512-u7lg4Mpl9qWpKgy6NzEkz/w0/keEHtOybmIl0ykgItBxEM5mYotS5PmqTpo+Rhg8FiOiWgwr8USxmKQkqLBCrw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@14.2.4': - resolution: {integrity: sha512-QVadW73sWIO6E2VroyUjuAxhWLZWEpiFqHdZdoQ/AMpN9YWGuHV8t2rChr0ahy+irKX5mlDU7OY68k3n4tAZTg==} + '@next/swc-darwin-x64@15.1.6': + resolution: {integrity: sha512-x1jGpbHbZoZ69nRuogGL2MYPLqohlhnT9OCU6E6QFewwup+z+M6r8oU47BTeJcWsF2sdBahp5cKiAcDbwwK/lg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@14.2.4': - resolution: {integrity: sha512-KT6GUrb3oyCfcfJ+WliXuJnD6pCpZiosx2X3k66HLR+DMoilRb76LpWPGb4tZprawTtcnyrv75ElD6VncVamUQ==} + '@next/swc-linux-arm64-gnu@15.1.6': + resolution: {integrity: sha512-jar9sFw0XewXsBzPf9runGzoivajeWJUc/JkfbLTC4it9EhU8v7tCRLH7l5Y1ReTMN6zKJO0kKAGqDk8YSO2bg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@14.2.4': - resolution: {integrity: sha512-Alv8/XGSs/ytwQcbCHwze1HmiIkIVhDHYLjczSVrf0Wi2MvKn/blt7+S6FJitj3yTlMwMxII1gIJ9WepI4aZ/A==} + '@next/swc-linux-arm64-musl@15.1.6': + resolution: {integrity: sha512-+n3u//bfsrIaZch4cgOJ3tXCTbSxz0s6brJtU3SzLOvkJlPQMJ+eHVRi6qM2kKKKLuMY+tcau8XD9CJ1OjeSQQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@14.2.4': - resolution: {integrity: sha512-ze0ShQDBPCqxLImzw4sCdfnB3lRmN3qGMB2GWDRlq5Wqy4G36pxtNOo2usu/Nm9+V2Rh/QQnrRc2l94kYFXO6Q==} + '@next/swc-linux-x64-gnu@15.1.6': + resolution: {integrity: sha512-SpuDEXixM3PycniL4iVCLyUyvcl6Lt0mtv3am08sucskpG0tYkW1KlRhTgj4LI5ehyxriVVcfdoxuuP8csi3kQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@14.2.4': - resolution: {integrity: sha512-8dwC0UJoc6fC7PX70csdaznVMNr16hQrTDAMPvLPloazlcaWfdPogq+UpZX6Drqb1OBlwowz8iG7WR0Tzk/diQ==} + '@next/swc-linux-x64-musl@15.1.6': + resolution: {integrity: sha512-L4druWmdFSZIIRhF+G60API5sFB7suTbDRhYWSjiw0RbE+15igQvE2g2+S973pMGvwN3guw7cJUjA/TmbPWTHQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@14.2.4': - resolution: {integrity: sha512-jxyg67NbEWkDyvM+O8UDbPAyYRZqGLQDTPwvrBBeOSyVWW/jFQkQKQ70JDqDSYg1ZDdl+E3nkbFbq8xM8E9x8A==} + '@next/swc-win32-arm64-msvc@15.1.6': + resolution: {integrity: sha512-s8w6EeqNmi6gdvM19tqKKWbCyOBvXFbndkGHl+c9YrzsLARRdCHsD9S1fMj8gsXm9v8vhC8s3N8rjuC/XrtkEg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-ia32-msvc@14.2.4': - resolution: {integrity: sha512-twrmN753hjXRdcrZmZttb/m5xaCBFa48Dt3FbeEItpJArxriYDunWxJn+QFXdJ3hPkm4u7CKxncVvnmgQMY1ag==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - - '@next/swc-win32-x64-msvc@14.2.4': - resolution: {integrity: sha512-tkLrjBzqFTP8DVrAAQmZelEahfR9OxWpFR++vAI9FBhCiIxtwHwBHC23SBHCTURBtwB4kc/x44imVOnkKGNVGg==} + '@next/swc-win32-x64-msvc@15.1.6': + resolution: {integrity: sha512-6xomMuu54FAFxttYr5PJbEfu96godcxBTRk1OhAvJq0/EnmFU/Ybiax30Snis4vdWZ9LGpf7Roy5fSs7v/5ROQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -493,151 +575,142 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@rollup/rollup-android-arm-eabi@4.18.0': - resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==} + '@playwright/test@1.50.1': + resolution: {integrity: sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==} + engines: {node: '>=18'} + hasBin: true + + '@rollup/rollup-android-arm-eabi@4.34.4': + resolution: {integrity: sha512-gGi5adZWvjtJU7Axs//CWaQbQd/vGy8KGcnEaCWiyCqxWYDxwIlAHFuSe6Guoxtd0SRvSfVTDMPd5H+4KE2kKA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.18.0': - resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==} + '@rollup/rollup-android-arm64@4.34.4': + resolution: {integrity: sha512-1aRlh1gqtF7vNPMnlf1vJKk72Yshw5zknR/ZAVh7zycRAGF2XBMVDAHmFQz/Zws5k++nux3LOq/Ejj1WrDR6xg==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.18.0': - resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==} + '@rollup/rollup-darwin-arm64@4.34.4': + resolution: {integrity: sha512-drHl+4qhFj+PV/jrQ78p9ch6A0MfNVZScl/nBps5a7u01aGf/GuBRrHnRegA9bP222CBDfjYbFdjkIJ/FurvSQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.18.0': - resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==} + '@rollup/rollup-darwin-x64@4.34.4': + resolution: {integrity: sha512-hQqq/8QALU6t1+fbNmm6dwYsa0PDD4L5r3TpHx9dNl+aSEMnIksHZkSO3AVH+hBMvZhpumIGrTFj8XCOGuIXjw==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.18.0': - resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==} + '@rollup/rollup-freebsd-arm64@4.34.4': + resolution: {integrity: sha512-/L0LixBmbefkec1JTeAQJP0ETzGjFtNml2gpQXA8rpLo7Md+iXQzo9kwEgzyat5Q+OG/C//2B9Fx52UxsOXbzw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.34.4': + resolution: {integrity: sha512-6Rk3PLRK+b8L/M6m/x6Mfj60LhAUcLJ34oPaxufA+CfqkUrDoUPQYFdRrhqyOvtOKXLJZJwxlOLbQjNYQcRQfw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.34.4': + resolution: {integrity: sha512-kmT3x0IPRuXY/tNoABp2nDvI9EvdiS2JZsd4I9yOcLCCViKsP0gB38mVHOhluzx+SSVnM1KNn9k6osyXZhLoCA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.18.0': - resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==} + '@rollup/rollup-linux-arm-musleabihf@4.34.4': + resolution: {integrity: sha512-3iSA9tx+4PZcJH/Wnwsvx/BY4qHpit/u2YoZoXugWVfc36/4mRkgGEoRbRV7nzNBSCOgbWMeuQ27IQWgJ7tRzw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.18.0': - resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==} + '@rollup/rollup-linux-arm64-gnu@4.34.4': + resolution: {integrity: sha512-7CwSJW+sEhM9sESEk+pEREF2JL0BmyCro8UyTq0Kyh0nu1v0QPNY3yfLPFKChzVoUmaKj8zbdgBxUhBRR+xGxg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.18.0': - resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==} + '@rollup/rollup-linux-arm64-musl@4.34.4': + resolution: {integrity: sha512-GZdafB41/4s12j8Ss2izofjeFXRAAM7sHCb+S4JsI9vaONX/zQ8cXd87B9MRU/igGAJkKvmFmJJBeeT9jJ5Cbw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': - resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==} + '@rollup/rollup-linux-loongarch64-gnu@4.34.4': + resolution: {integrity: sha512-uuphLuw1X6ur11675c2twC6YxbzyLSpWggvdawTUamlsoUv81aAXRMPBC1uvQllnBGls0Qt5Siw8reSIBnbdqQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.34.4': + resolution: {integrity: sha512-KvLEw1os2gSmD6k6QPCQMm2T9P2GYvsMZMRpMz78QpSoEevHbV/KOUbI/46/JRalhtSAYZBYLAnT9YE4i/l4vg==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.18.0': - resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==} + '@rollup/rollup-linux-riscv64-gnu@4.34.4': + resolution: {integrity: sha512-wcpCLHGM9yv+3Dql/CI4zrY2mpQ4WFergD3c9cpRowltEh5I84pRT/EuHZsG0In4eBPPYthXnuR++HrFkeqwkA==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.18.0': - resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==} + '@rollup/rollup-linux-s390x-gnu@4.34.4': + resolution: {integrity: sha512-nLbfQp2lbJYU8obhRQusXKbuiqm4jSJteLwfjnunDT5ugBKdxqw1X9KWwk8xp1OMC6P5d0WbzxzhWoznuVK6XA==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.18.0': - resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==} + '@rollup/rollup-linux-x64-gnu@4.34.4': + resolution: {integrity: sha512-JGejzEfVzqc/XNiCKZj14eb6s5w8DdWlnQ5tWUbs99kkdvfq9btxxVX97AaxiUX7xJTKFA0LwoS0KU8C2faZRg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.18.0': - resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==} + '@rollup/rollup-linux-x64-musl@4.34.4': + resolution: {integrity: sha512-/iFIbhzeyZZy49ozAWJ1ZR2KW6ZdYUbQXLT4O5n1cRZRoTpwExnHLjlurDXXPKEGxiAg0ujaR9JDYKljpr2fDg==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.18.0': - resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==} + '@rollup/rollup-win32-arm64-msvc@4.34.4': + resolution: {integrity: sha512-qORc3UzoD5UUTneiP2Afg5n5Ti1GAW9Gp5vHPxzvAFFA3FBaum9WqGvYXGf+c7beFdOKNos31/41PRMUwh1tpA==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.18.0': - resolution: {integrity: sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==} + '@rollup/rollup-win32-ia32-msvc@4.34.4': + resolution: {integrity: sha512-5g7E2PHNK2uvoD5bASBD9aelm44nf1w4I5FEI7MPHLWcCSrR8JragXZWgKPXk5i2FU3JFfa6CGZLw2RrGBHs2Q==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.18.0': - resolution: {integrity: sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==} + '@rollup/rollup-win32-x64-msvc@4.34.4': + resolution: {integrity: sha512-p0scwGkR4kZ242xLPBuhSckrJ734frz6v9xZzD+kHVYRAkSUmdSLCIJRfql6H5//aF8Q10K+i7q8DiPfZp0b7A==} cpu: [x64] os: [win32] '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - '@swc/helpers@0.5.5': - resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} - - '@types/babel__generator@7.6.8': - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} - '@types/estree@1.0.5': - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - - '@types/inquirer@9.0.7': - resolution: {integrity: sha512-Q0zyBupO6NxGRZut/JdmqYKOnN95Eg5V8Csg3PGKkP+FnvsUZx1jAyK7fztIszxxMuoBA6E3KXWvdZVXIpx60g==} - - '@types/mute-stream@0.0.4': - resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - '@types/node@20.14.9': - resolution: {integrity: sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==} - - '@types/prop-types@15.7.12': - resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + '@types/node@22.13.1': + resolution: {integrity: sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==} - '@types/react@18.3.3': - resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} + '@types/react@19.0.8': + resolution: {integrity: sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==} '@types/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} - '@types/through@0.0.33': - resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} - - '@types/wrap-ansi@3.0.0': - resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} - - '@types/ws@8.5.10': - resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} + '@types/ws@8.5.14': + resolution: {integrity: sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==} ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} engines: {node: '>=12'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -649,10 +722,6 @@ packages: any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -667,10 +736,6 @@ packages: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -678,11 +743,11 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - bundle-require@4.2.1: - resolution: {integrity: sha512-7Q/6vkyYAwOmQNRw75x+4yRtZCZJXUDmHHlFdkiV0wgv/reNjtJwpu1jPJ0w2kbEpIM0uoKI3S4/f39dU7AjSA==} + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: - esbuild: '>=0.17' + esbuild: '>=0.18' busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} @@ -692,72 +757,65 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - caniuse-lite@1.0.30001638: - resolution: {integrity: sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==} - - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} + caniuse-lite@1.0.30001697: + resolution: {integrity: sha512-GwNPlWJin8E+d7Gxq96jxM6w0w+VFeyyXRsjU58emtkYqnbwHqXm5uT2uCmO0RQE9htWknOP4xtBlLmM/gWxvQ==} - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + chalk@5.4.1: + resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - - cli-width@4.1.0: - resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} - engines: {node: '>= 12'} - client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} engines: {node: '>=18'} commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} - cross-spawn@5.1.0: - resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} + consola@3.4.0: + resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==} + engines: {node: ^14.18.0 || >=16.10.0} - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - debug@4.3.5: - resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} + dataloader@1.4.0: + resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -769,10 +827,18 @@ packages: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + dotenv@8.6.0: + resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} + engines: {node: '>=10'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -786,24 +852,16 @@ packages: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} + esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + engines: {node: '>=18'} hasBin: true - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} @@ -811,12 +869,20 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} - fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fastq@1.19.0: + resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==} + + fdir@6.4.3: + resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} @@ -826,8 +892,8 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} - foreground-child@3.2.1: - resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} fs-extra@7.0.1: @@ -838,25 +904,22 @@ packages: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - get-tsconfig@4.7.5: - resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} - glob@10.4.2: - resolution: {integrity: sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==} - engines: {node: '>=16 || 14 >=14.18'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true globby@11.1.0: @@ -866,32 +929,24 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - human-id@1.0.2: resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} - ignore@5.3.1: - resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} @@ -909,10 +964,6 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - is-subdir@1.2.0: resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} engines: {node: '>=4'} @@ -924,31 +975,22 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jackspeak@3.4.0: - resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==} - engines: {node: '>=14'} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true - jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true - jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - lilconfig@3.1.2: - resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} lines-and-columns@1.2.4: @@ -968,32 +1010,17 @@ packages: lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - - lru-cache@10.3.0: - resolution: {integrity: sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==} - engines: {node: 14 || >=16.14} - - lru-cache@4.1.5: - resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - micromatch@4.0.7: - resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -1006,55 +1033,51 @@ packages: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - - mute-stream@1.0.0: - resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - next@14.2.4: - resolution: {integrity: sha512-R8/V7vugY+822rsQGQCjoLhMuC9oFj9SOi4Cl4b2wjDrseD0LRZ10W7R6Czo4w9ZznVSshKjuIomsRjvm9EKJQ==} - engines: {node: '>=18.17.0'} + next@15.1.6: + resolution: {integrity: sha512-Hch4wzbaX0vKQtalpXvUiw5sYivBy4cm5rzUKrBnUB/y436LGrvOUqYvlSeNVCWFO/770gDlltR9gqZH62ct4Q==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 '@playwright/test': ^1.41.2 - react: ^18.2.0 - react-dom: ^18.2.0 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 sass: ^1.3.0 peerDependenciesMeta: '@opentelemetry/api': optional: true '@playwright/test': optional: true + babel-plugin-react-compiler: + optional: true sass: optional: true - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} @@ -1082,11 +1105,11 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - package-json-from-dist@1.0.0: - resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - package-manager-detector@0.2.2: - resolution: {integrity: sha512-VgXbyrSNsml4eHWIvxxG/nTL4wgybMTXCV2Un/+yEc3aDKKU6nQBZjbeP3Pl3qm9Qg92X/1ng4ffvCeD/zwHgg==} + package-manager-detector@0.2.9: + resolution: {integrity: sha512-+vYvA/Y31l8Zk8dwxHhL3JfTuHPm6tlxM2A3GeQyl7ovYnSp1+mzAxClxaOr0qO1TtPxbQxetI7v5XqKLJZk7Q==} path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} @@ -1104,9 +1127,6 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - picocolors@1.0.1: - resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1114,42 +1134,60 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + pinst@3.0.0: + resolution: {integrity: sha512-cengSmBxtCyaJqtRSvJorIIZXMXg+lJ3sIljGmtBGUVonMnMsVJbnzl6jGN1HkOWwxNuJynCJ2hXxxqCQrFDdw==} + engines: {node: '>=12.0.0'} + hasBin: true + pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - postcss-load-config@4.0.2: - resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} - engines: {node: '>= 14'} + playwright-core@1.50.1: + resolution: {integrity: sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.50.1: + resolution: {integrity: sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==} + engines: {node: '>=18'} + hasBin: true + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} peerDependencies: + jiti: '>=1.21.0' postcss: '>=8.0.9' - ts-node: '>=9.0.0' + tsx: ^4.8.1 + yaml: ^2.4.2 peerDependenciesMeta: + jiti: + optional: true postcss: optional: true - ts-node: + tsx: + optional: true + yaml: optional: true postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.4.39: - resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==} - engines: {node: ^10 || ^12 || >=14} - prettier@2.8.8: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} hasBin: true - pseudomap@1.0.2: - resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -1157,22 +1195,22 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - react-dom@18.3.1: - resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + react-dom@19.0.0: + resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} peerDependencies: - react: ^18.3.1 + react: ^19.0.0 - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'} read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} + readdirp@4.1.1: + resolution: {integrity: sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==} + engines: {node: '>= 14.18.0'} regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} @@ -1181,72 +1219,62 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.18.0: - resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==} + rollup@4.34.4: + resolution: {integrity: sha512-spF66xoyD7rz3o08sHP7wogp1gZ6itSq22SGa/IZTcUDXDlOyrShwMwkVSB+BUxFRZZCUYqdb3KWDEOMVQZxuw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} - safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.25.0: + resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} - semver@7.6.2: - resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} hasBin: true - shebang-command@1.2.0: - resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} - engines: {node: '>=0.10.0'} + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} - shebang-regex@1.0.0: - resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} - engines: {node: '>=0.10.0'} - shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} - spawndamnit@2.0.0: - resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} + spawndamnit@3.0.1: + resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -1275,17 +1303,13 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - styled-jsx@5.1.1: - resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} peerDependencies: '@babel/core': '*' babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' peerDependenciesMeta: '@babel/core': optional: true @@ -1297,14 +1321,6 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} @@ -1316,18 +1332,24 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.10: + resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} + engines: {node: '>=12.0.0'} + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} - to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} @@ -1338,102 +1360,57 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - tslib@2.6.3: - resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsup@8.1.0: - resolution: {integrity: sha512-UFdfCAXukax+U6KzeTNO2kAARHcWxmKsnvSPXUcfA1D+kU05XDccCrkffCQpFaWDsZfV0jMyTsxU39VfCp6EOg==} + tsup@8.3.6: + resolution: {integrity: sha512-XkVtlDV/58S9Ye0JxUUTcrQk4S+EqlOHKzg6Roa62rdjL1nGWNUstG0xgI4vanHdfIpjP448J8vlN0oK6XOJ5g==} engines: {node: '>=18'} hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true - - tsx@4.16.0: - resolution: {integrity: sha512-MPgN+CuY+4iKxGoJNPv+1pyo5YWZAQ5XfsyobUG+zoKG7IkvCPLZDEyoIb8yLS2FcWci1nlxAqmvPlFWD5AFiQ==} - engines: {node: '>=18.0.0'} - hasBin: true - - turbo-darwin-64@2.0.6: - resolution: {integrity: sha512-XpgBwWj3Ggmz/gQVqXdMKXHC1iFPMDiuwugLwSzE7Ih0O13JuNtYZKhQnopvbDQnFQCeRq2Vsm5OTWabg/oB/g==} - cpu: [x64] - os: [darwin] - - turbo-darwin-arm64@2.0.6: - resolution: {integrity: sha512-RfeZYXIAkiA21E8lsvfptGTqz/256YD+eI1x37fedfvnHFWuIMFZGAOwJxtZc6QasQunDZ9TRRREbJNI68tkIw==} - cpu: [arm64] - os: [darwin] - - turbo-linux-64@2.0.6: - resolution: {integrity: sha512-92UDa0xNQQbx0HdSp9ag3YSS3xPdavhc7q9q9mxIAcqyjjD6VElA4Y85m4F/DDGE5SolCrvBz2sQhVmkOd6Caw==} - cpu: [x64] - os: [linux] - - turbo-linux-arm64@2.0.6: - resolution: {integrity: sha512-eQKu6utCVUkIH2kqOzD8OS6E0ba6COjWm6PRDTNCHQRljZW503ycaTUIdMOiJrVg1MkEjDyOReUg8s8D18aJ4Q==} - cpu: [arm64] - os: [linux] - - turbo-windows-64@2.0.6: - resolution: {integrity: sha512-+9u4EPrpoeHYCQ46dRcou9kbkSoelhOelHNcbs2d86D6ruYD/oIAHK9qgYK8LeARRz0jxhZIA/dWYdYsxJJWkw==} - cpu: [x64] - os: [win32] - - turbo-windows-arm64@2.0.6: - resolution: {integrity: sha512-rdrKL+p+EjtdDVg0wQ/7yTbzkIYrnb0Pw4IKcjsy3M0RqUM9UcEi67b94XOAyTa5a0GqJL1+tUj2ebsFGPgZbg==} - cpu: [arm64] - os: [win32] - - turbo@2.0.6: - resolution: {integrity: sha512-/Ftmxd5Mq//a9yMonvmwENNUN65jOVTwhhBPQjEtNZutYT9YKyzydFGLyVM1nzhpLWahQSMamRc/RDBv5EapzA==} - hasBin: true - - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true - typescript@5.5.2: - resolution: {integrity: sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==} + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} engines: {node: '>=14.17'} hasBin: true - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} - which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -1442,8 +1419,8 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - ws@8.17.1: - resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -1454,103 +1431,52 @@ packages: utf-8-validate: optional: true - yallist@2.1.2: - resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} - - yaml@2.4.5: - resolution: {integrity: sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==} - engines: {node: '>= 14'} - hasBin: true - - yoctocolors-cjs@2.1.1: - resolution: {integrity: sha512-c6T13b6qYcJZvck7QbEFXrFX/Mu2KOjvAGiKHmYMUg96jxNpfP6i+psGW72BOPxOIDUJrORG+Kyu7quMX9CQBQ==} - engines: {node: '>=18'} - snapshots: - '@babel/code-frame@7.24.7': - dependencies: - '@babel/highlight': 7.24.7 - picocolors: 1.0.1 - - '@babel/generator@7.24.7': - dependencies: - '@babel/types': 7.24.7 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 - - '@babel/helper-string-parser@7.24.7': {} - - '@babel/helper-validator-identifier@7.24.7': {} - - '@babel/highlight@7.24.7': - dependencies: - '@babel/helper-validator-identifier': 7.24.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.0.1 - - '@babel/parser@7.24.7': - dependencies: - '@babel/types': 7.24.7 - - '@babel/runtime@7.26.0': + '@babel/runtime@7.26.7': dependencies: regenerator-runtime: 0.14.1 - '@babel/template@7.24.7': - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 - - '@babel/types@7.24.7': - dependencies: - '@babel/helper-string-parser': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - to-fast-properties: 2.0.0 - - '@biomejs/biome@1.8.3': + '@biomejs/biome@1.9.4': optionalDependencies: - '@biomejs/cli-darwin-arm64': 1.8.3 - '@biomejs/cli-darwin-x64': 1.8.3 - '@biomejs/cli-linux-arm64': 1.8.3 - '@biomejs/cli-linux-arm64-musl': 1.8.3 - '@biomejs/cli-linux-x64': 1.8.3 - '@biomejs/cli-linux-x64-musl': 1.8.3 - '@biomejs/cli-win32-arm64': 1.8.3 - '@biomejs/cli-win32-x64': 1.8.3 + '@biomejs/cli-darwin-arm64': 1.9.4 + '@biomejs/cli-darwin-x64': 1.9.4 + '@biomejs/cli-linux-arm64': 1.9.4 + '@biomejs/cli-linux-arm64-musl': 1.9.4 + '@biomejs/cli-linux-x64': 1.9.4 + '@biomejs/cli-linux-x64-musl': 1.9.4 + '@biomejs/cli-win32-arm64': 1.9.4 + '@biomejs/cli-win32-x64': 1.9.4 - '@biomejs/cli-darwin-arm64@1.8.3': + '@biomejs/cli-darwin-arm64@1.9.4': optional: true - '@biomejs/cli-darwin-x64@1.8.3': + '@biomejs/cli-darwin-x64@1.9.4': optional: true - '@biomejs/cli-linux-arm64-musl@1.8.3': + '@biomejs/cli-linux-arm64-musl@1.9.4': optional: true - '@biomejs/cli-linux-arm64@1.8.3': + '@biomejs/cli-linux-arm64@1.9.4': optional: true - '@biomejs/cli-linux-x64-musl@1.8.3': + '@biomejs/cli-linux-x64-musl@1.9.4': optional: true - '@biomejs/cli-linux-x64@1.8.3': + '@biomejs/cli-linux-x64@1.9.4': optional: true - '@biomejs/cli-win32-arm64@1.8.3': + '@biomejs/cli-win32-arm64@1.9.4': optional: true - '@biomejs/cli-win32-x64@1.8.3': + '@biomejs/cli-win32-x64@1.9.4': optional: true - '@changesets/apply-release-plan@7.0.5': + '@changesets/apply-release-plan@7.0.8': dependencies: - '@changesets/config': 3.0.3 + '@changesets/config': 3.0.5 '@changesets/get-version-range-type': 0.4.0 - '@changesets/git': 3.0.1 + '@changesets/git': 3.0.2 '@changesets/should-skip-package': 0.1.1 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 @@ -1560,34 +1486,42 @@ snapshots: outdent: 0.5.0 prettier: 2.8.8 resolve-from: 5.0.0 - semver: 7.6.2 + semver: 7.7.1 - '@changesets/assemble-release-plan@6.0.4': + '@changesets/assemble-release-plan@6.0.5': dependencies: '@changesets/errors': 0.2.0 '@changesets/get-dependents-graph': 2.1.2 '@changesets/should-skip-package': 0.1.1 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 - semver: 7.6.2 + semver: 7.7.1 '@changesets/changelog-git@0.2.0': dependencies: '@changesets/types': 6.0.0 - '@changesets/cli@2.27.9': + '@changesets/changelog-github@0.5.0': + dependencies: + '@changesets/get-github-info': 0.6.0 + '@changesets/types': 6.0.0 + dotenv: 8.6.0 + transitivePeerDependencies: + - encoding + + '@changesets/cli@2.27.12': dependencies: - '@changesets/apply-release-plan': 7.0.5 - '@changesets/assemble-release-plan': 6.0.4 + '@changesets/apply-release-plan': 7.0.8 + '@changesets/assemble-release-plan': 6.0.5 '@changesets/changelog-git': 0.2.0 - '@changesets/config': 3.0.3 + '@changesets/config': 3.0.5 '@changesets/errors': 0.2.0 '@changesets/get-dependents-graph': 2.1.2 - '@changesets/get-release-plan': 4.0.4 - '@changesets/git': 3.0.1 + '@changesets/get-release-plan': 4.0.6 + '@changesets/git': 3.0.2 '@changesets/logger': 0.1.1 '@changesets/pre': 2.0.1 - '@changesets/read': 0.6.1 + '@changesets/read': 0.6.2 '@changesets/should-skip-package': 0.1.1 '@changesets/types': 6.0.0 '@changesets/write': 0.3.2 @@ -1599,14 +1533,14 @@ snapshots: fs-extra: 7.0.1 mri: 1.2.0 p-limit: 2.3.0 - package-manager-detector: 0.2.2 + package-manager-detector: 0.2.9 picocolors: 1.1.1 resolve-from: 5.0.0 - semver: 7.6.2 - spawndamnit: 2.0.0 + semver: 7.7.1 + spawndamnit: 3.0.1 term-size: 2.2.1 - '@changesets/config@3.0.3': + '@changesets/config@3.0.5': dependencies: '@changesets/errors': 0.2.0 '@changesets/get-dependents-graph': 2.1.2 @@ -1614,7 +1548,7 @@ snapshots: '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 - micromatch: 4.0.7 + micromatch: 4.0.8 '@changesets/errors@0.2.0': dependencies: @@ -1625,26 +1559,33 @@ snapshots: '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 picocolors: 1.1.1 - semver: 7.6.2 + semver: 7.7.1 + + '@changesets/get-github-info@0.6.0': + dependencies: + dataloader: 1.4.0 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding - '@changesets/get-release-plan@4.0.4': + '@changesets/get-release-plan@4.0.6': dependencies: - '@changesets/assemble-release-plan': 6.0.4 - '@changesets/config': 3.0.3 + '@changesets/assemble-release-plan': 6.0.5 + '@changesets/config': 3.0.5 '@changesets/pre': 2.0.1 - '@changesets/read': 0.6.1 + '@changesets/read': 0.6.2 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 '@changesets/get-version-range-type@0.4.0': {} - '@changesets/git@3.0.1': + '@changesets/git@3.0.2': dependencies: '@changesets/errors': 0.2.0 '@manypkg/get-packages': 1.1.3 is-subdir: 1.2.0 - micromatch: 4.0.7 - spawndamnit: 2.0.0 + micromatch: 4.0.8 + spawndamnit: 3.0.1 '@changesets/logger@0.1.1': dependencies: @@ -1662,9 +1603,9 @@ snapshots: '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 - '@changesets/read@0.6.1': + '@changesets/read@0.6.2': dependencies: - '@changesets/git': 3.0.1 + '@changesets/git': 3.0.2 '@changesets/logger': 0.1.1 '@changesets/parse': 0.4.0 '@changesets/types': 6.0.0 @@ -1688,101 +1629,160 @@ snapshots: human-id: 1.0.2 prettier: 2.8.8 - '@esbuild/aix-ppc64@0.21.5': + '@emnapi/runtime@1.3.1': + dependencies: + tslib: 2.8.1 optional: true - '@esbuild/android-arm64@0.21.5': + '@esbuild/aix-ppc64@0.24.2': optional: true - '@esbuild/android-arm@0.21.5': + '@esbuild/android-arm64@0.24.2': optional: true - '@esbuild/android-x64@0.21.5': + '@esbuild/android-arm@0.24.2': optional: true - '@esbuild/darwin-arm64@0.21.5': + '@esbuild/android-x64@0.24.2': optional: true - '@esbuild/darwin-x64@0.21.5': + '@esbuild/darwin-arm64@0.24.2': optional: true - '@esbuild/freebsd-arm64@0.21.5': + '@esbuild/darwin-x64@0.24.2': optional: true - '@esbuild/freebsd-x64@0.21.5': + '@esbuild/freebsd-arm64@0.24.2': optional: true - '@esbuild/linux-arm64@0.21.5': + '@esbuild/freebsd-x64@0.24.2': optional: true - '@esbuild/linux-arm@0.21.5': + '@esbuild/linux-arm64@0.24.2': optional: true - '@esbuild/linux-ia32@0.21.5': + '@esbuild/linux-arm@0.24.2': optional: true - '@esbuild/linux-loong64@0.21.5': + '@esbuild/linux-ia32@0.24.2': optional: true - '@esbuild/linux-mips64el@0.21.5': + '@esbuild/linux-loong64@0.24.2': optional: true - '@esbuild/linux-ppc64@0.21.5': + '@esbuild/linux-mips64el@0.24.2': optional: true - '@esbuild/linux-riscv64@0.21.5': + '@esbuild/linux-ppc64@0.24.2': optional: true - '@esbuild/linux-s390x@0.21.5': + '@esbuild/linux-riscv64@0.24.2': optional: true - '@esbuild/linux-x64@0.21.5': + '@esbuild/linux-s390x@0.24.2': optional: true - '@esbuild/netbsd-x64@0.21.5': + '@esbuild/linux-x64@0.24.2': optional: true - '@esbuild/openbsd-x64@0.21.5': + '@esbuild/netbsd-arm64@0.24.2': optional: true - '@esbuild/sunos-x64@0.21.5': + '@esbuild/netbsd-x64@0.24.2': optional: true - '@esbuild/win32-arm64@0.21.5': + '@esbuild/openbsd-arm64@0.24.2': optional: true - '@esbuild/win32-ia32@0.21.5': + '@esbuild/openbsd-x64@0.24.2': optional: true - '@esbuild/win32-x64@0.21.5': + '@esbuild/sunos-x64@0.24.2': optional: true - '@inquirer/confirm@3.1.12': - dependencies: - '@inquirer/core': 9.0.0 - '@inquirer/type': 1.4.0 + '@esbuild/win32-arm64@0.24.2': + optional: true - '@inquirer/core@9.0.0': - dependencies: - '@inquirer/figures': 1.0.3 - '@inquirer/type': 1.4.0 - '@types/mute-stream': 0.0.4 - '@types/node': 20.14.9 - '@types/wrap-ansi': 3.0.0 - ansi-escapes: 4.3.2 - cli-spinners: 2.9.2 - cli-width: 4.1.0 - mute-stream: 1.0.0 - signal-exit: 4.1.0 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.1 + '@esbuild/win32-ia32@0.24.2': + optional: true + + '@esbuild/win32-x64@0.24.2': + optional: true + + '@img/sharp-darwin-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + optional: true + + '@img/sharp-darwin-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + optional: true - '@inquirer/figures@1.0.3': {} + '@img/sharp-libvips-darwin-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.0.5': + optional: true + + '@img/sharp-libvips-linux-s390x@1.0.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + optional: true + + '@img/sharp-linux-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + optional: true + + '@img/sharp-linux-arm@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + optional: true + + '@img/sharp-linux-s390x@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + optional: true + + '@img/sharp-linux-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + optional: true - '@inquirer/type@1.4.0': + '@img/sharp-wasm32@0.33.5': dependencies: - mute-stream: 1.0.0 + '@emnapi/runtime': 1.3.1 + optional: true + + '@img/sharp-win32-ia32@0.33.5': + optional: true + + '@img/sharp-win32-x64@0.33.5': + optional: true '@isaacs/cliui@8.0.2': dependencies: @@ -1793,66 +1793,63 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@jridgewell/gen-mapping@0.3.5': + '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.25 '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/set-array@1.2.1': {} - '@jridgewell/sourcemap-codec@1.4.15': {} + '@jridgewell/sourcemap-codec@1.5.0': {} '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 '@manypkg/find-root@1.1.0': dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.7 '@types/node': 12.20.55 find-up: 4.1.0 fs-extra: 8.1.0 '@manypkg/get-packages@1.1.3': dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.7 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 globby: 11.1.0 read-yaml-file: 1.1.0 - '@next/env@14.2.4': {} - - '@next/swc-darwin-arm64@14.2.4': - optional: true + '@next/env@15.1.6': {} - '@next/swc-darwin-x64@14.2.4': + '@next/swc-darwin-arm64@15.1.6': optional: true - '@next/swc-linux-arm64-gnu@14.2.4': + '@next/swc-darwin-x64@15.1.6': optional: true - '@next/swc-linux-arm64-musl@14.2.4': + '@next/swc-linux-arm64-gnu@15.1.6': optional: true - '@next/swc-linux-x64-gnu@14.2.4': + '@next/swc-linux-arm64-musl@15.1.6': optional: true - '@next/swc-linux-x64-musl@14.2.4': + '@next/swc-linux-x64-gnu@15.1.6': optional: true - '@next/swc-win32-arm64-msvc@14.2.4': + '@next/swc-linux-x64-musl@15.1.6': optional: true - '@next/swc-win32-ia32-msvc@14.2.4': + '@next/swc-win32-arm64-msvc@15.1.6': optional: true - '@next/swc-win32-x64-msvc@14.2.4': + '@next/swc-win32-x64-msvc@15.1.6': optional: true '@nodelib/fs.scandir@2.1.5': @@ -1865,124 +1862,101 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 + fastq: 1.19.0 '@pkgjs/parseargs@0.11.0': optional: true - '@rollup/rollup-android-arm-eabi@4.18.0': - optional: true + '@playwright/test@1.50.1': + dependencies: + playwright: 1.50.1 - '@rollup/rollup-android-arm64@4.18.0': + '@rollup/rollup-android-arm-eabi@4.34.4': optional: true - '@rollup/rollup-darwin-arm64@4.18.0': + '@rollup/rollup-android-arm64@4.34.4': optional: true - '@rollup/rollup-darwin-x64@4.18.0': + '@rollup/rollup-darwin-arm64@4.34.4': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.18.0': + '@rollup/rollup-darwin-x64@4.34.4': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.18.0': + '@rollup/rollup-freebsd-arm64@4.34.4': optional: true - '@rollup/rollup-linux-arm64-gnu@4.18.0': + '@rollup/rollup-freebsd-x64@4.34.4': optional: true - '@rollup/rollup-linux-arm64-musl@4.18.0': + '@rollup/rollup-linux-arm-gnueabihf@4.34.4': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': + '@rollup/rollup-linux-arm-musleabihf@4.34.4': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.18.0': + '@rollup/rollup-linux-arm64-gnu@4.34.4': optional: true - '@rollup/rollup-linux-s390x-gnu@4.18.0': + '@rollup/rollup-linux-arm64-musl@4.34.4': optional: true - '@rollup/rollup-linux-x64-gnu@4.18.0': + '@rollup/rollup-linux-loongarch64-gnu@4.34.4': optional: true - '@rollup/rollup-linux-x64-musl@4.18.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.34.4': optional: true - '@rollup/rollup-win32-arm64-msvc@4.18.0': + '@rollup/rollup-linux-riscv64-gnu@4.34.4': optional: true - '@rollup/rollup-win32-ia32-msvc@4.18.0': + '@rollup/rollup-linux-s390x-gnu@4.34.4': optional: true - '@rollup/rollup-win32-x64-msvc@4.18.0': + '@rollup/rollup-linux-x64-gnu@4.34.4': optional: true - '@swc/counter@0.1.3': {} + '@rollup/rollup-linux-x64-musl@4.34.4': + optional: true - '@swc/helpers@0.5.5': - dependencies: - '@swc/counter': 0.1.3 - tslib: 2.6.3 + '@rollup/rollup-win32-arm64-msvc@4.34.4': + optional: true - '@types/babel__generator@7.6.8': - dependencies: - '@babel/types': 7.24.7 + '@rollup/rollup-win32-ia32-msvc@4.34.4': + optional: true - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 + '@rollup/rollup-win32-x64-msvc@4.34.4': + optional: true - '@types/estree@1.0.5': {} + '@swc/counter@0.1.3': {} - '@types/inquirer@9.0.7': + '@swc/helpers@0.5.15': dependencies: - '@types/through': 0.0.33 - rxjs: 7.8.1 + tslib: 2.8.1 - '@types/mute-stream@0.0.4': - dependencies: - '@types/node': 20.14.9 + '@types/estree@1.0.6': {} '@types/node@12.20.55': {} - '@types/node@20.14.9': + '@types/node@22.13.1': dependencies: - undici-types: 5.26.5 - - '@types/prop-types@15.7.12': {} + undici-types: 6.20.0 - '@types/react@18.3.3': + '@types/react@19.0.8': dependencies: - '@types/prop-types': 15.7.12 csstype: 3.1.3 '@types/semver@7.5.8': {} - '@types/through@0.0.33': - dependencies: - '@types/node': 20.14.9 - - '@types/wrap-ansi@3.0.0': {} - - '@types/ws@8.5.10': + '@types/ws@8.5.14': dependencies: - '@types/node': 20.14.9 + '@types/node': 22.13.1 ansi-colors@4.1.3: {} - ansi-escapes@4.3.2: - dependencies: - type-fest: 0.21.3 - ansi-regex@5.0.1: {} - ansi-regex@6.0.1: {} - - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 + ansi-regex@6.1.0: {} ansi-styles@4.3.0: dependencies: @@ -1992,11 +1966,6 @@ snapshots: any-promise@1.3.0: {} - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -2009,8 +1978,6 @@ snapshots: dependencies: is-windows: 1.0.2 - binary-extensions@2.3.0: {} - brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 @@ -2019,9 +1986,9 @@ snapshots: dependencies: fill-range: 7.1.1 - bundle-require@4.2.1(esbuild@0.21.5): + bundle-require@5.1.0(esbuild@0.24.2): dependencies: - esbuild: 0.21.5 + esbuild: 0.24.2 load-tsconfig: 0.2.5 busboy@1.6.0: @@ -2030,64 +1997,45 @@ snapshots: cac@6.7.14: {} - caniuse-lite@1.0.30001638: {} - - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 + caniuse-lite@1.0.30001697: {} - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 + chalk@5.4.1: {} chardet@0.7.0: {} - chokidar@3.6.0: + chokidar@4.0.3: dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 + readdirp: 4.1.1 ci-info@3.9.0: {} - cli-spinners@2.9.2: {} - - cli-width@4.1.0: {} - client-only@0.0.1: {} - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-name@1.1.3: {} - color-name@1.1.4: {} - commander@12.1.0: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + optional: true + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + optional: true + + commander@13.1.0: {} commander@4.1.1: {} - cross-spawn@5.1.0: - dependencies: - lru-cache: 4.1.5 - shebang-command: 1.2.0 - which: 1.3.1 + consola@3.4.0: {} - cross-spawn@7.0.3: + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 @@ -2095,16 +2043,23 @@ snapshots: csstype@3.1.3: {} - debug@4.3.5: + dataloader@1.4.0: {} + + debug@4.4.0: dependencies: - ms: 2.1.2 + ms: 2.1.3 detect-indent@6.1.0: {} + detect-libc@2.0.3: + optional: true + dir-glob@3.0.1: dependencies: path-type: 4.0.0 + dotenv@8.6.0: {} + eastasianwidth@0.2.0: {} emoji-regex@8.0.0: {} @@ -2116,48 +2071,36 @@ snapshots: ansi-colors: 4.1.3 strip-ansi: 6.0.1 - esbuild@0.21.5: + esbuild@0.24.2: optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 - - escape-string-regexp@1.0.5: {} + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 esprima@4.0.1: {} - execa@5.1.1: - dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - extendable-error@0.1.7: {} external-editor@3.1.0: @@ -2166,18 +2109,22 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 - fast-glob@3.3.2: + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.7 + micromatch: 4.0.8 - fastq@1.17.1: + fastq@1.19.0: dependencies: reusify: 1.0.4 + fdir@6.4.3(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -2187,9 +2134,9 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 - foreground-child@3.2.1: + foreground-child@3.3.0: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 signal-exit: 4.1.0 fs-extra@7.0.1: @@ -2204,56 +2151,48 @@ snapshots: jsonfile: 4.0.0 universalify: 0.1.2 - fsevents@2.3.3: + fsevents@2.3.2: optional: true - get-stream@6.0.1: {} - - get-tsconfig@4.7.5: - dependencies: - resolve-pkg-maps: 1.0.0 + fsevents@2.3.3: + optional: true glob-parent@5.1.2: dependencies: is-glob: 4.0.3 - glob@10.4.2: + glob@10.4.5: dependencies: - foreground-child: 3.2.1 - jackspeak: 3.4.0 + foreground-child: 3.3.0 + jackspeak: 3.4.3 minimatch: 9.0.5 minipass: 7.1.2 - package-json-from-dist: 1.0.0 + package-json-from-dist: 1.0.1 path-scurry: 1.11.1 globby@11.1.0: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.1 + fast-glob: 3.3.3 + ignore: 5.3.2 merge2: 1.4.1 slash: 3.0.0 graceful-fs@4.2.11: {} - has-flag@3.0.0: {} - - has-flag@4.0.0: {} - human-id@1.0.2: {} - human-signals@2.1.0: {} + husky@9.1.7: {} iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 - ignore@5.3.1: {} + ignore@5.3.2: {} - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 + is-arrayish@0.3.2: + optional: true is-extglob@2.1.1: {} @@ -2265,8 +2204,6 @@ snapshots: is-number@7.0.0: {} - is-stream@2.0.1: {} - is-subdir@1.2.0: dependencies: better-path-resolve: 1.0.0 @@ -2275,7 +2212,7 @@ snapshots: isexe@2.0.0: {} - jackspeak@3.4.0: + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: @@ -2283,20 +2220,16 @@ snapshots: joycon@3.1.1: {} - js-tokens@4.0.0: {} - js-yaml@3.14.1: dependencies: argparse: 1.0.10 esprima: 4.0.1 - jsesc@2.5.2: {} - jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 - lilconfig@3.1.2: {} + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -2310,28 +2243,15 @@ snapshots: lodash.startcase@4.4.0: {} - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 - - lru-cache@10.3.0: {} - - lru-cache@4.1.5: - dependencies: - pseudomap: 1.0.2 - yallist: 2.1.2 - - merge-stream@2.0.0: {} + lru-cache@10.4.3: {} merge2@1.4.1: {} - micromatch@4.0.7: + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 - mimic-fn@2.1.0: {} - minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 @@ -2340,9 +2260,7 @@ snapshots: mri@1.2.0: {} - ms@2.1.2: {} - - mute-stream@1.0.0: {} + ms@2.1.3: {} mz@2.7.0: dependencies: @@ -2350,45 +2268,40 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.3.7: {} + nanoid@3.3.8: {} - next@14.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@15.1.6(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - '@next/env': 14.2.4 - '@swc/helpers': 0.5.5 + '@next/env': 15.1.6 + '@swc/counter': 0.1.3 + '@swc/helpers': 0.5.15 busboy: 1.6.0 - caniuse-lite: 1.0.30001638 - graceful-fs: 4.2.11 + caniuse-lite: 1.0.30001697 postcss: 8.4.31 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + styled-jsx: 5.1.6(react@19.0.0) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.4 - '@next/swc-darwin-x64': 14.2.4 - '@next/swc-linux-arm64-gnu': 14.2.4 - '@next/swc-linux-arm64-musl': 14.2.4 - '@next/swc-linux-x64-gnu': 14.2.4 - '@next/swc-linux-x64-musl': 14.2.4 - '@next/swc-win32-arm64-msvc': 14.2.4 - '@next/swc-win32-ia32-msvc': 14.2.4 - '@next/swc-win32-x64-msvc': 14.2.4 + '@next/swc-darwin-arm64': 15.1.6 + '@next/swc-darwin-x64': 15.1.6 + '@next/swc-linux-arm64-gnu': 15.1.6 + '@next/swc-linux-arm64-musl': 15.1.6 + '@next/swc-linux-x64-gnu': 15.1.6 + '@next/swc-linux-x64-musl': 15.1.6 + '@next/swc-win32-arm64-msvc': 15.1.6 + '@next/swc-win32-x64-msvc': 15.1.6 + '@playwright/test': 1.50.1 + sharp: 0.33.5 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - normalize-path@3.0.0: {} - - npm-run-path@4.0.1: + node-fetch@2.7.0: dependencies: - path-key: 3.1.1 + whatwg-url: 5.0.0 object-assign@4.1.1: {} - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - os-tmpdir@1.0.2: {} outdent@0.5.0: {} @@ -2409,9 +2322,9 @@ snapshots: p-try@2.2.0: {} - package-json-from-dist@1.0.0: {} + package-json-from-dist@1.0.1: {} - package-manager-detector@0.2.2: {} + package-manager-detector@0.2.9: {} path-exists@4.0.0: {} @@ -2419,58 +2332,55 @@ snapshots: path-scurry@1.11.1: dependencies: - lru-cache: 10.3.0 + lru-cache: 10.4.3 minipass: 7.1.2 path-type@4.0.0: {} - picocolors@1.0.1: {} - picocolors@1.1.1: {} picomatch@2.3.1: {} + picomatch@4.0.2: {} + pify@4.0.1: {} + pinst@3.0.0: {} + pirates@4.0.6: {} - postcss-load-config@4.0.2(postcss@8.4.39): + playwright-core@1.50.1: {} + + playwright@1.50.1: dependencies: - lilconfig: 3.1.2 - yaml: 2.4.5 + playwright-core: 1.50.1 optionalDependencies: - postcss: 8.4.39 + fsevents: 2.3.2 - postcss@8.4.31: + postcss-load-config@6.0.1(postcss@8.4.31): dependencies: - nanoid: 3.3.7 - picocolors: 1.0.1 - source-map-js: 1.2.0 + lilconfig: 3.1.3 + optionalDependencies: + postcss: 8.4.31 - postcss@8.4.39: + postcss@8.4.31: dependencies: - nanoid: 3.3.7 + nanoid: 3.3.8 picocolors: 1.1.1 - source-map-js: 1.2.0 - optional: true + source-map-js: 1.2.1 prettier@2.8.8: {} - pseudomap@1.0.2: {} - punycode@2.3.1: {} queue-microtask@1.2.3: {} - react-dom@18.3.1(react@18.3.1): + react-dom@19.0.0(react@19.0.0): dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 + react: 19.0.0 + scheduler: 0.25.0 - react@18.3.1: - dependencies: - loose-envify: 1.4.0 + react@19.0.0: {} read-yaml-file@1.1.0: dependencies: @@ -2479,84 +2389,101 @@ snapshots: pify: 4.0.1 strip-bom: 3.0.0 - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 + readdirp@4.1.1: {} regenerator-runtime@0.14.1: {} resolve-from@5.0.0: {} - resolve-pkg-maps@1.0.0: {} - reusify@1.0.4: {} - rollup@4.18.0: + rollup@4.34.4: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.18.0 - '@rollup/rollup-android-arm64': 4.18.0 - '@rollup/rollup-darwin-arm64': 4.18.0 - '@rollup/rollup-darwin-x64': 4.18.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.18.0 - '@rollup/rollup-linux-arm-musleabihf': 4.18.0 - '@rollup/rollup-linux-arm64-gnu': 4.18.0 - '@rollup/rollup-linux-arm64-musl': 4.18.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.18.0 - '@rollup/rollup-linux-riscv64-gnu': 4.18.0 - '@rollup/rollup-linux-s390x-gnu': 4.18.0 - '@rollup/rollup-linux-x64-gnu': 4.18.0 - '@rollup/rollup-linux-x64-musl': 4.18.0 - '@rollup/rollup-win32-arm64-msvc': 4.18.0 - '@rollup/rollup-win32-ia32-msvc': 4.18.0 - '@rollup/rollup-win32-x64-msvc': 4.18.0 + '@rollup/rollup-android-arm-eabi': 4.34.4 + '@rollup/rollup-android-arm64': 4.34.4 + '@rollup/rollup-darwin-arm64': 4.34.4 + '@rollup/rollup-darwin-x64': 4.34.4 + '@rollup/rollup-freebsd-arm64': 4.34.4 + '@rollup/rollup-freebsd-x64': 4.34.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.34.4 + '@rollup/rollup-linux-arm-musleabihf': 4.34.4 + '@rollup/rollup-linux-arm64-gnu': 4.34.4 + '@rollup/rollup-linux-arm64-musl': 4.34.4 + '@rollup/rollup-linux-loongarch64-gnu': 4.34.4 + '@rollup/rollup-linux-powerpc64le-gnu': 4.34.4 + '@rollup/rollup-linux-riscv64-gnu': 4.34.4 + '@rollup/rollup-linux-s390x-gnu': 4.34.4 + '@rollup/rollup-linux-x64-gnu': 4.34.4 + '@rollup/rollup-linux-x64-musl': 4.34.4 + '@rollup/rollup-win32-arm64-msvc': 4.34.4 + '@rollup/rollup-win32-ia32-msvc': 4.34.4 + '@rollup/rollup-win32-x64-msvc': 4.34.4 fsevents: 2.3.3 run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - rxjs@7.8.1: - dependencies: - tslib: 2.6.3 - safer-buffer@2.1.2: {} - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 + scheduler@0.25.0: {} - semver@7.6.2: {} + semver@7.7.1: {} - shebang-command@1.2.0: + sharp@0.33.5: dependencies: - shebang-regex: 1.0.0 + color: 4.2.3 + detect-libc: 2.0.3 + semver: 7.7.1 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + optional: true shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 - shebang-regex@1.0.0: {} - shebang-regex@3.0.0: {} - signal-exit@3.0.7: {} - signal-exit@4.1.0: {} + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + optional: true + slash@3.0.0: {} - source-map-js@1.2.0: {} + source-map-js@1.2.1: {} source-map@0.8.0-beta.0: dependencies: whatwg-url: 7.1.0 - spawndamnit@2.0.0: + spawndamnit@3.0.1: dependencies: - cross-spawn: 5.1.0 - signal-exit: 3.0.7 + cross-spawn: 7.0.6 + signal-exit: 4.1.0 sprintf-js@1.0.3: {} @@ -2580,35 +2507,25 @@ snapshots: strip-ansi@7.1.0: dependencies: - ansi-regex: 6.0.1 + ansi-regex: 6.1.0 strip-bom@3.0.0: {} - strip-final-newline@2.0.0: {} - - styled-jsx@5.1.1(react@18.3.1): + styled-jsx@5.1.6(react@19.0.0): dependencies: client-only: 0.0.1 - react: 18.3.1 + react: 19.0.0 sucrase@3.35.0: dependencies: - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.8 commander: 4.1.1 - glob: 10.4.2 + glob: 10.4.5 lines-and-columns: 1.2.4 mz: 2.7.0 pirates: 4.0.6 ts-interface-checker: 0.1.13 - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - term-size@2.2.1: {} thenify-all@1.6.0: @@ -2619,16 +2536,23 @@ snapshots: dependencies: any-promise: 1.3.0 + tinyexec@0.3.2: {} + + tinyglobby@0.2.10: + dependencies: + fdir: 6.4.3(picomatch@4.0.2) + picomatch: 4.0.2 + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 - to-fast-properties@2.0.0: {} - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + tr46@0.0.3: {} + tr46@1.0.1: dependencies: punycode: 2.3.1 @@ -2637,95 +2561,60 @@ snapshots: ts-interface-checker@0.1.13: {} - tslib@2.6.3: {} + tslib@2.8.1: {} - tsup@8.1.0(postcss@8.4.39)(typescript@5.5.2): + tsup@8.3.6(postcss@8.4.31)(typescript@5.7.3): dependencies: - bundle-require: 4.2.1(esbuild@0.21.5) + bundle-require: 5.1.0(esbuild@0.24.2) cac: 6.7.14 - chokidar: 3.6.0 - debug: 4.3.5 - esbuild: 0.21.5 - execa: 5.1.1 - globby: 11.1.0 + chokidar: 4.0.3 + consola: 3.4.0 + debug: 4.4.0 + esbuild: 0.24.2 joycon: 3.1.1 - postcss-load-config: 4.0.2(postcss@8.4.39) + picocolors: 1.1.1 + postcss-load-config: 6.0.1(postcss@8.4.31) resolve-from: 5.0.0 - rollup: 4.18.0 + rollup: 4.34.4 source-map: 0.8.0-beta.0 sucrase: 3.35.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.10 tree-kill: 1.2.2 optionalDependencies: - postcss: 8.4.39 - typescript: 5.5.2 + postcss: 8.4.31 + typescript: 5.7.3 transitivePeerDependencies: + - jiti - supports-color - - ts-node - - tsx@4.16.0: - dependencies: - esbuild: 0.21.5 - get-tsconfig: 4.7.5 - optionalDependencies: - fsevents: 2.3.3 - - turbo-darwin-64@2.0.6: - optional: true - - turbo-darwin-arm64@2.0.6: - optional: true - - turbo-linux-64@2.0.6: - optional: true - - turbo-linux-arm64@2.0.6: - optional: true - - turbo-windows-64@2.0.6: - optional: true - - turbo-windows-arm64@2.0.6: - optional: true - - turbo@2.0.6: - optionalDependencies: - turbo-darwin-64: 2.0.6 - turbo-darwin-arm64: 2.0.6 - turbo-linux-64: 2.0.6 - turbo-linux-arm64: 2.0.6 - turbo-windows-64: 2.0.6 - turbo-windows-arm64: 2.0.6 - - type-fest@0.21.3: {} + - tsx + - yaml - typescript@5.5.2: {} + typescript@5.7.3: {} - undici-types@5.26.5: {} + undici-types@6.20.0: {} universalify@0.1.2: {} + webidl-conversions@3.0.1: {} + webidl-conversions@4.0.2: {} + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + whatwg-url@7.1.0: dependencies: lodash.sortby: 4.7.0 tr46: 1.0.1 webidl-conversions: 4.0.2 - which@1.3.1: - dependencies: - isexe: 2.0.0 - which@2.0.2: dependencies: isexe: 2.0.0 - wrap-ansi@6.2.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -2738,10 +2627,4 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 - ws@8.17.1: {} - - yallist@2.1.2: {} - - yaml@2.4.5: {} - - yoctocolors-cjs@2.1.1: {} + ws@8.18.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index dee51e9..19b49ed 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,3 @@ packages: - - "packages/*" + - "." + - "examples/*" diff --git a/readme.md b/readme.md deleted file mode 100644 index bb82c61..0000000 --- a/readme.md +++ /dev/null @@ -1,246 +0,0 @@ -
-

Next WS

- Add support for WebSockets in Next.js app directory
- npm install next-ws ws -
- -
- package version - total downloads -
- next-ws repo stars - apteryxxyz followers - discord shield -
- -## 🤔 About - -Next WS (`next-ws`) is an advanced Next.js plugin that seamlessly integrates WebSocket server capabilities directly into routes located in the **app directory**. With Next WS, you no longer require a separate server for WebSocket functionality. - -> [!IMPORTANT] -> Next WS is designed for use in server-based environments. It is not suitable for serverless platforms like Vercel, where WebSocket servers are not supported. Furthermore, this plugin is built for the app directory and does not support the older pages directory. - -This module is inspired by the now outdated `next-plugin-websocket`, if you are using an older version of Next.js, that module may work for you. - -## 🏓 Table of Contents - -- [📦 Installation](#-installation) -- [🚀 Usage](#-usage) -- [🌀 Examples](#-examples) - - [Creating a Socket](#creating-a-socket) - - [Using a Custom Server](#using-a-custom-server) - - [Accessing the WebSocket Server](#accessing-the-websocket-server) - - [Client-Side Utilities](#client-side-utilities) - -## 📦 Installation - -Setting up a WebSocket server with Next WS involves patching your local Next.js installation. Next WS simplifies this process with a CLI command that automatically detects and patches your Next.js version, ensuring compatibility. Note that Next.js version 13.1.1 or higher is required. - -```sh -npx next-ws-cli@latest patch -``` - -> [!NOTE] -> If at any point your local Next.js installation is changed or updated you will need to re-run the patch command. - -After successfully patching Next.js, install the Next WS package along with its peer dependency, ws, into your project: - -```sh -npm install next-ws ws -``` - -## 🚀 Usage - -Using WebSocket functionality in your Next.js application with Next WS is simple and requires no additional configuration. Simply export a `SOCKET` function from any route file. This function will be invoked whenever a client establishes a WebSocket connection to that specific route. - -The `SOCKET` function receives three arguments: the WebSocket client instance, the incoming HTTP request - which you can use to get the URL path, query parameters, and headers - and the WebSocket server instance. - -```ts -export function SOCKET( - client: import('ws').WebSocket, - request: import('http').IncomingMessage, - server: import('ws').WebSocketServer -) { - // ... -} -``` - -### With a Custom Server - -In production, Next.js uses a worker process for routes, which can make it difficult to access the WebSocket server from outside a `SOCKET` handler, especially when the WebSocket server exists on the main process. For those needing to overcome this challenge or preferring a custom server setup, Next WS provides a solution. - -The `next-ws/server` module offers functions for setting the HTTP and WebSocket servers. You use these functions to tell Next WS to use your server instances instead of creating its own. This allows you to then access the instances you created yourself from anywhere in your app. Refer to the [example below](#using-a-custom-server). - -## 🌀 Examples - -For more detailed examples, refer the [`examples` directory](https://github.com/apteryxxyz/next-ws/tree/main/examples). - -### Creating a Socket - -Creating an API route anywhere within the app directory and exporting a `SOCKET` function from it is all that is required. Below is an example of a simple echo server, which sends back any message it receives. - -```ts -// app/api/ws/route.ts (can be any route file in the app directory) - -export function SOCKET( - client: import('ws').WebSocket, - request: import('http').IncomingMessage, - server: import('ws').WebSocketServer -) { - console.log('A client connected'); - - client.on('message', (message) => { - console.log('Received message:', message); - client.send(message); - }); - - client.on('close', () => { - console.log('A client disconnected'); - }); -} -``` - -### Using a Custom Server - -> [!IMPORTANT] -> Next WS was made to avoid the need for a custom server, if you are using one, you don't need this package and can just use a websocket server directly. - -To use a custom server, all you need to do is tell Next WS to use your server instead of creating its own. This can be done by calling the `setHttpServer` and `setWebSocketServer` functions from `next-ws/server` and passing your server instances. - -```ts -// server.js - -const { setHttpServer, setWebSocketServer } = require('next-ws/server'); -const { Server } = require('node:http'); -const { parse } = require('node:url'); -const next = require('next'); -const { WebSocketServer } = require('ws'); - -const dev = process.env.NODE_ENV !== 'production'; -const hostname = 'localhost'; -const port = 3000; - -const httpServer = new Server(); -const webSocketServer = new WebSocketServer({ noServer: true }); -// Tell Next WS about the HTTP and WebSocket servers before starting the custom server -setHttpServer(httpServer); -setWebSocketServer(webSocketServer); - -const app = next({ dev, hostname, port, customServer: true }); -const handle = app.getRequestHandler(); - -app.prepare().then(() => { - httpServer - .on('request', async (req, res) => { - const parsedUrl = parse(req.url, true); - await handle(req, res, parsedUrl); - }) - .listen(port, () => { - console.log(` ▲ Ready on http://${hostname}:${port}`); - }); -}); -``` - -### Accessing the WebSocket Server - -Along with setters, Next WS also provides getters for the HTTP and WebSocket servers. These functions can be used to access the servers from anywhere in your app. - -> [!IMPORTANT] -> In order to use the `getWebSocketServer` and `getHttpServer` functions, you must be using a [custom server](https://nextjs.org/docs/advanced-features/custom-server), this is due to a limitation in Next.js. Refer to the [With a Custom Server](#with-a-custom-server). - -```ts -// app/api/stats/route.ts - -import { getWebSocketServer } from 'next-ws/server'; -// There is also a `getHttpServer` function available - -export function GET() { - const wsServer = getWebSocketServer(); - // Response with the number of connected clients - return Response.json({ count: wsServer.clients.size }); -} -``` - -### Client-Side Utilities - -To make it easier to connect to your new WebSocket server, Next WS also provides some client-side utilities. These are completely optional, you can use your own implementation if you wish. - -```tsx -// layout.tsx -'use client'; - -import { WebSocketProvider } from 'next-ws/client'; - -export default function Layout({ children }: React.PropsWithChildren) { - return ( - - - - {children} - - - - ); -} -``` - -The following is the props interface for the `WebSocketProvider` component, containing all the available options. - -```ts -interface WebSocketProviderProps { - children: React.ReactNode; - - /** The URL for the WebSocket to connect to. */ - url: string; - /** The subprotocols to use. */ - protocols?: string[] | string; - /** The binary type to use. */ - binaryType?: BinaryType; -} -``` - -Now you can use the `useWebSocket` hook to get the WebSocket instance, and send and receive messages. - -```tsx -// page.tsx -'use client'; - -import { useCallback, useEffect, useRef, useState } from 'react'; -import { useWebSocket } from 'next-ws/client'; - -export default function Page() { - const ws = useWebSocket(); - // ^? WebSocket on the client, null on the server - - const inputRef = useRef(null); - const [message, setMessage] = useState(null); - - useEffect(() => { - async function onMessage(event: MessageEvent) { - const payload = - typeof event.data === 'string' ? event.data : await event.data.text(); - const message = JSON.parse(payload) as Message; - setMessages((p) => [...p, message]); - } - - ws?.addEventListener('message', onMessage); - return () => ws?.removeEventListener('message', onMessage); - }, [ws]); - - return ( - <> - - - - -

- {message === null - ? 'Waiting to receive message...' - : `Got message: ${message}`} -

- - ); -} -``` diff --git a/packages/cli/src/program.ts b/src/cli.ts similarity index 80% rename from packages/cli/src/program.ts rename to src/cli.ts index 6fefcf8..be2b3c5 100644 --- a/packages/cli/src/program.ts +++ b/src/cli.ts @@ -6,11 +6,7 @@ import verifyCommand from './commands/verify'; program .name('next-ws') - .description('Patch the local Next.js installation to support WebSockets') + .description('Patch the local Next.js installation to support WebSockets.') .addCommand(patchCommand) .addCommand(verifyCommand) .parse(); - -if (!process.argv.slice(2).length) { - program.outputHelp(); -} diff --git a/packages/core/src/client/context.tsx b/src/client/context.tsx similarity index 70% rename from packages/core/src/client/context.tsx rename to src/client/context.tsx index 87235dd..955fa16 100644 --- a/packages/core/src/client/context.tsx +++ b/src/client/context.tsx @@ -1,8 +1,8 @@ 'use client'; -// biome-ignore lint/style/useImportType: -import React from 'react'; -import { createContext, useContext, useEffect, useMemo } from 'react'; +// biome-ignore lint/style/useImportType: Actually need the value for JSX +import React, { useRef } from 'react'; +import { createContext, useContext, useEffect } from 'react'; export const WebSocketContext = createContext(null); WebSocketContext.displayName = 'WebSocketContext'; @@ -24,20 +24,27 @@ export function WebSocketProvider( binaryType?: BinaryType; }>, ) { - const client = useMemo(() => { - if (typeof window === 'undefined') return null; + const clientRef = useRef(null); + useEffect(() => { + if (typeof window === 'undefined') return; + + if (clientRef.current) { + clientRef.current.close(); + clientRef.current = null; + } + const client = new WebSocket(p.url, p.protocols); if (p.binaryType) client.binaryType = p.binaryType; - return client; - }, [p.url, p.protocols, p.binaryType]); + clientRef.current = client; - useEffect(() => { - if (client?.readyState !== WebSocket.OPEN) return; - return () => client.close(); - }, [client]); + return () => { + client.close(); + clientRef.current = null; + }; + }, [p.url, p.protocols, p.binaryType]); return ( - + {p.children} ); diff --git a/packages/core/src/client/index.ts b/src/client/index.ts similarity index 100% rename from packages/core/src/client/index.ts rename to src/client/index.ts diff --git a/src/commands/helpers/logger.ts b/src/commands/helpers/logger.ts new file mode 100644 index 0000000..c71743f --- /dev/null +++ b/src/commands/helpers/logger.ts @@ -0,0 +1,106 @@ +import readline from 'node:readline'; +import chalk from 'chalk'; + +// Helper functions + +const prefix = (message: string, colour = chalk.cyan) => + `${colour('[next-ws]')} ${message}`; +const line = (message: string) => { + // @ts-ignore + if (message instanceof Error) return message; + return String(message).replaceAll(/(?:\n\s*)+/g, ' '); +}; + +// Logging message builders + +const log = (message: string) => prefix(line(message)); +const info = (message: string) => prefix(line(message), chalk.cyan); +const warn = (message: string) => prefix(line(message), chalk.yellow); +const error = (message: string) => prefix(line(message), chalk.red); + +// Task message builders + +const loading = (symbol: string, message: string) => + prefix(`${chalk.cyan(symbol)} ${line(message)}`, chalk.cyan); +const success = (message: string) => + prefix(`${chalk.green('✔')} ${line(message)}`, chalk.green); +const failure = (message: string) => + prefix(`${chalk.red('✖')} ${line(message)}`, chalk.red); + +// Make logging functions + +const make = + ( + type: 'log' | 'info' | 'warn' | 'error', + modifier: (message: string) => string, + ) => + (message: string) => + console[type](modifier(message)); + +// Inquirer confirm + +async function confirm(message = 'Continue?', defaultChoice = false) { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + return new Promise((resolve) => { + const question = `${prefix(line(message), chalk.yellow)}`; + const options = `[${defaultChoice ? 'Y/n' : 'y/N'}]`; + + rl.question(`${question} ${chalk.cyan(options)} `, (answer) => { + const normalisedAnswer = answer.trim().toLowerCase(); + if (normalisedAnswer === '') resolve(defaultChoice); + else if (normalisedAnswer === 'y') resolve(true); + else resolve(false); + rl.close(); + }); + }); +} + +// Task runner with spinner + +async function task(title: string, promise: Promise) { + // Hide the cursor + process.stdout.write('\x1B[?25l'); + + const spinnerChars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧']; + let spinnerIndex = 0; + const spinnerInterval = setInterval(() => { + readline.cursorTo(process.stdout, 0); + const spinnerChar = spinnerChars[spinnerIndex++ % spinnerChars.length]!; + process.stdout.write(loading(spinnerChar, title)); + }, 100); + + return promise + .then((value) => { + clearInterval(spinnerInterval); + readline.cursorTo(process.stdout, 0); + readline.clearLine(process.stdout, 0); + console.info(success(title)); + return value; + }) + .catch((err) => { + clearInterval(spinnerInterval); + readline.cursorTo(process.stdout, 0); + readline.clearLine(process.stdout, 0); + console.error(failure(title)); + throw err; + }) + .finally(() => { + // Show the cursor + process.stdout.write('\x1B[?25h'); + }); +} + +// Logger + +export default { + log: make('log', log), + info: make('info', info), + warn: make('warn', warn), + error: make('error', error), + confirm, + task, +}; diff --git a/src/commands/helpers/semver.ts b/src/commands/helpers/semver.ts new file mode 100644 index 0000000..4c9486c --- /dev/null +++ b/src/commands/helpers/semver.ts @@ -0,0 +1,41 @@ +import type { Options } from 'semver'; +import Range from 'semver/classes/range'; +import SemVer from 'semver/classes/semver'; +import gt from 'semver/functions/gt'; + +/** + * Get the maximum version from a range + * @param range Range or string + * @param loose Options or boolean + * @returns Maximum version + */ +export function maxVersion(range: Range | string, loose?: Options | boolean) { + range = new Range(range, loose); + let maximumVersion: SemVer | null = null; + + for (const comparators of range.set) { + for (const { + operator, + semver: { version: version_ }, + } of comparators) { + if (operator === '>' || operator === '>=') continue; + + const version = new SemVer(version_); + + if (operator === '<') { + version.patch--; + version.raw = version.format(); + } + + if (!maximumVersion || gt(version, maximumVersion)) + maximumVersion = version; + } + } + + return maximumVersion; +} + +export { default as minVersion } from 'semver/ranges/min-version'; +export { default as ltr } from 'semver/ranges/ltr'; +export { default as gtr } from 'semver/ranges/gtr'; +export { default as satisfies } from 'semver/functions/satisfies'; diff --git a/src/commands/patch.ts b/src/commands/patch.ts new file mode 100644 index 0000000..563d9d9 --- /dev/null +++ b/src/commands/patch.ts @@ -0,0 +1,55 @@ +import { Command } from 'commander'; +import logger from '~/commands/helpers/logger'; +import * as semver from '~/commands/helpers/semver'; +import patches from '~/patches'; +import { getNextVersion, setTrace } from '~/patches/helpers/next'; + +export default new Command('patch') + .description('Patch the local Next.js installation to support WebSockets') + .option('-y, --yes', 'Skip confirmation prompt for unsupported versions') + .action(async (options: { yes: boolean }) => { + const supported = patches.map((p) => p.versions).join(' || '); + const minimum = semver.minVersion(supported)?.version ?? supported; + const maximum = semver.maxVersion(supported)?.version ?? supported; + const current = await getNextVersion(); + + if (semver.ltr(current, minimum)) { + // The installed version is lower than the minimum supported version + logger.error(`Next.js v${current} is not supported, + a minimum of v${minimum} is required`); + process.exit(1); + } + + let patch = patches.find((p) => semver.satisfies(current, p.versions)); + if (semver.gtr(current, maximum)) { + // The installed version is higher than the maximum supported version + logger.warn(`Next.js v${current} is not officially supported, + a maximum of v${maximum} is recommended. + Are you sure you want to proceed?`); + const confirm = options.yes || (await logger.confirm()); + + if (confirm) { + patch = patches[patches.length - 1]; + logger.info('Proceeding with the latest patch'); + logger.log(`If you encounter any issues please report them at + https://github.com/apteryxxyz/next-ws/issues`); + } else { + logger.error('Aborted'); + process.exit(1); + } + } + + if (!patch) { + logger.error(`Next.js v${current} is not supported, + please upgrade to a version within the range '${supported}'`); + process.exit(1); + } + + logger.info(`Patching Next.js v${current} with '${patch.versions}'`); + await patch.execute(); + + logger.info('Saving patch trace file...'); + setTrace({ patch: patch.versions, version: current }); + + logger.info('All done!'); + }); diff --git a/src/commands/verify.ts b/src/commands/verify.ts new file mode 100644 index 0000000..9a39748 --- /dev/null +++ b/src/commands/verify.ts @@ -0,0 +1,32 @@ +import { Command } from 'commander'; +import { getNextVersion, getTrace } from '~/patches/helpers/next'; +import logger from './helpers/logger'; +import patchCommand from './patch'; + +export default new Command('verify') + .description('Verify that the local Next.js installation has been patched') + .option('-e, --ensure', 'If not patched, then run the patch command') + .action(async (options: { ensure: boolean }) => { + const trace = await getTrace(); + if (!trace) { + if (options.ensure) { + logger.warn(`Next.js has not been patched, + running the patch command`); + const action = Reflect.get(patchCommand, '_actionHandler'); + return action(['-y']); + } else { + logger.error(`Next.js has not been patched, + you'll need to run the patch command`); + process.exit(1); + } + } + + const current = await getNextVersion(); + if (current !== trace.version) { + logger.error(`Next.js has been patched with a different version, + you'll need to run the patch command`); + process.exit(1); + } + + logger.info('Next.js is patched'); + }); diff --git a/src/patches/helpers/next.ts b/src/patches/helpers/next.ts new file mode 100644 index 0000000..7417803 --- /dev/null +++ b/src/patches/helpers/next.ts @@ -0,0 +1,57 @@ +import { readFile, writeFile } from 'node:fs/promises'; +import { dirname as resolveDirname, join as resolveJoin } from 'node:path'; + +/** + * Get the dist dirname of this package. + * @returns The dist dirname of this package + */ +export function getDistDirname() { + const resolveOptions = { paths: [process.cwd()] }; + const nextWsPackagePath = // + require.resolve('next-ws/package.json', resolveOptions); + const nextWsDirName = resolveDirname(nextWsPackagePath); + return `${nextWsDirName.replaceAll('\\', '/')}/dist`; +} + +/** + * Find the Next.js installation directory. + * @returns The Next.js installation directory + */ +export async function findNextDirectory() { + const resolveOptions = { paths: [process.cwd()] }; + const nextPackagePath = require.resolve('next/package.json', resolveOptions); + return resolveDirname(nextPackagePath); +} + +/** + * Get the version of Next.js from the installation directory's `package.json`. + * @returns The version of Next.js + */ +export async function getNextVersion() { + const nextDirectory = await findNextDirectory(); + const nextPackagePath = resolveJoin(nextDirectory, 'package.json'); + const nextPackage = await readFile(nextPackagePath, 'utf-8').then(JSON.parse); + return String(nextPackage.version.split('-')[0]); +} + +/** + * Get the next-ws trace of the current installation of Next.js. + * @returns Trace object + */ +export async function getTrace() { + const nextDirectory = await findNextDirectory(); + const tracePath = resolveJoin(nextDirectory, '.next-ws-trace.json'); + return readFile(tracePath, 'utf-8') + .then[0]>(JSON.parse) + .catch(() => null); +} + +/** + * Set the next-ws trace of the current installation of Next.js. + * @param trace Trace object + */ +export async function setTrace(trace: { patch: string; version: string }) { + const nextDirectory = await findNextDirectory(); + const tracePath = resolveJoin(nextDirectory, '.next-ws-trace.json'); + await writeFile(tracePath, JSON.stringify(trace, null, 2)); +} diff --git a/src/patches/helpers/patch.ts b/src/patches/helpers/patch.ts new file mode 100644 index 0000000..e8b2183 --- /dev/null +++ b/src/patches/helpers/patch.ts @@ -0,0 +1,68 @@ +import { readFile, writeFile } from 'node:fs/promises'; +import { join as joinPath } from 'node:path'; +import logger from '~/commands/helpers/logger'; +import { findNextDirectory } from '~/patches/helpers/next'; + +/** + * Patches allow prepending short-hands to paths, this will resolve them. + * @param path The path to resolve + * @returns The resolved path + */ +async function resolvePath(path: string) { + switch (path.split(':')[0]) { + case 'next': { + const nextDirectory = await findNextDirectory(); + const realPath = path.slice(5); + return joinPath(nextDirectory, realPath); + } + default: { + return path; + } + } +} + +/** + * Create a step for a patch. + * @param options The options for the step + * @returns The step + */ +export function createPatchStep(options: { + title: string; + path: `next:${string}`; + ignoreIf?: string; + modify(source: string): string | Promise; +}) { + return { + title: options.title, + modify: options.modify, + async execute() { + const path = await resolvePath(options.path); + const source = await readFile(path, 'utf-8'); + if (options.ignoreIf && source.includes(options.ignoreIf)) return false; + const newSource = await options.modify(source); + await writeFile(path, newSource); + return true; + }, + }; +} + +/** + * Create a patch. + * @param options The options for the patch + * @returns The patch + */ +export function createPatch(options: { + name: string; + // Due to the auto update github action, the typing of this needs to be strict + versions: `>=${string}.${string}.${string} <=${string}.${string}.${string}`; + steps: ReturnType[]; +}) { + return { + name: options.name, + versions: options.versions, + async execute() { + for (const step of options.steps) + await logger.task(step.title, step.execute()); + }, + }; +} diff --git a/src/patches/index.ts b/src/patches/index.ts new file mode 100644 index 0000000..69d9893 --- /dev/null +++ b/src/patches/index.ts @@ -0,0 +1,4 @@ +import patch1 from './patch-1'; +import patch2 from './patch-2'; + +export default [patch1, patch2] as const; diff --git a/src/patches/patch-1.ts b/src/patches/patch-1.ts new file mode 100644 index 0000000..fb674ce --- /dev/null +++ b/src/patches/patch-1.ts @@ -0,0 +1,79 @@ +import { createPatch, createPatchStep } from '~/patches/helpers/patch'; +import { getDistDirname } from './helpers/next'; + +/** + * Add `require('next-ws/server').setupWebSocketServer(this)` to the constructor of + * `NextNodeServer` in `next/dist/server/next-server.js`. + * @remark Starting the server and handling connections is part of the core + */ +export const patchNextNodeServer = createPatchStep({ + title: 'Add WebSocket server setup script to NextNodeServer constructor.', + path: 'next:dist/server/next-server.js', + ignoreIf: 'setupWebSocketServer(this)', + modify(source) { + const sourceLines = source.split('\n'); + + let inConstructor = false; + let constructorEndIndex = -1; + let braceCount = 0; + + for (let i = 0; i < sourceLines.length; i++) { + const line = sourceLines[i] ?? ''; + + if (!inConstructor && line.includes('constructor(')) { + inConstructor = true; + braceCount = 0; + } + + if (inConstructor) { + braceCount += (line.match(/{/g) || []).length; + braceCount -= (line.match(/}/g) || []).length; + if (braceCount === 0) { + constructorEndIndex = i; + break; + } + } + } + + if (constructorEndIndex === -1) + throw new Error('Could not find constructor end index.'); + + // Package manager weirdness, + const setupScript = ` +;{ + let nextWs; + try { nextWs = require('next-ws/server') } catch { + try { nextWs = require('${getDistDirname()}/server/index.cjs') } catch { + /* don't let this crash apps that don't use next-ws */ }} + nextWs?.setupWebSocketServer(this); +}; + `; + sourceLines.splice(constructorEndIndex, 0, setupScript); + const newSource = sourceLines.join('\n'); + return newSource; + }, +}); + +/** + * Add `SOCKET?: Function` to the page module interface check field thing in + * `next/dist/build/webpack/plugins/next-types-plugin.js`. + */ +export const patchNextTypesPlugin = createPatchStep({ + title: 'Add SOCKET type to Next types', + path: 'next:dist/build/webpack/plugins/next-types-plugin.js', + ignoreIf: 'SOCKET?: Function;', + modify(source) { + const mapRegex = + /\.map\(\(method\)=>`\${method}\?: Function`\).join\(['"]\\n +['"]\)/g; + const newSource = source.replace(mapRegex, (match) => { + return `${match} + "; SOCKET?: Function;"`; + }); + return newSource; + }, +}); + +export default createPatch({ + name: 'patch-1', + versions: '>=13.2.0 <=13.4.8', + steps: [patchNextNodeServer, patchNextTypesPlugin], +}); diff --git a/src/patches/patch-2.ts b/src/patches/patch-2.ts new file mode 100644 index 0000000..8104480 --- /dev/null +++ b/src/patches/patch-2.ts @@ -0,0 +1,37 @@ +import { createPatch, createPatchStep } from '~/patches/helpers/patch'; +import { + patchNextNodeServer as p1_patchNextNodeServer, + patchNextTypesPlugin as p1_patchNextTypesPlugin, +} from './patch-1'; + +/** + * Add `SOCKET?: Function` to the page module interface check field thing in + * `next/dist/build/webpack/plugins/next-types-plugin.js`. + * @remark The file for `next-types-plugin` was moved in 13.4.9 + */ +export const patchNextTypesPlugin = createPatchStep({ + ...p1_patchNextTypesPlugin, + path: 'next:dist/build/webpack/plugins/next-types-plugin/index.js', + ignoreIf: 'SOCKET?: Function', +}); + +/** + * Prevent Next.js from immediately closing WebSocket connections on matched routes. + * @remark This patch is only necessary for Next.js versions greater than 13.5.1 + */ +export const patchRouterServer = createPatchStep({ + title: 'Prevent Next.js from immediately closing WebSocket connections', + path: 'next:dist/server/lib/router-server.js', + modify(source) { + const newSource = source + .replace('return socket.end();', '') + .replace(/(\/\/ [a-zA-Z .]+\s+)socket\.end\(\);/, ''); + return newSource; + }, +}); + +export default createPatch({ + name: 'patch-3', + versions: '>=13.5.1 <=15.1.6', + steps: [p1_patchNextNodeServer, patchRouterServer, patchNextTypesPlugin], +}); diff --git a/packages/core/src/server/helpers/persistent.ts b/src/server/helpers/persistent.ts similarity index 93% rename from packages/core/src/server/helpers/persistent.ts rename to src/server/helpers/persistent.ts index ead392d..add4eee 100644 --- a/packages/core/src/server/helpers/persistent.ts +++ b/src/server/helpers/persistent.ts @@ -1,4 +1,4 @@ -import * as logger from 'next/dist/build/output/log'; +import * as logger from 'next/dist/build/output/log.js'; /** * Get the environment metadata. @@ -11,6 +11,11 @@ function getEnvironmentMeta() { return { isCustomServer, isMainProcess, isDevelopment }; } +/** + * Ensure that the current process is the main process. + * @param fnName The name of the function that is being invoked for logging purposes. + * @throws If the current process is not the main process. + */ function mainProcessOnly(fnName: string) { if (process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK === '1') return; diff --git a/packages/core/src/server/helpers/route.ts b/src/server/helpers/route.ts similarity index 59% rename from packages/core/src/server/helpers/route.ts rename to src/server/helpers/route.ts index 0d98608..1c32339 100644 --- a/packages/core/src/server/helpers/route.ts +++ b/src/server/helpers/route.ts @@ -1,7 +1,13 @@ -import * as logger from 'next/dist/build/output/log'; -import type NextNodeServer from 'next/dist/server/next-server'; +import { pathToFileURL } from 'node:url'; +import * as logger from 'next/dist/build/output/log.js'; +import type NextNodeServer from 'next/dist/server/next-server.js'; import type { SocketHandler } from './socket'; +/** + * Create a regular expression from a route pattern. + * @param routePattern The route pattern to use + * @returns The regular expression + */ function createRouteRegex(routePattern: string) { const escapedPattern = routePattern.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); const paramRegex = escapedPattern @@ -10,6 +16,12 @@ function createRouteRegex(routePattern: string) { return new RegExp(`^${paramRegex}$`); } +/** + * Get the route parameters from a route pattern and a request path. + * @param routePattern The route pattern to use + * @param routePath The request path to get from + * @returns The route parameters + */ function getRouteParams(routePattern: string, routePath: string) { const routeRegex = createRouteRegex(routePattern); const match = routePath.match(routeRegex); @@ -18,7 +30,10 @@ function getRouteParams(routePattern: string, routePath: string) { const params: Record = {}; for (let [k, v] of Object.entries(match.groups)) { - if (k.startsWith('rest_')) (k = k.slice(5)), (v = v.split('/') as never); + if (k.startsWith('rest_')) { + k = k.slice(5); + v = v.split('/') as never; + } Reflect.set(params, k, v); } return params; @@ -26,9 +41,9 @@ function getRouteParams(routePattern: string, routePath: string) { /** * Resolve a request path to a route file path and route parameters. - * @param nextServer The NextNodeServer instance. - * @param requestPath The request path to resolve for. - * @returns The resolved file path and route parameters, or null if the route could not be resolved. + * @param nextServer The NextNodeServer instance + * @param requestPath The request path to resolve for + * @returns The resolved file path and route parameters, or null if the route could not be resolved */ export function resolvePathToRoute( nextServer: NextNodeServer, @@ -51,9 +66,9 @@ export function resolvePathToRoute( /** * Import the route module for a route. - * @param nextServer The NextNodeServer instance. - * @param filePath The file path of the route. - * @returns + * @param nextServer The NextNodeServer instance + * @param filePath The file path of the route + * @returns The imported route module */ export async function importRouteModule( nextServer: NextNodeServer, @@ -81,7 +96,34 @@ export async function importRouteModule( // @ts-expect-error - getPageModule is protected const buildPagePath = nextServer.getPagePath(filePath); - return require(buildPagePath) as RouteModule; + return importModule(buildPagePath); +} + +/** + * Import a module from a file path using either import or require. + * @param modulePath The file path of the module. + * @returns The imported module. + * @throws If the module could not be imported. + */ +async function importModule(modulePath: string): Promise { + const moduleUrl = pathToFileURL(modulePath).toString(); + + try { + return await import(moduleUrl).then((m) => m.default); + } catch (requireError) { + try { + return require(modulePath); + } catch (requireError) { + console.error(`Both import and require failed for ${modulePath}`); + throw requireError; + } + } +} + +export function getSocketHandler(routeModule: RouteModule) { + return ( + routeModule?.routeModule?.userland?.SOCKET ?? routeModule?.handlers?.SOCKET + ); } export interface RouteModule { @@ -90,4 +132,7 @@ export interface RouteModule { SOCKET?: SocketHandler; }; }; + handlers?: { + SOCKET?: SocketHandler; + }; } diff --git a/packages/core/src/server/helpers/socket.ts b/src/server/helpers/socket.ts similarity index 100% rename from packages/core/src/server/helpers/socket.ts rename to src/server/helpers/socket.ts diff --git a/src/server/index.ts b/src/server/index.ts new file mode 100644 index 0000000..7eeabb5 --- /dev/null +++ b/src/server/index.ts @@ -0,0 +1,7 @@ +export * from './setup'; +export { + setHttpServer, + getHttpServer, + setWebSocketServer, + getWebSocketServer, +} from './helpers/persistent'; diff --git a/packages/core/src/server/setup.ts b/src/server/setup.ts similarity index 75% rename from packages/core/src/server/setup.ts rename to src/server/setup.ts index 75bbcf5..2224f88 100644 --- a/packages/core/src/server/setup.ts +++ b/src/server/setup.ts @@ -1,8 +1,12 @@ -import * as logger from 'next/dist/build/output/log'; -import type NextNodeServer from 'next/dist/server/next-server'; +import * as logger from 'next/dist/build/output/log.js'; +import type NextNodeServer from 'next/dist/server/next-server.js'; import { WebSocketServer } from 'ws'; import { useHttpServer, useWebSocketServer } from './helpers/persistent'; -import { importRouteModule, resolvePathToRoute } from './helpers/route'; +import { + getSocketHandler, + importRouteModule, + resolvePathToRoute, +} from './helpers/route'; export function setupWebSocketServer(nextServer: NextNodeServer) { process.env.NEXT_WS_MAIN_PROCESS = String(1); @@ -11,8 +15,7 @@ export function setupWebSocketServer(nextServer: NextNodeServer) { // @ts-expect-error - serverOptions is protected const httpServer = useHttpServer(nextServer.serverOptions?.httpServer); const wsServer = useWebSocketServer(new WebSocketServer({ noServer: true })); - // biome-ignore lint/performance/noDelete: - delete process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK; + process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK = String(0); if (!httpServer) return logger.error('[next-ws] was not able to find the HTTP server'); @@ -38,7 +41,7 @@ export function setupWebSocketServer(nextServer: NextNodeServer) { return socket.destroy(); } - const socketHandler = routeModule?.routeModule?.userland?.SOCKET; + const socketHandler = getSocketHandler(routeModule); if (!socketHandler || typeof socketHandler !== 'function') { logger.error(`[next-ws] ${pathname} does not export a SOCKET handler`); return socket.destroy(); @@ -52,9 +55,3 @@ export function setupWebSocketServer(nextServer: NextNodeServer) { }); }); } - -// Next WS versions below 0.2.0 used a different method of setup -// This remains for backwards compatibility, but may be removed in a future version -export function hookNextNodeServer(this: NextNodeServer) { - setupWebSocketServer(this); -} diff --git a/tests/chat-room-with-custom-server.test.ts b/tests/chat-room-with-custom-server.test.ts new file mode 100644 index 0000000..ad7648b --- /dev/null +++ b/tests/chat-room-with-custom-server.test.ts @@ -0,0 +1,117 @@ +import { expect, test } from '@playwright/test'; +import type { Page } from '@playwright/test'; + +test.describe('Chat Room with Custom Server', () => { + test.use({ baseURL: 'http://localhost:3002' }); + + // biome-ignore lint/style/useSingleVarDeclarator: I do what I want + let page1: Page, page2: Page; + test.beforeEach(async ({ browser }) => { + page1 = await browser.newPage(); + const context2 = await browser.newContext({ locale: 'fr-FR' }); + page2 = await context2.newPage(); + }); + test.afterEach(async () => { + await page1.close(); + await page2.close(); + }); + + test('a user joins the chat and receives a welcome message', async () => { + await page1.goto('/chat/simple'); + + const welcome1 = await page1.textContent('li:first-child'); + expect(welcome1).toContain('Welcome to the chat!'); + expect(welcome1).toContain('There are no other users online'); + + await page2.goto('/chat/simple'); + + const welcome2 = await page2.textContent('li:first-child'); + expect(welcome2).toContain('Welcome to the chat!'); + expect(welcome2).toContain('There is 1 other user online'); + }); + + test('a new user joins the chat and all users receive a message', async () => { + await page1.goto('/chat/simple'); + await page2.goto('/chat/simple'); + + await page1.waitForTimeout(1000); // Can take a moment + const message1 = await page1.textContent('li:last-child'); + expect(message1).toContain('A new user joined the chat'); + }); + + test('a user sends a message and all users receive it', async () => { + await page1.goto('/chat/simple'); + await page2.goto('/chat/simple'); + + await page1.fill('input[name=author]', 'Alice'); + await page1.fill('input[name=content]', 'Hello, world!'); + await page1.click('button[type=submit]'); + + const message1 = await page1.textContent('li:last-child'); + expect(message1).toContain('You'); + expect(message1).toContain('Hello, world!'); + + const message2 = await page2.textContent('li:last-child'); + expect(message2).toContain('Alice'); + expect(message2).toContain('Hello, world!'); + }); +}); + +test.describe('Chat Room with Custom Server with Dynamic Socket Route', () => { + test.use({ baseURL: 'http://localhost:3002' }); + + // biome-ignore lint/style/useSingleVarDeclarator: I do what I want + let page1: Page, page2: Page; + test.beforeEach(async ({ browser }) => { + page1 = await browser.newPage(); + const context2 = await browser.newContext({ locale: 'fr-FR' }); + page2 = await context2.newPage(); + }); + test.afterEach(async () => { + await page1.close(); + await page2.close(); + }); + + test('a user joins the chat and receives a welcome message with their dynamic value', async () => { + await page1.goto('/chat/dynamic'); + + const welcome1 = await page1.textContent('li:first-child'); + expect(welcome1).toContain('Welcome to the chat!'); + expect(welcome1).toContain('Your language is en-US'); + expect(welcome1).toContain('There are no other users online'); + + await page2.goto('/chat/dynamic'); + + const welcome2 = await page2.textContent('li:first-child'); + expect(welcome2).toContain('Welcome to the chat!'); + expect(welcome2).toContain('Your language is fr-FR'); + expect(welcome2).toContain('There is 1 other user online'); + }); + + test('a new user joins the chat and all users receive a message with their dynamic value', async () => { + await page1.goto('/chat/dynamic'); + await page2.goto('/chat/dynamic'); + + await page1.waitForTimeout(1000); // Can take a moment + const message1 = await page1.textContent('li:last-child'); + expect(message1).toContain('A new user joined the chat'); + expect(message1).toContain('their language is fr-FR'); + }); + + test('a user sends a message and all users receive it', async () => { + await page1.goto('/chat/dynamic'); + await page2.goto('/chat/dynamic'); + + await page1.fill('input[name=author]', 'Alice'); + await page1.fill('input[name=content]', 'Hello, world!'); + await page1.click('button[type=submit]'); + + const message1 = await page1.textContent('li:last-child'); + expect(message1).toContain('You'); + expect(message1).toContain('Hello, world!'); + + const message2 = await page2.textContent('li:last-child'); + expect(message2).toContain('Alice'); + expect(message2).toContain('Hello, world!'); + }); +}); diff --git a/tests/chat-room.test.ts b/tests/chat-room.test.ts new file mode 100644 index 0000000..b03c948 --- /dev/null +++ b/tests/chat-room.test.ts @@ -0,0 +1,117 @@ +import { expect, test } from '@playwright/test'; +import type { Page } from '@playwright/test'; + +test.describe('Chat Room', () => { + test.use({ baseURL: 'http://localhost:3001' }); + + // biome-ignore lint/style/useSingleVarDeclarator: I do what I want + let page1: Page, page2: Page; + test.beforeEach(async ({ browser }) => { + page1 = await browser.newPage(); + const context2 = await browser.newContext({ locale: 'fr-FR' }); + page2 = await context2.newPage(); + }); + test.afterEach(async () => { + await page1.close(); + await page2.close(); + }); + + test('a user joins the chat and receives a welcome message', async () => { + await page1.goto('/chat/simple'); + + const welcome1 = await page1.textContent('li:first-child'); + expect(welcome1).toContain('Welcome to the chat!'); + expect(welcome1).toContain('There are no other users online'); + + await page2.goto('/chat/simple'); + + const welcome2 = await page2.textContent('li:first-child'); + expect(welcome2).toContain('Welcome to the chat!'); + expect(welcome2).toContain('There is 1 other user online'); + }); + + test('a new user joins the chat and all users receive a message', async () => { + await page1.goto('/chat/simple'); + await page2.goto('/chat/simple'); + + await page1.waitForTimeout(1000); // Can take a moment + const message1 = await page1.textContent('li:last-child'); + expect(message1).toContain('A new user joined the chat'); + }); + + test('a user sends a message and all users receive it', async () => { + await page1.goto('/chat/simple'); + await page2.goto('/chat/simple'); + + await page1.fill('input[name=author]', 'Alice'); + await page1.fill('input[name=content]', 'Hello, world!'); + await page1.click('button[type=submit]'); + + const message1 = await page1.textContent('li:last-child'); + expect(message1).toContain('You'); + expect(message1).toContain('Hello, world!'); + + const message2 = await page2.textContent('li:last-child'); + expect(message2).toContain('Alice'); + expect(message2).toContain('Hello, world!'); + }); +}); + +test.describe('Chat Room with Dynamic Socket Route', () => { + test.use({ baseURL: 'http://localhost:3001' }); + + // biome-ignore lint/style/useSingleVarDeclarator: I do what I want + let page1: Page, page2: Page; + test.beforeEach(async ({ browser }) => { + page1 = await browser.newPage(); + const context2 = await browser.newContext({ locale: 'fr-FR' }); + page2 = await context2.newPage(); + }); + test.afterEach(async () => { + await page1.close(); + await page2.close(); + }); + + test('a user joins the chat and receives a welcome message with their dynamic value', async () => { + await page1.goto('/chat/dynamic'); + + const welcome1 = await page1.textContent('li:first-child'); + expect(welcome1).toContain('Welcome to the chat!'); + expect(welcome1).toContain('Your language is en-US'); + expect(welcome1).toContain('There are no other users online'); + + await page2.goto('/chat/dynamic'); + + const welcome2 = await page2.textContent('li:first-child'); + expect(welcome2).toContain('Welcome to the chat!'); + expect(welcome2).toContain('Your language is fr-FR'); + expect(welcome2).toContain('There is 1 other user online'); + }); + + test('a new user joins the chat and all users receive a message with their dynamic value', async () => { + await page1.goto('/chat/dynamic'); + await page2.goto('/chat/dynamic'); + + await page1.waitForTimeout(1000); // Can take a moment + const message1 = await page1.textContent('li:last-child'); + expect(message1).toContain('A new user joined the chat'); + expect(message1).toContain('their language is fr-FR'); + }); + + test('a user sends a message and all users receive it', async () => { + await page1.goto('/chat/dynamic'); + await page2.goto('/chat/dynamic'); + + await page1.fill('input[name=author]', 'Alice'); + await page1.fill('input[name=content]', 'Hello, world!'); + await page1.click('button[type=submit]'); + + const message1 = await page1.textContent('li:last-child'); + expect(message1).toContain('You'); + expect(message1).toContain('Hello, world!'); + + const message2 = await page2.textContent('li:last-child'); + expect(message2).toContain('Alice'); + expect(message2).toContain('Hello, world!'); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index d8285fc..e53adc6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,8 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "include": ["${configDir}/src/**/*"], - "exclude": ["node_modules"], + "include": ["src/**/*", "tests/**/*"], + "exclude": ["node_modules", "dist"], "compileOnSave": true, "compilerOptions": { @@ -45,8 +45,9 @@ "useDefineForClassFields": true, "skipLibCheck": true, - "baseUrl": "${configDir}", - "outDir": "${configDir}/dist", - "paths": { "~/*": ["./src/*"] } + "outDir": "./dist", + "paths": { "~/*": ["./src/*"] }, + + "jsx": "react" } } diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..b094a5d --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig([ + { + entry: ['src/{client,server}/index.ts'], + format: ['cjs', 'esm'], + dts: true, + }, + { + entry: ['src/cli.ts'], + format: 'cjs', + external: ['next-ws'], + noExternal: ['*'], + }, +]); diff --git a/turbo.json b/turbo.json deleted file mode 100644 index facad4e..0000000 --- a/turbo.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$schema": "https://turbo.build/schema.json", - "tasks": { - "check": { - "dependsOn": ["^check"], - "inputs": ["src/**"], - "outputLogs": "new-only" - }, - "build": { - "dependsOn": ["^build"], - "inputs": ["../../readme.md", "src/**"], - "outputs": ["dist/**"], - "outputLogs": "new-only" - }, - "dev": { - "cache": false, - "persistent": true - } - } -}