From bc3768819fa0db285ed772a198c6de09958f7f2f Mon Sep 17 00:00:00 2001 From: Jamil Afouri Date: Sun, 22 Mar 2026 20:10:01 +0300 Subject: [PATCH 1/2] Prepare repository for open-source release - Remove proprietary branding from layout, icon, and sample webhook events - Replace domain-specific events with generic e-commerce examples - Rewrite README with features, setup guide, and API reference - Add LICENSE (MIT), CONTRIBUTING, CODE_OF_CONDUCT, SECURITY.md - Add CHANGELOG following Keep a Changelog format - Parameterize hardcoded credentials across Docker and shell scripts - Add engines field to package.json and .nvmrc for Node 20 - Add GitHub issue/PR templates (YAML forms), dependabot config - Add README badges (license, Node, TypeScript, Docker, PRs welcome) - Fix .dockerignore to exclude all .env variants --- .dockerignore | 2 +- .github/ISSUE_TEMPLATE/bug_report.yml | 94 ++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/feature_request.yml | 50 +++++++ .github/PULL_REQUEST_TEMPLATE.md | 22 +++ .github/dependabot.yml | 17 +++ .nvmrc | 1 + CHANGELOG.md | 21 +++ CODE_OF_CONDUCT.md | 33 +++++ CONTRIBUTING.md | 63 ++++++++ Dockerfile | 2 +- LICENSE | 21 +++ README.md | 160 ++++++++++++++++++--- SECURITY.md | 59 ++++++++ docker-compose.yml | 6 +- docker-entrypoint.sh | 4 +- package.json | 3 + src/app/icon.svg | 8 +- src/app/layout.tsx | 4 +- src/components/test-trigger-panel.tsx | 76 +++++----- start.sh | 9 +- 21 files changed, 582 insertions(+), 78 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/dependabot.yml create mode 100644 .nvmrc create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 SECURITY.md diff --git a/.dockerignore b/.dockerignore index 01b87ea..bc5e028 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,5 @@ node_modules .next .git -.env +.env* *.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..74a0694 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,94 @@ +name: Bug Report +description: Report a bug or unexpected behavior +title: "[Bug]: " +labels: ["bug"] + +body: + - type: textarea + id: description + attributes: + label: Description + description: A clear description of the bug. + placeholder: Describe the bug you encountered. + validations: + required: true + + - type: textarea + id: steps-to-reproduce + attributes: + label: Steps to Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. Go to '...' + 2. Click on '...' + 3. Scroll down to '...' + 4. See error + validations: + required: true + + - type: textarea + id: expected-behavior + attributes: + label: Expected Behavior + description: What you expected to happen. + placeholder: Describe what you expected to happen. + validations: + required: true + + - type: textarea + id: actual-behavior + attributes: + label: Actual Behavior + description: What actually happened. + placeholder: Describe what actually happened. + validations: + required: true + + - type: dropdown + id: os + attributes: + label: Operating System + description: Which operating system are you using? + options: + - macOS + - Windows + - Linux + - Other + validations: + required: true + + - type: input + id: node-version + attributes: + label: Node.js Version + description: What version of Node.js are you running? + placeholder: e.g. 20.11.0 + validations: + required: false + + - type: input + id: docker-version + attributes: + label: Docker Version + description: What version of Docker are you running, if applicable? + placeholder: e.g. 24.0.7 + validations: + required: false + + - type: input + id: browser + attributes: + label: Browser + description: Which browser are you using, if this is a UI-related issue? + placeholder: e.g. Chrome 122, Firefox 124, Safari 17 + validations: + required: false + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Any other information, screenshots, or logs that might help. + placeholder: Add any other context about the problem here. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..a45080b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: Discussions + url: https://github.com/keyper/webhook-tester/discussions + about: Ask questions and discuss ideas in GitHub Discussions. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..2b440f5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,50 @@ +name: Feature Request +description: Suggest a new feature or improvement +title: "[Feature]: " +labels: ["enhancement"] + +body: + - type: textarea + id: description + attributes: + label: Description + description: A clear description of the feature you would like. + placeholder: Describe the feature you are requesting. + validations: + required: true + + - type: textarea + id: use-case + attributes: + label: Use Case + description: Why is this feature needed? What problem does it solve? + placeholder: Explain the use case and motivation for this feature. + validations: + required: true + + - type: textarea + id: proposed-solution + attributes: + label: Proposed Solution + description: How you think this could work. Include API designs, UI mockups, or behavior descriptions if relevant. + placeholder: Describe your proposed solution. + validations: + required: false + + - type: textarea + id: alternatives-considered + attributes: + label: Alternatives Considered + description: Any alternative approaches you have thought about. + placeholder: Describe any alternative solutions or features you have considered. + validations: + required: false + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Any other information that might be helpful. + placeholder: Add any other context about the feature request here. + validations: + required: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..20baccf --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,22 @@ +## Description + +What does this PR do? Briefly describe the change. + +## Type of Change + +- [ ] Bug fix +- [ ] New feature +- [ ] Documentation update +- [ ] Refactoring +- [ ] Other (describe): + +## Testing + +- [ ] Tested locally with `npm run dev` +- [ ] Linting passes (`npm run lint`) +- [ ] Docker build works (`docker build -t webhook-tester .`) +- [ ] Existing functionality is not broken + +## Notes + +Any additional context or notes for reviewers. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..3c5f79d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + target-branch: "main" + open-pull-requests-limit: 10 + + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + target-branch: "main" + open-pull-requests-limit: 10 diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ab613fd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.1.0] - 2026-03-22 + +### Added + +- Initial open-source release +- Channel management with unique webhook URLs +- Configurable response behaviors (success, errors, timeouts, slow responses, redirects) +- Behavior sequences for rotating through responses +- JWT signature verification support +- 24 predefined test scenarios +- Real-time event streaming via Server-Sent Events +- Full webhook history with request/response details +- Docker support with embedded PostgreSQL +- Web UI for channel management and webhook inspection diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..da9bcb5 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,33 @@ +# Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a welcoming experience for everyone. + +## Our Standards + +Examples of behavior that contributes to a positive environment: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community + +Examples of unacceptable behavior: + +- Trolling, insulting or derogatory comments, and personal attacks +- Publishing others' private information without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement + +Instances of unacceptable behavior may be reported to the project maintainers. +All complaints will be reviewed and investigated and will result in a response +that is deemed necessary and appropriate to the circumstances. + +## Attribution + +This Code of Conduct is adapted from the +[Contributor Covenant](https://www.contributor-covenant.org), version 2.1. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..584a5b0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,63 @@ +# Contributing to Webhook Tester + +Thanks for your interest in contributing! This document covers the essentials. + +## Getting Started + +See the [README](README.md) for instructions on setting up the development environment. + +## Reporting Issues + +- Search existing issues before opening a new one. +- Use the appropriate issue template (bug report or feature request). +- Provide as much detail as possible, including steps to reproduce for bugs. + +## Submitting Pull Requests + +1. Fork the repository and create a branch from `main`. +2. Make your changes in a focused, single-purpose branch. +3. Ensure your code passes linting and any existing tests. +4. Fill out the pull request template when submitting. +5. Keep pull requests small and reviewable when possible. + +## Code Style + +This project includes an ESLint configuration (`eslint.config.mjs`). Run the linter before submitting: + +```bash +npm run lint +``` + +Follow the existing code patterns and conventions you see in the codebase. + +## Commit Messages + +This project follows [Conventional Commits](https://www.conventionalcommits.org/). Format your commit messages as: + +``` +(): + +[optional body] +``` + +Common types: + +- `feat` -- a new feature +- `fix` -- a bug fix +- `docs` -- documentation changes +- `style` -- formatting, no code change +- `refactor` -- code restructuring without behavior change +- `test` -- adding or updating tests +- `chore` -- maintenance tasks + +Examples: + +``` +feat(api): add webhook retry endpoint +fix(ui): correct timestamp display in request list +docs: update setup instructions in README +``` + +## Code of Conduct + +This project follows the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. diff --git a/Dockerfile b/Dockerfile index d454d6f..e5ed2b7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,6 +47,6 @@ RUN chmod +x /docker-entrypoint.sh EXPOSE 4100 ENV PORT=4100 ENV HOSTNAME="0.0.0.0" -ENV DATABASE_URL="postgres://webhook:webhook_local@localhost:5432/webhook_tester" +ENV DATABASE_URL=${DATABASE_URL:-postgres://webhook:webhook_local@localhost:5432/webhook_tester} ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a489b16 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024-present Webhook Tester Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index e215bc4..0528e16 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,152 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# Webhook Tester -## Getting Started +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![Node.js](https://img.shields.io/badge/Node.js-20%2B-green.svg)](https://nodejs.org/) [![TypeScript](https://img.shields.io/badge/TypeScript-5-blue.svg)](https://www.typescriptlang.org/) [![Docker](https://img.shields.io/badge/Docker-ready-blue.svg)](Dockerfile) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) -First, run the development server: +A self-hosted tool for testing webhook delivery, retry logic, and failure handling. -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev +## What It Does + +Webhook Tester lets you create isolated channels that receive webhooks and respond with configurable behaviors. Point your webhook producer at a channel URL, then simulate successes, errors, timeouts, slow responses, and more. Use it to verify retry logic, test circuit breaker behavior, validate JWT signatures, and run predefined test scenarios -- all through a web UI or REST API. + +## Features + +- Create isolated webhook channels, each with a unique URL, behavior config, and history +- Simulate responses: 200 OK, 400/401/404/429, 500, timeouts, slow responses, redirects +- Behavior sequences -- rotate through a list of responses across consecutive requests +- JWT signature verification (RS256) via configurable public key per channel +- 24 predefined test scenarios covering retry logic, circuit breakers, exponential backoff, edge cases, and more +- Real-time event stream via Server-Sent Events (SSE) +- Full webhook history with request headers, body, and response details +- Delivery analysis that groups attempts by event ID and computes retry gaps +- Docker-ready with embedded PostgreSQL -- single container, no external dependencies + +## Quick Start + +### Docker (single command) + +```sh +docker build -t webhook-tester . && docker run -p 4100:4100 webhook-tester ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +Open `http://localhost:4100`. Create a channel, then send webhooks to `http://localhost:4100/api/webhook/{slug}`. + +### Local Development + +Requires Node.js 20+ and Docker (for PostgreSQL). + +```sh +./start.sh dev +``` + +This starts PostgreSQL in Docker, runs migrations, and launches the dev server with hot reload on port 4100. + +## Local Development Setup + +```sh +# Clone the repository +git clone && cd webhook-tester + +# Install dependencies +npm install + +# Create environment file +cp .env.example .env + +# Start PostgreSQL +docker compose up -d db + +# Run database migrations +DATABASE_URL="postgres://webhook:webhook_local@localhost:5433/webhook_tester" npx prisma db push + +# Generate Prisma client +npx prisma generate + +# Start the dev server +DATABASE_URL="postgres://webhook:webhook_local@localhost:5433/webhook_tester" npm run dev +``` + +Or simply run `./start.sh` which handles all of the above automatically. + +## Usage + +1. **Create a channel** -- Open the UI at `http://localhost:4100` and create a channel with a slug (e.g., `my-test`). +2. **Send webhooks** -- Point your webhook producer at `http://localhost:4100/api/webhook/my-test`. +3. **Configure behavior** -- Use the UI or API to set how the channel responds (success, error, timeout, sequence, etc.). +4. **Observe results** -- Watch incoming webhooks in real time. Review history, retry patterns, and delivery analysis. +5. **Run scenarios** -- Activate a predefined scenario to test specific patterns like retry exhaustion or circuit breaker tripping. + +## API Reference + +### Health + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/health` | Health check and uptime | + +### Channel Management + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/channels` | List all channels | +| POST | `/api/channels` | Create a channel (`{ slug, name }`) | +| GET | `/api/channels/:slug` | Get channel details and state | +| DELETE | `/api/channels/:slug` | Delete a channel and all its data | +| GET | `/api/channels/:slug/status` | Get current channel state | +| POST | `/api/channels/:slug/behavior` | Set response behavior (`{ behavior, delayMs?, statusCode? }`) | +| POST | `/api/channels/:slug/sequence` | Set behavior sequence (`{ steps: [{ behavior, delayMs? }] }`) | +| POST | `/api/channels/:slug/public-key` | Set public key for JWT verification (`{ publicKey }`) | +| POST | `/api/channels/:slug/reset` | Reset channel to defaults and clear history | + +### Webhook Receiver + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/api/webhook/:slug` | Receive a webhook (this is the URL you give to producers) | +| POST | `/api/webhook/:slug/*` | Receive a webhook on any sub-path | + +### History and Analysis + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/channels/:slug/history` | Get webhook history (`?limit=&offset=&event=`) | +| DELETE | `/api/channels/:slug/history` | Clear webhook history | +| GET | `/api/channels/:slug/history/last` | Get the most recent webhook | +| GET | `/api/channels/:slug/history/:id` | Get a specific webhook by ID | +| GET | `/api/channels/:slug/observe` | Delivery analysis (grouped by event ID) | +| GET | `/api/channels/:slug/observe/:eventId` | Delivery analysis for a specific event | + +### Scenarios + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/scenarios` | List all predefined scenarios | +| POST | `/api/channels/:slug/scenarios/:name/activate` | Activate a scenario on a channel | + +### Real-Time Events + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/channels/:slug/events` | SSE stream (events: `webhook`, `state-change`, `reset`, `history-cleared`) | + +## Configuration -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +| Variable | Default | Description | +|----------|---------|-------------| +| `DATABASE_URL` | `postgres://webhook:webhook_local@localhost:5433/webhook_tester` | PostgreSQL connection string | +| `PORT` | `4100` | Server port | -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +## Security Note -## Learn More +Management endpoints (channel creation, behavior configuration, history) have no authentication. This tool is intended for local development and internal test environments. If you deploy it on a publicly accessible server, put it behind a reverse proxy with authentication. -To learn more about Next.js, take a look at the following resources: +## Tech Stack -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +Next.js, React, PostgreSQL, Prisma, TypeScript, Tailwind CSS -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +## Contributing -## Deploy on Vercel +See [CONTRIBUTING.md](CONTRIBUTING.md). -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +## License -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +[MIT](LICENSE) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..4af8101 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,59 @@ +# Security Policy + +We take the security of this project seriously. If you believe you have found a security vulnerability, please follow the responsible disclosure process outlined below. + +## Reporting a Vulnerability + +**Do NOT report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please use [GitHub's Private Vulnerability Reporting](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability) feature to submit your report. This ensures that sensitive details remain confidential until a fix is available. + +When reporting, please include as much of the following as possible: + +- A description of the vulnerability and its potential impact +- Step-by-step instructions to reproduce the issue +- Any relevant logs, screenshots, or proof-of-concept code +- The version(s) affected, if known + +## Response Timeline + +- **Acknowledgment:** We will acknowledge receipt of your report within 48 hours. +- **Detailed response:** We will provide a detailed response within 7 days, including an assessment of the issue and an expected timeline for a fix. +- **Fix and disclosure:** Once a fix is ready, we will coordinate with you on an appropriate disclosure timeline. + +## What Qualifies as a Security Issue + +Security issues include, but are not limited to: + +- Authentication or authorization bypasses +- Injection vulnerabilities (SQL, command, etc.) +- Cross-site scripting (XSS) or cross-site request forgery (CSRF) +- Exposure of sensitive data (credentials, tokens, personal information) +- Remote code execution +- Privilege escalation +- Denial of service vulnerabilities with a clear exploit path + +The following are generally **not** security issues and should be filed as regular bug reports: + +- Application crashes without a security impact +- UI/UX bugs +- Performance issues +- Feature requests +- Issues requiring physical access to a user's device + +If you are unsure whether something qualifies as a security issue, err on the side of caution and report it through the private vulnerability reporting process. + +## Coordinated Disclosure + +We follow a coordinated disclosure model: + +1. The reporter submits the vulnerability privately. +2. We work to verify and develop a fix. +3. We coordinate with the reporter on a disclosure date, typically once a fix has been released. +4. We publicly disclose the vulnerability along with credit to the reporter (unless anonymity is requested). + +We ask that reporters refrain from publicly disclosing the vulnerability until we have had a reasonable opportunity to address it. + +## Thank You + +We appreciate the efforts of security researchers and community members who help keep this project safe. Responsible disclosure makes a meaningful difference. diff --git a/docker-compose.yml b/docker-compose.yml index c67c7d1..ccaec37 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,9 +2,9 @@ services: db: image: postgres:16-alpine environment: - POSTGRES_DB: webhook_tester - POSTGRES_USER: webhook - POSTGRES_PASSWORD: webhook_local + POSTGRES_USER: ${POSTGRES_USER:-webhook} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-webhook_local} + POSTGRES_DB: ${POSTGRES_DB:-webhook_tester} ports: - '5433:5432' volumes: diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index f805735..0926532 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -3,8 +3,8 @@ set -e PGDATA="/var/lib/postgresql/data" DB_NAME="webhook_tester" -DB_USER="webhook" -DB_PASS="webhook_local" +DB_USER="${DB_USER:-webhook}" +DB_PASS="${DB_PASS:-webhook_local}" # ── 1. Initialize PostgreSQL if needed ─────────────────────── if [ ! -f "$PGDATA/PG_VERSION" ]; then diff --git a/package.json b/package.json index d5a49e2..cb57d9d 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,9 @@ "name": "webhook-tester", "version": "0.1.0", "private": true, + "engines": { + "node": ">=20.0.0" + }, "scripts": { "dev": "next dev", "build": "next build", diff --git a/src/app/icon.svg b/src/app/icon.svg index c835663..a0dc008 100644 --- a/src/app/icon.svg +++ b/src/app/icon.svg @@ -1,7 +1,7 @@ - - - - + + + + diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 9607aa9..7ed736d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,8 +5,8 @@ import './globals.css' const inter = Inter({ subsets: ['latin'] }) export const metadata: Metadata = { - title: 'Webhook Tester — Keyper', - description: 'QA tool for testing the full webhook delivery lifecycle', + title: 'Webhook Tester', + description: 'A developer tool for testing webhook delivery, retry logic, and failure scenarios', } export const viewport: Viewport = { diff --git a/src/components/test-trigger-panel.tsx b/src/components/test-trigger-panel.tsx index 27ef98b..254eac6 100644 --- a/src/components/test-trigger-panel.tsx +++ b/src/components/test-trigger-panel.tsx @@ -5,62 +5,60 @@ import { useState, useEffect } from 'react' type WebhookEventDef = { value: string label: string - category: 'application' | 'tenancy' | 'test' + category: 'order' | 'payment' | 'test' payload: object context: Record } const webhookEvents: WebhookEventDef[] = [ - // Application lifecycle - { value: 'application.qualified', label: 'Qualified', category: 'application', payload: { annualRentalPower: 144000 }, context: { applicationId: 'test-app-001' } }, - { value: 'application.approved', label: 'Approved', category: 'application', payload: {}, context: { applicationId: 'test-app-001' } }, - { value: 'application.rejected', label: 'Rejected', category: 'application', payload: { reason: { code: 'INSUFFICIENT_INCOME', message: 'Insufficient income documentation' } }, context: { applicationId: 'test-app-001' } }, - { value: 'application.change_requested', label: 'Change Requested', category: 'application', payload: { requestedDocuments: [{ action: 'reupload', documentType: 'general', reason: 'Please re-upload the tenancy contract with valid signatures' }] }, context: { applicationId: 'test-app-001' } }, - { value: 'application.withdrawn', label: 'Withdrawn', category: 'application', payload: { previousStatus: 'document_submitted', reason: 'Tenant chose a different property' }, context: { applicationId: 'test-app-001' } }, - { value: 'application.documents_submitted', label: 'Documents Submitted', category: 'application', payload: {}, context: { applicationId: 'test-app-001' } }, + // Order lifecycle + { value: 'order.created', label: 'Created', category: 'order', payload: { orderId: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', totalAmount: 14999, currency: 'USD' }, context: { orderId: 'test-order-001' } }, + { value: 'order.confirmed', label: 'Confirmed', category: 'order', payload: {}, context: { orderId: 'test-order-001' } }, + { value: 'order.cancelled', label: 'Cancelled', category: 'order', payload: { reason: { code: 'CUSTOMER_REQUEST', message: 'Customer requested cancellation' } }, context: { orderId: 'test-order-001' } }, + { value: 'order.updated', label: 'Updated', category: 'order', payload: { changes: [{ field: 'shipping_address', reason: 'Customer updated their delivery address' }] }, context: { orderId: 'test-order-001' } }, + { value: 'order.refunded', label: 'Refunded', category: 'order', payload: { previousStatus: 'confirmed', reason: 'Item out of stock' }, context: { orderId: 'test-order-001' } }, + { value: 'order.fulfilled', label: 'Fulfilled', category: 'order', payload: { trackingNumber: '1Z999AA10123456784', carrier: 'UPS' }, context: { orderId: 'test-order-001' } }, - // Tenancy signing lifecycle - { value: 'tenancy.in_signing', label: 'In Signing', category: 'tenancy', payload: { tenancyId: 'a0B7Q000001XXXXYY' }, context: { tenancyId: 'test-tenancy-001' } }, - { value: 'tenancy.rent_confirmed', label: 'Rent Confirmed', category: 'tenancy', payload: { tenancyId: 'a0B7Q000001XXXXYY' }, context: { tenancyId: 'test-tenancy-001' } }, - { value: 'tenancy.payment_plan_confirmed', label: 'Payment Plan Confirmed', category: 'tenancy', payload: { tenancyId: 'a0B7Q000001XXXXYY' }, context: { tenancyId: 'test-tenancy-001' } }, - { value: 'tenancy.tpma_tenant_signed', label: 'Tenant Signed', category: 'tenancy', payload: { tenancyId: 'a0B7Q000001XXXXYY' }, context: { tenancyId: 'test-tenancy-001' } }, - { value: 'tenancy.tpma_tenant_declined', label: 'Tenant Declined', category: 'tenancy', payload: { tenancyId: 'a0B7Q000001XXXXYY' }, context: { tenancyId: 'test-tenancy-001' } }, - { value: 'tenancy.tpma_keyper_signed', label: 'Keyper Signed', category: 'tenancy', payload: { tenancyId: 'a0B7Q000001XXXXYY' }, context: { tenancyId: 'test-tenancy-001' } }, - { value: 'tenancy.tpma_keyper_declined', label: 'Keyper Declined', category: 'tenancy', payload: { tenancyId: 'a0B7Q000001XXXXYY' }, context: { tenancyId: 'test-tenancy-001' } }, - { value: 'tenancy.published', label: 'Published', category: 'tenancy', payload: { - tenancyId: 'a0B7Q000001XXXXYY', - firstPayments: [ - { id: 'a0C7Q000001XXXXYY', type: 'RENT_RNPL', amount: 12000, from: 'tenant', to: 'keyper', status: 'Paid' }, - { id: 'a0C7Q000001XXXXYZ', type: 'RENT', amount: 5000, from: 'keyper', to: 'landlord', status: 'Pending' }, + // Payment lifecycle + { value: 'payment.completed', label: 'Completed', category: 'payment', payload: { paymentId: 'c9bf9e57-1685-4c89-bafb-ff5af830be8a' }, context: { paymentId: 'test-payment-001' } }, + { value: 'payment.failed', label: 'Failed', category: 'payment', payload: { paymentId: 'c9bf9e57-1685-4c89-bafb-ff5af830be8a', errorCode: 'CARD_DECLINED' }, context: { paymentId: 'test-payment-001' } }, + { value: 'payment.refunded', label: 'Refunded', category: 'payment', payload: { paymentId: 'c9bf9e57-1685-4c89-bafb-ff5af830be8a', refundAmount: 4999 }, context: { paymentId: 'test-payment-001' } }, + { value: 'invoice.paid', label: 'Invoice Paid', category: 'payment', payload: { invoiceId: 'e4d909c2-90d0-4b05-9c59-f5b21bde4e50', amount: 9900, currency: 'USD' }, context: { invoiceId: 'test-invoice-001' } }, + { value: 'invoice.overdue', label: 'Invoice Overdue', category: 'payment', payload: { invoiceId: 'e4d909c2-90d0-4b05-9c59-f5b21bde4e50', dueDate: '2025-01-15', daysOverdue: 7 }, context: { invoiceId: 'test-invoice-001' } }, + { value: 'subscription.renewed', label: 'Subscription Renewed', category: 'payment', payload: { + subscriptionId: 'b2d8f636-28a2-4b6e-bc6e-31f58c27a9b3', + plan: 'pro', + billingPeriod: 'monthly', + nextBillingDate: '2025-03-01T00:00:00.000Z', + lineItems: [ + { id: '7c9e6679-7425-40de-944b-e07fc1f90ae7', type: 'SUBSCRIPTION_FEE', amount: 4900, description: 'Pro plan - monthly', status: 'Paid' }, + { id: 'aab3238e-e612-4e3a-b2e5-7c5e2b0c93f1', type: 'PLATFORM_FEE', amount: 500, description: 'Platform usage fee', status: 'Paid' }, ], - payments: [ - { id: 'a0C7Q000001XXXXZZ', type: 'RENT_RNPL', amount: 5000, from: 'tenant', to: 'keyper', status: 'Pending' }, - ], - }, context: { tenancyId: 'test-tenancy-001' } }, + }, context: { subscriptionId: 'test-sub-001' } }, - // Tenancy payment lifecycle - { value: 'tenancy.tpma_first_payment_attempt', label: 'First Payment Attempt', category: 'tenancy', payload: { - tenancyId: 'a0B7Q000001XXXXYY', + // Payment attempt details + { value: 'payment.initial_attempt', label: 'Initial Attempt', category: 'payment', payload: { + orderId: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', totalAmount: 15000, status: 'succeeded', successfulPayments: [ - { paymentId: 'a0C7Q000001XXXXYY', amount: 12000, paymentType: 'RENT_RNPL', clearedAt: '2024-06-15T10:30:00.000Z' }, - { paymentId: 'a0C7Q000001XXXXYZ', amount: 3000, paymentType: 'DIGITAL_MANAGEMENT_FEE', clearedAt: '2024-06-15T10:30:00.000Z' }, + { paymentId: '7c9e6679-7425-40de-944b-e07fc1f90ae7', amount: 12000, paymentType: 'ORDER_PAYMENT', clearedAt: '2025-01-15T10:30:00.000Z' }, + { paymentId: 'aab3238e-e612-4e3a-b2e5-7c5e2b0c93f1', amount: 3000, paymentType: 'SERVICE_FEE', clearedAt: '2025-01-15T10:30:00.000Z' }, ], failedPayments: [], - }, context: { tenancyId: 'test-tenancy-001', paymentId: 'test-payment-001' } }, - { value: 'tenancy.payment_attempt', label: 'Payment Attempt', category: 'tenancy', payload: { - tenancyId: 'a0B7Q000001XXXXYY', + }, context: { orderId: 'test-order-001', paymentId: 'test-payment-001' } }, + { value: 'payment.retry_attempt', label: 'Retry Attempt', category: 'payment', payload: { + orderId: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', totalAmount: 5000, status: 'succeeded', - type: 'user', + type: 'retry', payments: [ - { paymentId: 'a0C7Q000001XXXXYY', amount: 5000, paymentType: 'RENT_RNPL', from: 'tenant', to: 'keyper', status: 'succeeded', clearedAt: '2024-07-01T09:00:00.000Z' }, + { paymentId: '7c9e6679-7425-40de-944b-e07fc1f90ae7', amount: 5000, paymentType: 'ORDER_PAYMENT', from: 'customer', to: 'merchant', status: 'succeeded', clearedAt: '2025-02-01T09:00:00.000Z' }, ], - }, context: { tenancyId: 'test-tenancy-001', paymentId: 'test-payment-001' } }, + }, context: { orderId: 'test-order-001', paymentId: 'test-payment-001' } }, // Test - { value: 'test.ping', label: 'Test Ping', category: 'test', payload: { message: 'This is a test webhook from Keyper' }, context: {} }, + { value: 'test.ping', label: 'Test Ping', category: 'test', payload: { message: 'This is a test webhook event' }, context: {} }, ] const DEFAULT_EVENT_INDEX = Math.max(0, webhookEvents.findIndex(e => e.value === 'test.ping')) @@ -183,9 +181,9 @@ export function TestTriggerPanel({ channelSlug }: { channelSlug: string }) { {/* Event type */}
- {(['application', 'tenancy', 'test'] as const).map(category => { + {(['order', 'payment', 'test'] as const).map(category => { const categoryEvents = webhookEvents.filter(e => e.category === category) - const categoryLabel = { application: 'Application', tenancy: 'Tenancy', test: 'Test' }[category] + const categoryLabel = { order: 'Order', payment: 'Payment', test: 'Test' }[category] return (
{categoryLabel}
diff --git a/start.sh b/start.sh index d828c92..d3fe543 100755 --- a/start.sh +++ b/start.sh @@ -4,7 +4,7 @@ # # Usage: # ./start.sh # Dev mode (hot reload) -# ./start.sh prod # Production (single Docker container for EC2) +# ./start.sh prod # Production (single Docker container for server) # ./start.sh --port 4200 # Custom port # # Stop: ./stop.sh or Ctrl+C @@ -24,7 +24,7 @@ while [[ $# -gt 0 ]]; do echo "" echo " Usage:" echo " ./start.sh Dev (hot reload + DB in Docker)" - echo " ./start.sh prod Production (single container for EC2)" + echo " ./start.sh prod Production (single container for server)" echo " ./start.sh --port 4200 Custom port" echo "" exit 0 @@ -87,7 +87,8 @@ fi # Migrations echo " Running migrations..." -DATABASE_URL="postgres://webhook:webhook_local@localhost:5433/webhook_tester" npx prisma db push --skip-generate 2>&1 | grep -v "^$" | sed 's/^/ /' +source .env 2>/dev/null || true +DATABASE_URL="${DATABASE_URL:-postgres://webhook:webhook_local@localhost:5433/webhook_tester}" npx prisma db push --skip-generate 2>&1 | grep -v "^$" | sed 's/^/ /' # Kill existing EXISTING=$(lsof -ti:$PORT 2>/dev/null || true) @@ -100,4 +101,4 @@ echo " http://localhost:$PORT" echo " Webhook URL: http://localhost:$PORT/api/webhook/{slug}" echo "" -DATABASE_URL="postgres://webhook:webhook_local@localhost:5433/webhook_tester" PORT=$PORT npx next dev -p "$PORT" +DATABASE_URL="${DATABASE_URL:-postgres://webhook:webhook_local@localhost:5433/webhook_tester}" PORT=$PORT npx next dev -p "$PORT" From e97828171a5c0eb7d5a91c9b4ced4f810a8a9226 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 22 Mar 2026 17:11:55 +0000 Subject: [PATCH 2/2] Bump @tailwindcss/postcss from 4.2.1 to 4.2.2 Bumps [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss) from 4.2.1 to 4.2.2. - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.2.2/packages/@tailwindcss-postcss) --- updated-dependencies: - dependency-name: "@tailwindcss/postcss" dependency-version: 4.2.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 297 ++++++++++++++++++++++++++++------------------ 1 file changed, 182 insertions(+), 115 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9eea82e..17f6519 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,9 @@ "eslint-config-next": "16.1.6", "tailwindcss": "^4", "typescript": "^5" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@alloc/quick-lru": { @@ -1520,49 +1523,49 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", - "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", + "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", - "lightningcss": "1.31.1", + "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.2.1" + "tailwindcss": "4.2.2" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", - "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz", + "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", "dev": true, "license": "MIT", "engines": { "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.2.1", - "@tailwindcss/oxide-darwin-arm64": "4.2.1", - "@tailwindcss/oxide-darwin-x64": "4.2.1", - "@tailwindcss/oxide-freebsd-x64": "4.2.1", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", - "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", - "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", - "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", - "@tailwindcss/oxide-linux-x64-musl": "4.2.1", - "@tailwindcss/oxide-wasm32-wasi": "4.2.1", - "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", - "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" + "@tailwindcss/oxide-android-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-x64": "4.2.2", + "@tailwindcss/oxide-freebsd-x64": "4.2.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-x64-musl": "4.2.2", + "@tailwindcss/oxide-wasm32-wasi": "4.2.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", - "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", + "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", "cpu": [ "arm64" ], @@ -1577,9 +1580,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", - "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", + "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", "cpu": [ "arm64" ], @@ -1594,9 +1597,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", - "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", + "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", "cpu": [ "x64" ], @@ -1611,9 +1614,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", - "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", + "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", "cpu": [ "x64" ], @@ -1628,9 +1631,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", - "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", + "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", "cpu": [ "arm" ], @@ -1645,9 +1648,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", - "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", + "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", "cpu": [ "arm64" ], @@ -1662,9 +1665,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", - "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", + "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", "cpu": [ "arm64" ], @@ -1679,9 +1682,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", - "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", + "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", "cpu": [ "x64" ], @@ -1696,9 +1699,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", - "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", + "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", "cpu": [ "x64" ], @@ -1713,9 +1716,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", - "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz", + "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -1742,10 +1745,74 @@ "node": ">=14.0.0" } }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.8.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.8.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "inBundle": true, + "license": "0BSD", + "optional": true + }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", - "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", + "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==", "cpu": [ "arm64" ], @@ -1760,9 +1827,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", - "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz", + "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==", "cpu": [ "x64" ], @@ -1777,17 +1844,17 @@ } }, "node_modules/@tailwindcss/postcss": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.1.tgz", - "integrity": "sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.2.tgz", + "integrity": "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==", "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.2.1", - "@tailwindcss/oxide": "4.2.1", + "@tailwindcss/node": "4.2.2", + "@tailwindcss/oxide": "4.2.2", "postcss": "^8.5.6", - "tailwindcss": "4.2.1" + "tailwindcss": "4.2.2" } }, "node_modules/@tybys/wasm-util": { @@ -3316,9 +3383,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.20.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", - "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", "dev": true, "license": "MIT", "dependencies": { @@ -5220,9 +5287,9 @@ } }, "node_modules/lightningcss": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", - "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -5236,23 +5303,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.31.1", - "lightningcss-darwin-arm64": "1.31.1", - "lightningcss-darwin-x64": "1.31.1", - "lightningcss-freebsd-x64": "1.31.1", - "lightningcss-linux-arm-gnueabihf": "1.31.1", - "lightningcss-linux-arm64-gnu": "1.31.1", - "lightningcss-linux-arm64-musl": "1.31.1", - "lightningcss-linux-x64-gnu": "1.31.1", - "lightningcss-linux-x64-musl": "1.31.1", - "lightningcss-win32-arm64-msvc": "1.31.1", - "lightningcss-win32-x64-msvc": "1.31.1" + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", - "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", "cpu": [ "arm64" ], @@ -5271,9 +5338,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", - "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" ], @@ -5292,9 +5359,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", - "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", "cpu": [ "x64" ], @@ -5313,9 +5380,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", - "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", "cpu": [ "x64" ], @@ -5334,9 +5401,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", - "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", "cpu": [ "arm" ], @@ -5355,9 +5422,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", - "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", "cpu": [ "arm64" ], @@ -5376,9 +5443,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", - "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", "cpu": [ "arm64" ], @@ -5397,9 +5464,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", - "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", "cpu": [ "x64" ], @@ -5418,9 +5485,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", - "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", "cpu": [ "x64" ], @@ -5439,9 +5506,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", - "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", "cpu": [ "arm64" ], @@ -5460,9 +5527,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", - "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", "cpu": [ "x64" ], @@ -7294,9 +7361,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", - "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", + "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", "dev": true, "license": "MIT" },