diff --git a/.github/workflows/blog-deployment.yml b/.github/workflows/blog-deployment.yml index cef9a8398..acecfc8e1 100644 --- a/.github/workflows/blog-deployment.yml +++ b/.github/workflows/blog-deployment.yml @@ -29,10 +29,6 @@ jobs: working-directory: ./opensaas-sh/blog run: npm install - - name: Generate LLM files - working-directory: ./opensaas-sh/blog - run: npm run generate-llm-files - - name: Build site working-directory: ./opensaas-sh/blog run: npm run build @@ -53,10 +49,6 @@ jobs: working-directory: ./opensaas-sh/blog run: npm install - - name: Generate LLM files - working-directory: ./opensaas-sh/blog - run: npm run generate-llm-files - - name: Build site working-directory: ./opensaas-sh/blog run: npm run build diff --git a/.github/workflows/check-opensaas-diffs.yml b/.github/workflows/check-opensaas-diffs.yml new file mode 100644 index 000000000..f146f550b --- /dev/null +++ b/.github/workflows/check-opensaas-diffs.yml @@ -0,0 +1,28 @@ +name: Check `opensaas-sh` diffs are up to date + +on: + pull_request: + push: + branches: + - main + +jobs: + check-diffs: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Generate diffs + run: | + ./opensaas-sh/tools/patch.sh + ./opensaas-sh/tools/diff.sh + + - name: Check for uncommitted changes + run: | + if [[ -n $(git status --porcelain) ]]; then + echo "Error: There are uncommitted diff changes" + echo "Please run './opensaas-sh/tools/patch.sh' and './opensaas-sh/tools/diff.sh' locally and commit the changes" + git status + exit 1 + fi diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 149a04d0c..a14c41cfd 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -5,12 +5,12 @@ on: branches: - main pull_request: - branches: - - main env: WASP_TELEMETRY_DISABLE: 1 - WASP_VERSION: 0.18.0 + WASP_VERSION: + # If you're copying this workflow to your own project, set this to your app's Wasp version: + main jobs: test: @@ -29,9 +29,16 @@ jobs: - name: Docker setup uses: docker/setup-buildx-action@v3 - - name: Install Wasp + - name: Install Wasp ${{ env.WASP_VERSION }} + if: env.WASP_VERSION != 'main' run: curl -sSL https://get.wasp.sh/installer.sh | sh -s -- -v ${{ env.WASP_VERSION }} + # If you're copying this workflow to your own project, you can remove this step: + - name: Install latest development Wasp + if: env.WASP_VERSION == 'main' + # Installs the latest published build of the CLI from the `main` branch. + run: npm i -g https://pkg.pr.new/@wasp.sh/wasp-cli@main + - name: Cache global node modules uses: actions/cache@v4 with: @@ -108,7 +115,6 @@ jobs: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} STRIPE_API_KEY: ${{ secrets.STRIPE_KEY }} STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }} - STRIPE_CUSTOMER_PORTAL_URL: https://billing.stripe.com/p/login/test_8wM8x17JN7DT4zC000 PAYMENTS_HOBBY_SUBSCRIPTION_PLAN_ID: ${{ secrets.STRIPE_HOBBY_SUBSCRIPTION_PRICE_ID }} PAYMENTS_PRO_SUBSCRIPTION_PLAN_ID: ${{ secrets.STRIPE_PRO_SUBSCRIPTION_PRICE_ID }} PAYMENTS_CREDITS_10_PLAN_ID: ${{ secrets.STRIPE_CREDITS_PRICE_ID }} diff --git a/.github/workflows/template-release.yml b/.github/workflows/template-release.yml new file mode 100644 index 000000000..59c1bf3fc --- /dev/null +++ b/.github/workflows/template-release.yml @@ -0,0 +1,48 @@ +--- +name: Template Release + +"on": + push: + tags: + - "wasp-v*-template" + + workflow_dispatch: + inputs: + tag: + description: "Tag to release (e.g., wasp-v0.18-template)" + required: true + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + + env: + tag_name: ${{ inputs.tag || github.ref_name }} + archive_name: template.tar.gz + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + ref: ${{ env.tag_name}} + + - name: Create template archive + # The Wasp CLI expects the template contents to be in the root of the archive. + working-directory: template + run: tar -czf "../$archive_name" . + + - name: Create release if it doesn't exist + run: | + if ! gh release view "$tag_name"; then + gh release create "$tag_name" + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload release asset + run: gh release upload "$tag_name" "$archive_name" --clobber + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 38525f5f2..cec50602b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Dependencies node_modules/ -# MacOS-specific files. +# macOS-specific files. .DS_Store # We want to keep the template clean from the usual build artifacts. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d82128c9c..d6bde10d6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,6 +17,10 @@ Repo is divided into two main parts: [template](/template) dir and [opensaas-sh] ## How to Contribute +> [!IMPORTANT] +> The in-development version of the template uses the in-development version of Wasp. We've set up the `./tools/wasp` script. +> To use it, whenever you would normally run `wasp `, run `/tools/wasp ` instead. + 1. Make sure you understand the basics of how open-saas works (check out [docs](https://docs.opensaas.sh)). 2. Check out this repo (`main` branch) and make sure you are able to get the app in [template/app/](/template/app) running (to set it up, follow the same steps as for running a new open-saas app, as explained in the open-saas docs). 3. Create a new git branch for your work (aka feature branch) and do your changes on it. diff --git a/README.md b/README.md index 8f6ca1a78..d67c7e5a9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ ## Welcome to your new SaaS App! 🎉 -Open SaaS - The open-source SaaS boilerplate with superpowers! | Product Hunt +
+ + Open SaaS 2.0 - Free, open-source SaaS starter kit with superpowers | Product Hunt + + + Open SaaS 2.0 - Free, open-source SaaS starter kit with superpowers | Product Hunt + +
https://github.com/user-attachments/assets/3856276b-23e9-455e-a564-b5f26f4f0e98 @@ -73,6 +80,10 @@ There are two ways to get help or provide feedback (and we try to always respond 1. [Open an issue](https://github.com/wasp-lang/open-saas/issues) 2. [Wasp Discord](https://discord.gg/aCamt5wCpS) -- please direct questions to the #🙋questions forum channel +## Development Tools + +For information about the development tools used to maintain derived projects (like opensaas.sh and template-test), see [tools/README.md](./tools/README.md). + ## Contributing Note that we've tried to get as many of the core features of a SaaS app into this template as possible, but there still might be some missing features or functionality. diff --git a/opensaas-sh/README.md b/opensaas-sh/README.md index bc3dc5fce..7fb6b61f3 100644 --- a/opensaas-sh/README.md +++ b/opensaas-sh/README.md @@ -10,29 +10,18 @@ Inception :)! ### Demo app (app_diff/) -Since the demo app is just the open saas template with some small tweaks, and we want to be able to easily keep it up to date as the template changes, we don't version (in git) the actual demo app code, instead we version the diffs between it and the template: `app_diff/`. - -So because we don't version the actual demo app (`app/`) but its diffs instead (`app_diff`), the typical workflow is as follows: - -1. Run `./tools/patch.sh` to generate `app/` from `../template/` and `app_diff/`. -2. If there are any conflicts (normally due to updates to the template), modify `app/` till you resolve them. Do any additional changes also if you wish. -3. Generate new `app_diff/`, based on the current updated `app/`, by running `./tools/diff.sh`. +> [!IMPORTANT] +> The in-development version of the template uses the in-development version of Wasp. We've set up the `./tools/wasp` script. +> To use it, whenever you would normally run `wasp `, run `/tools/wasp ` instead. -**Running on MacOS** - -If you're running the `patch.sh` or `diff.sh` scripts on Mac, you need to install: +Since the demo app is just the open saas template with some small tweaks, and we want to be able to easily keep it up to date as the template changes, we don't version (in git) the actual demo app code, instead we version the diffs between it and the template: `app_diff/`. -- `grealpath` (packaged within `coreutils`), -- `gpatch`, -- and `diffutils`. +#### Workflow -```sh -brew install coreutils # contains grealpath -brew install gpatch -brew install diffutils -``` +- Generate `app/` from template and diffs: `./tools/patch.sh` +- Update diffs after modifying `app/`: `./tools/diff.sh` -Make sure not to commit `app/` to git. It is currently (until we resolve this) not added to .gitignore because that messes up diffing for us. +For detailed information about the diff/patch workflow and macOS setup requirements, see [../tools/README.md](../tools/README.md). ### Blog (blog/) diff --git a/opensaas-sh/app_diff/.env.vault.diff b/opensaas-sh/app_diff/.env.vault.diff index 714c36b85..ee47573dc 100644 --- a/opensaas-sh/app_diff/.env.vault.diff +++ b/opensaas-sh/app_diff/.env.vault.diff @@ -7,20 +7,20 @@ +#/--------------------------------------------------/ + +# development -+DOTENV_VAULT_DEVELOPMENT="STJRGW84NkbUqenj6Eyt98WiAcvzWsWhOKwyktUfAJ0dR8TlNnSdCDRsCRisCzKuDTTZtgCf6FyTgT48HsYuwFZqydg/aFeM4skfLpyUy0wz0K90hq2sNEB85HCAwZaTTf9ye5fEBq9P1PAEoYlHEFtPXO1exSkXyxQHzkv5/j2xLJVkg89cZ+MBaQQtzmgK/A9ya5eVSX2zIhum+ipZKIE04pjc4H8fu5YgxY91rOVTFHBQc1dlbuCr+vvmvgHxNS2gO+eM5UcmZk6FY4yY+GUZmf+xGw5ZPwFjiS3awC+jNhdebWnkrk/PopAqBC3i7eH3fdaPvJW2RWkMMrY4+gtN8Hs9lPNopWkl3uavAva9qmvrCE1KLu94IaQh34Cy6dRurPQDF/MmnugqYzcEQvvtIV1sgZig6Id4c9P67gs/Q9NvKXvbeNxzXYTwkF3SvnMRXk33yBKbXHxE1vIn9k9mbMnX3DVeVdT5TDR9BKs9xGMuvP7rQv5RA/xSnzMFg8LeJ+oZ2zNUWWN8tOyx6K4WwiIJN39AOLYElXAFa3UTSk12GtzaySM5rQdcWn0YLN3uncye26zRCA+BpR231fgrSpfJJ2VkNBG0pTh1ELySOFug6mItLOb7CQGXjZrrwTSfEBhhpMZn/c2Ec6p/nBN2UcHAmB55N6GDFuAkagv9l/4C5fIbS6iGHYAOwwYKczSBryi3+5y7sDYkdVhOTzuKTfkpY1FpkEgUY8FFnW2GjYodH2T+l23Q84a7A9VjxZAyZJkgwGnezO+M8fWcwtqt3iywEVhTd5Hi7Nxe+Q53oQeUrwnTKSkx5Ig5uR1RJIybW+gWvpSOEcUZN6AVf67/UPu9RHpwHDQ5YCGGqJwDzR/f5QxR0kqiW3B0ZDWpJX7zFk2QiLGLzDZ6xmdaGenbJ0jWMA116Xm+ot0GCEqim+RVk768zSAxIQFOCcllXWhmxwTO2/SzY6j7Hxf1Ps8e0uCVc7FPemh7nBYLRlSOI/Pjk9gLUkyKhV7IEiaL2BbPFLGOFkOk0AGNc+m/y1BH+CnJYYKCK1AXNquKM9+MV8Wmure2iBIm2oNlDOcJl48YpUQ0BcKzmFw06OJHVULPVn7da7XZ2/34SvyFcsYARE6NZLLMMg810S1cok0gQ3FpxKv3wxP1jRXntEC1ZxUqJQE/hZ5IxoOxhAJGQfqYbJIQSE5oE8Ve7E0X2lTYVEPiwziYOeovKidJX1vccZh4ggxgYsRzT/qAiS/FcvgsWz9kY6n0e1iKLEmsGR96SDpzNSWV/57iSuOd6nKtcU8HWhekTMnTQK4wrhXuYQHZhLeRnGQIjHzNyzJ8lV6HVcot6NZPPHjOfohQ9eIhQi7qUC7JPcLFkWixjrPTAEJVlQelbAOPHPOUyYEoejcPi/0LMTrQPYN/To/lhDHn/5N9vhlk8flj0zuJHpCFq8laDsn9wdvymuK/0MQ7DrPPU00nNzQjXDtT9xbb6I9ZX7XtgTVHVnWtEwxCQRYkmbR0T+0TzDLXlQObITanuFVa19ZSeATGc+PhxFAva5yxPih2L+j3BTN43ap/X3j54DDylyNHDQlpC21wOa+HFGftYUAWx8/w8i5gr7ZU66aVQWZspSLP7BIFzMaVVjAM5gVqaCLTpcnCE3oMMYddsqsXUbkJrmdlh4cP8ao2Y9YHzv3KRTyAU4wDxhkwWdDqnUSRVXkcDeZUuCQJsHFRvXVzgCNDSktMewTC4xMNniW6KNUwbqzWxGBCz6g/0WUJEaJYG+teu1jBMPDhNFFlw6BjIoNX7iButmm81XCG8D1uEgRvo+H4lxAL4rPyPNCQieT34XHk/QxcQsqij2wnTenGUwWQt++kzMLvL/69RtvaskA8I3aLXDLn3ATMU4JlMvD1MqRHrGb3tzoz+lXKWVgrJR+BSYUBXZc9EAUsj4G4+NsRyMPbBWE2ADgM4ksDflXCccbelu/4S50eS9dEXf7ms0CeYh+dFmqjhCXI8JrgL+bUdOwDbEJVPjM2yOVM1wATGAa8+0nLaJbIwI41JVXBx/uLZ8pt2pyvQB9cyLQR00hcjbLymHBfLP51B6nlaNhUq4yZsI/JM59XNmEYAY8GZJFuvIBvSimC5plUCc1bfGhK694k7Fv1bKtlIzXd4kCy5bKNiwWMTVbOy3Uv87FQTa61z5mnz/XGmRwU/22v4VOWy11BKWGYkYJGN1KrAVkUenCKQw6FjgVXBbfJ6m2k9tl5dfQ48ppToorUZkFiKsPuc8V6DhcUt2stxEETnUnU9K2p8QyyhfPykBK6YWbShDobeA0TPF+vo2ypkVOvhNo7F4cQnvvVKl9MJmmAxFOisB9yHXREk2GSlivNJeTqIz8Vv4K42/wNMAO2ZQqSPlE4wt2ueFQqLMkG+UDlD6gujSx8D/OzSTfCx+vLTzdqsbubPxjcPx+pQA==" -+DOTENV_VAULT_DEVELOPMENT_VERSION=12 ++DOTENV_VAULT_DEVELOPMENT="KcbdthT5Zsh4u+Na9bsjC/RP23SmpKnLFcdXEls8EeSN+UVM0uJBiKqMBEu9tHNHauObmj2X8jQ/RCjDndNI/YoPCihS9u/c9LCFVU+itCoBbG38ob+Ux+bjB5SuE7Y2L3dfvChi1wLIwXbxX0TIAIB0MK/R6AdV/LRwKOFGL3eGE4eKIDAzCIYqbFGTpOaz2yz7Vn19OH3pNCwCGfzmn84AMBbANQfIoMvDGWGEPYO6cmoz004NBXKmlkdpLVoMKy5e1jzC6lZz2ni5fIceb5NFtRfG/3sQ563XT384oHj6/uDXPpqmoWdLeBfGAKm7GTt22Bgk+ZccBtcbkypcU8F+8KhhBTisTFjyXvq4y/ayNdZNds3mCjwchBnMdIhZod3VaZSgfOGqOrI6gfScsbCCx/omUbuCLW+lfmWtl46gNRs2iaiD8WmMmPiQL/MPr4NW0x44vc6j0ug1/FDvCurA/V++qu+KOvqoVHVglMQ+Vx8TG0D2W+EM7f30UezI77sjaP3+4vf9WfA7tu2MDsO6Z/EOHaJu48AOcR/hEPHKudU/9YBxF0nH5bJYcDqJPYf30Xvbip5NGI8vMYtP7S8qAGz6mj8U8cwV8k0GQXH1Js/0t6FmSVaWhY3W/U44oRP0ZbIkxGZ8WBffQUYTKgm7U3jAUV+cZ7LwVElaxiaQMeEQwsveFKxVi7fPR5AjjzSW84GGH2iGSzSU0fKqt3jrO1y3UOl/yr9Z47GLn1OG994WHhchW9quhG20vHynFwe1aGzFGpGma2xPMBp6RrLHIrlpfrs2imNaWVYMyXQ2Btj3vUaBDbS5NsM+8UwrA8yxUtvtVG1Z9TKWdwpDpp31uEGn72nvi6XO8hf1MLmRDbOfqlcGW7F0CMUux6ERkevf6JylkAVoeGvt1GWDsfJpVA2TKs1UMQ44yUOthDoIBkzGgfU8NTQhliT7mOrvVj+4KKO2LswkiKSfnr3jprtCZf5qRReV5ZDYUKC/5u3jGqc5dyi63x5eNI6avxmz4/NJd+qbNXoZJ0KXISTi52QvaH/uEkVCm/TM2LgZQsaw47MRx6+ZLcsIb9s5UJGClwM/7PRtkGPOT7JuMhlL3uNDxF8OJYjxTbaLKbxqQa24yB3CmoB4Wcz/N+05RIT5DcWDxdqaPt5UHmZxLkUc7Tj/pfDAYPaD/fePlIlubWGBEMtrw6zTzqv6QSTikcDDlIz7GlLTVzGoukTuvrMm+H6vka8QxJGAwT4JKca5ZsO6qgN5hvSV9T4hX9QPyhEYbd6HoMlARJ7dQjztK4sE35AVpP2aRhkYNGJ9JZW+fU7OBf7H7B5HnlKG6lpwXASTYv9q9mpyK3bEuafUplu3WcbOfrhEHw4Mg5taIOtKPx9Se/FRzBJhYrlidnMbdLapvJyEAYw1+SJzZj4SIJA70TBk2jDrKvvxh1osHUTQBW0COBLIPj4J+YrA9nLx5MtozREqboK8YYglxqEXZ36yIB0zO56O7E4IK9rHkH1d4mhkyuwQA5pw1tGcXuHMFdofbyxmxSyr/RIKMO4zdMA2ncob1Bun5Pr7CX0XNhwK6FCVPTKxNP2GS+AXDuDSiccRuQL7NE5+oCXGDqhuLw5hFu1T1i10RvHJWpo5NQAJSE09QrdS/+8aC3V5PhkemJQ7GHj56qxDzKNglbET6tpR/x6mM2o4FYoWqZ3wGJQetEObI4BQ6L0r+6goiBFWN5J54r0hFUZntAkWtGQvdYQK+8c0oiV00gj2P6oTBFMzBbu1d6Zu1SZ/5n2mhh86avJXcGCppCq5xmzMt0qy4Xz/bBW7JFGxQ0F/XdWDJlhcEiJW3WREWfPXxjwxLMxGZjzmpUaG4c2qkSeopb6Eoxa1yoq7TngjTDwfdhkgRDexJPjy95nzclDpSY4o8AzbCEG+AS7T30hKulRXVY/lntV3W7RfwIj1nrwqSCZLwiCgOoifIlEeXLgta2k7SWgRdyMS01XP/4Hs7gsSOEKc5u3CKprTvf3MWMrK3ehrgS5XcDEmMeWCjEz58HbRsnhKryKT0NZ/7Y21xXApxKuA31/qHpdtrDdy+REa1ogxQf1WiVfdYe7Fj1W5Y7TNzbiDTw46qyG6JpW5biq/DjGnBz6ZAXBh/PLlRkVizK7qxqtIWHT6CXqTTlMFdVuJL39zNiOhX3YeLn/q9dVgUVKAIEJCVzcgI7RJHryr2iff1Y6L21wrP7vFX8HAYiE+rdl3VmTZtEin1bvDBgYoHxp/+VaYpxGA0DcAg/b2cbOREj1apXzzWj8AkFq0a6Fx7mKXI6CuMrv97alo/lNZX0ZqG2hIKdabM7De7ngNoZklX6kc/8HfQfKLoLsj7D2UEU3UDx6yjXHJAHH1KuPwwf8NtcIRbP6YcFopz8IAAIt8NJrksNMeEaV3SfKeQN9Knflg77QsObGxA5xYs8MF/xHDxto7ooiiitJNVQ4QkXZ59I9AK+qQwr0ub8inl57fO9sJU3MC17JOINJosrgGqDLuDwAY53t1MY7dTcxuXA==" ++DOTENV_VAULT_DEVELOPMENT_VERSION=13 + +# ci -+DOTENV_VAULT_CI="pveNDc605iqf5ZOh9mvYpFOc925852DGrvz7y+vs82ub3bZvTiLhJMuJKZEDtyjn7yBUdfMRsAP6wAL8/73te+4y7K56lT48MuvFlpvwM9ERkCzJY1yZ6ib3eBzTax/jon2H5kfEHBM9TRIa5B8JrCHbYqhcrnxj0VyLEa4x1I4pMTdesRZ+907FTdExaRSFFujdkUaScytOV93DTr2lQZpUGBga5CbKr3KyeSS1+ONGSWc+BQiAkdhmnvPsyKHnWRKHSFVhInne2W00LFZLBZ56I4+G7D+x+ze3/WNb5GSq7eNlVwnTJgj5AXJBjtAV/uD7cmOnJ3h+lOkvz3Wry10BaICTAx5SVfOuD5Pr/FLgR2zJau+pgqhRdcp7gG4y2thWN5ZtlLGnJlL7xgnRaD+oIYBn6fg8CCZsnsX2XZHAzJcgntph2pUd0At4UawSijDVZuylcneNdF3CJ68/fcqqpR94LYU61w4WasrAWj1hFiRV1MTpLkdQWTMT6K5k5TDouuljI3nt+Upf9mDK7efjy7OTiiArNxKpUxUy31Kl0TgWTsttDFbcOGwbsxq1t/Tu9Ph0eEhwHc2dTUuSuhV+PHxE8GwUvGiIDBbAZZq7ZyeLc5btdP++8zdXBtYM7bIa9qubaCWakruk31w6Gm5ySjGY+R1HGURldXJmypsAEcu5kK3IX4Z8IubF79T8ZvutLdoXJPSaH9x8ij7EcamTVfZPmekj6FpI3sqy7rmaUup73MBld9a3r9AQTzXbEmR+2xbK488D1il8PUqu9J3QkUeF2rpB97Uy2mgl1zF8VXiGkdP1Hpqeck8FpS89GWFF4FA3oBE/Keqvqvvs/f+KzDn2AvHHwrT6fnVBdeuSp4hK+sSkig1rh+1P69zYjIDqpph0kT+Ri7uNMkbrXX7iT5he9J1LrXtiM/DLxXqi8M3FfeN7aaSxQ2nzac1mlN+vwsIqJbzOSm+ABGHNJNjPjBZpMbeBZsFwogaCbFd2oorp02h6yC6741qwH7gFEr1EJVcu+TJspHyKpq4TFIk+srRMhuCqwUAkdzY4skjEGVhxxYMiKAtRg5IKYICqbfNsestMipAcf22teriGnmqBe9Yuzq9IXZXRcl/etagr0/B3SQhNlVj2u9VMBfhj5ZY+QcQDfSjqOcW/DyphyBU4T1/rHn1x5RznCvtbOPZ+XXFApZaeoKzj/s8JxHzM8aegITx3jVRwXnr0/0FnHTnC34b124K6HM3oKxdPdrtACgW8gH+mgXCaStBqwBdk1aV62+YQxWdHXjy9FAvTF8BktcxwE0TXC5Qu0JsoS8uYbbQmaQCNWIJWF/KptW+g9gkKDBV2Iq5mXwMLHzCtzxGarj5jqznVEX4OdbYbgSOZ5mB5JEU2OwSn2QtzqUVquAN+DygbDVrh1+KA9bk4HMt38rMKHV/i9VBHkuApYT7OUIexCu4jxUDBnid/Hnm3OfMvXrUesoqFv2cYEHX8R/jvhLx36jEWeaTYL+e9XFpSLlWJTGc7wAg0Zj+XJlNtLhFlB6pw" -+DOTENV_VAULT_CI_VERSION=8 ++DOTENV_VAULT_CI="XGJ7HhbwC6C7beVXbifG+tkHOTHTPNHoECye25INPUQmK9vxQ9RObeQjfhLGd2Qkj1bsHDU3VGEjuReB7OxZ7YSFF1xaFoVPdm4x+Rwlr/vbVEy0t4JeUGYA54aNPQYaQBmCF0QVKSI0l7O+WWX7Th62nGyDYwwVOs4PIXwGf1Zr+mtmmJSOuJT8jPQdTfFTKVi/ntWrxl3CBsflW4xQIWsvnoHyDy161gg/HNmmnmdqDYK+roUBokBc1X2rBoSI+vmUUAg+M0rejg8HBs8ZKFDDBXPYOrcnQR306VYrVZjoS0HaA1pZNROXbUaUS2lxl74mP1vD3nV5oQ79coPc9FcwrIMBpv3V7gjdHSM/D/Scf3Wkq+nkk0YsysebRg3qBv/pOy5zb53+NF2AfHZ2drcW4X5QZng6haCqwaDuBDBetro9NECTuMTxuVR8Qfd+NlCXP9RVxhmVMETBYbABSzj2uXKPfCtc2QbtJULXtdO7BkW2ED7c40MGO2NDdEbv8NzqwPqPzpVjtw86YrzHH1y0pgfoEzGPXv6A9QQlt2Ns2EPHjPM87swrTuOGr/xHfpm/j6X8+kB4GwsJehes+BELT6UM9+maPExap/qh1c4WKCwrUYxLD82nixmzxARsqyqJYIoD57AzPeWeqwrxy2nNmKuN/xuau7u/mZqbXwpbE0RIs2zbREZazHDiD8GOL/kXAYhGOQABXrTpl6mFsNVU0OPWE6VkxA90YNxG0kTIBHYaOd2IbPDxPFYbtIGZNCYTn50+7JwPfl56fbVAM5mbvTGaQPo/74H92E3akWFBCA2Wa1yUn1jhfoYzGYrRj+CBCtJLpZI/JIfsVeKdVZ+3/osTb2UdN7humZlrpKzrFqcIKCsbYB2Y55R+LrbuQzbqKl7JUT/y0obboa6jBtO2VFzsF1cJDh3UCCQ2BdBXu5KYhk8BdM1SKWN/p+kteGSgdOuxs9dZW/z/p2Ycjd1cNZjDahN4Ye5HBtiNEXuej7o7SaF5BQC/fqQpSqvVKacxkNL+0X/UR+HUJE1t8d8jEwfT9WE0zxjnraDVxMbC6EHbPV44hUoYyclaKraSAgpaZuW0dkikWrTbyl8SMJPGsD5WZU5gps9i9SfY7y6R2Ruc7vxPRRYb25NTZap6L+hkWeXVM50tKDCIs6cWFHNF7oW9oF72WdZVziWm7kWX7Q0BjNT76od+CMD9tyHo48qSEmYed22cXCMV+dIsQtXwz8y0S8xuaBs1dHsHcEnF6sOGFsRlLaYbXbJiBOFqzqW2iKFdu/CMEJ8qmssxpKQfyy93mOSragacJ3D6iVbTvDaHZ+KbFtIdk+w368MaA/9xEvvdkDJX+u+wqA13SpXkOr2T0Czip1h8fiEw41Jc5cGmMLZyPo1d9dDnc9vyFtfDxGZxQ38BpEmDZcy21cM57TuhLmlZ0U15aiD122rZfdWGkKjtG2854MEJDoLVZRZHhxpSqdhEKRHCgFvhjHYU/FjjaBSGu0g5lOzbc3V/Un8U9Ix5KwMx+VAahGAF2pLaYRmNof3IXTzJC9xPwj+IsO7jTPtsBuJFjSjG" ++DOTENV_VAULT_CI_VERSION=9 + +# staging -+DOTENV_VAULT_STAGING="nAXVAHQaoF1R6J23PDrfp6kH0L+nXmZaI0OUfcCBU7QSfP+K3mbkdO92cbKVUN6ycc1wxRue6BFLEUgBLuCJQwUdKYevK81fs/ACrHYxuW6Px85FG3NJVLGvgF/3qiqu0RRfcpeB6lYJCAPFsYcoA9Cx8QDkYmvSlWAGmLJQ7cqzHUNlveOqtrXsLFV2OjlWjlYD1l7VQkaXnsY+Mij60DbRIadusoDuAB3z5m7N8/ogMOGENlUl9HRJJ4ppcJiHoBm7pxrLpK0oubx3KEDTSkPVlpNb1YYMeU2VKiGYCOMyXX8IguISrrgU4hHEvbLM8F3gnKHyx59qbwU/32yX7ofIIPsTLLTwD+mHWElPoLs9WWq249RYEmRT7w5iuCdjl9Qlr36EJKPYc7UG+P0DZROb7pXcbtYg8/gfaCXOQG5Ue2FMon4bEphkmY1CKaJKtPTXNuecDVTa8O4hEM65xrEClieVUgexEaM0g7fEUo8wOljZx96gG8Mf4RqHX3xy4M0d4/2UluE053kavUaTULhRPG/oTHIGiKFRnG0BfgQFcGRXkFgLG9zerVAoWEGAcBGHn5NECIUWqaD2Z7sdEZd9R0SZmtwUXoPV1jqHNeKEvt7fUN8xcPsV1MFAUi2cAyGLkrl541KbWpU0IpHK1pIkbfUVyDOL4htSD/SajhtlyJ79q7Xd5sKsb273ZQsmtk3ZREBjQUFA2NP+fPH7ifttVeWi3EFUi8+8cWwgNHM6Cn7LaFitah6+DcCQ6+l7SneFTEbzMlprzazBBL/Gu4kr6pNXGX0RZnR8uhs6aZZgV2h9V14s0uZx3EcAFqC3g+I5X08Fi+2ERpcfWUvFdjx5akmDQllSB+oqy6SxiJvqFnfWvVPQNhx1xh2x8OplZbzykEJ+Tfl0mlc5UHHoLQqje0jagBRSHWQbxC0jIGGyqdhnz3uG672krM/zTTc2wKWMk4EZFDG7Yc7oz3DcJXjFBgxEagd9jYxqLIN0RAH9pq+0aUP7daz0CbX+xhTd988dl+LblcnEN59XEsBOhWCd5bm51KJZcKQ1UpsVVPGHnTT7MPE2+6BheEr1dgu/LDUPDW8NT0QwBUwXwxH1ROuTOu2+LWNdFrV11sEpKGfW2bjqrkpbx/xovahbFyCSq12T0EBInAFF5bq2pB0Ik2vfEH8gNL0Z+FoqH0/aDrSuw6j8xqmd2rKL3XwL5bYVZDCc1isAWXESA9CHHTSx1NGoa1kpunJkGJY//2rn5srK9RlOOHxxUrtqVBMfEGsj7qIv/Ebsgr3dvFpdVXiRZ5s3KZZs6YlSXyukklehkjtYoMM2TaZ478ioFOtD8yx12PjI3Pfoapo2La2boM48RFcipmVNpWNsLjGXaYyhYVV3RBsZAMvkOnYRVFUiZ50+ETdfizX0H23+mmkBt8FjNqHhvg9BIRaYnKTar1FPAd840lo4w2e7n2mKufHwykI5ILrJV0ozhPy7Yr8kltPiQYjpy1ua2scUdnKCngdX0KjqNQz072e6uYlhRpOib6CEwZXqMbr/" -+DOTENV_VAULT_STAGING_VERSION=8 ++DOTENV_VAULT_STAGING="YrzhnLjbj2iFnQUJpisMUL3I0Z0xD/45ZCpyo6N73EcX4465FwJ5sVw9nGjq/+UIxPJKvEk4ibq907PIPAHkSY3+j+Dsx3aydlokbgmv2X1OjVK4QmoCmwYRHBChTgbIkDlwhbjxbiiRiu6S8lQmFrDe4DcstI4gtp6x5PbdNKbYIKrDU+iXgLh8vlul6emavsloUKAyAf/5yaobAyQSJFkEDOmHdHiFGnwkxJeuwn3BCyPZ16mg3vx7BmpVdNSbk1coQ2HTsYPfoZsKZKzc/Oon+qo/FSQ7+fyp+8+CJvpINUm9Xkn40kN1IBVJ3ZZowdJ4rYPW2Uz0oD0bLGv6jjZVAD0z8HVOpBbcaqfEwWx9b6NJ1j7xg2GiZ6321V93D14LJL6KT/Sl4391j8CUhEKTEXCNtKMchyJyo+N/hlE3BAk2Yv3llw2YW4w9FSxecbuKni2ZlH6AGGupEv3VNzrlFhyvoBKQlFbLSnrEMIlUJ0Bo0KhQF6lBlJUdBiR4KsO+8WBbj7qn25rlDp++XBshivWf08NpzMB1+fHQ05QS+gwYeLtnhD8VxvCm3nHpHPCw2zZrhWZQ7Z1n7hA2qxknp/Hhs4agY3I7ZmeGDGYwAID8RcVGGyqE7VU8eu7x1OWqrDbtCzWRgeoHG1yB2upUQ3taKyyvUdeh4bwDXp5M0ZOsh+0+X4ZphEV81s4D0ABVifp9eu+PUob/kJATutNWNCsgysO8nzryACKAXb4WOHItw5DuNdd2L4y9gWcOC5Ecf44NEhO4Zr/E9TXNSavQR0SiTTsCRodAIG0k1VDJnzdV/GI7dXGcnx/pI92bAD8Evubh3oxROCwuyIb0ch6foQA/wjygujHn6wZq+TyQVrhklzKMcxiBtifkDVL9ROjQ4nJ3eOzZ6P5pLb+s42DlS/SD94XnqCSLmnh2Nok5FhMWVI3PZN76xcqvC/+0ctwMiS04dcefvBb6Aegyyp9MoDnG2z5BwsGi2YY1mj3U3baDEeMRc4WF6y+Ac9BC7LvgMgA0W8lODRuChRBfEx9Z6qBhJAhHwBUdERg9Nm3maQU1rsBMmSQ7rt6DyEc4b0HWJdy9UMq6Q33wTdCihPBhMxZPEF6x6k8Y0jcfyzu5JWIH08QtiP0CYwA6dke3E7grS9GxHkRW/AZmCuLNSuKpwLLb6f1Y+pSIaeyI8k6jhuWoN8kLaaCgHBt+eo3jL/0A4fjaSdjYCCbJgDIBdW4Ku8PcxocKymvPd4IBhRjXQiKdmL6wjYQqaz7pCEyxkq0n8Z2h8CnnjuaUUU710keHI/MnfvlUGtSyjFHIwS4RGpU8F/Y9KTvGKpbjGpvWK6+Gie959VR1u/NdgM4mlgJOzI8xqSm8jfNL5eUegnGqor4CbWrn1vECEqbcBaGI5Lzw/0T6zrEErV6QDdihMjJE9ouBEihUtoesRKd3q9zza/4AlLb/a/kulVX33N4VNSxaidEYat6H09S0qUDxcWD92GxHuxxhMU9X0U+YrswMXzUwGEkDd2Hp4IPqsJ6tG1lORgHV2JLoTeQmPob5fzokIapakE+g78MiWlgn" ++DOTENV_VAULT_STAGING_VERSION=9 + +# production -+DOTENV_VAULT_PRODUCTION="zGpYx7O6vRv3vYt0aW1H6UF3TSi6DUFgqjeKzkNcJukFWQWvTgX5HYtLKQnBAGCDpuWY+KWfiFGNu/zVouo5LTlQb6apzBLzj0dr+PxxDpjXe8s+q7OSPH7Ist4UiVurC2nAMjgFsUpDA2LUkfPOuWPRvg1UGYjTaxEADTYCI/vk0Uegv4R11K1/HcCGXlliJk2QAVaT5ZYSo0VKvcRu5HYHSX4aAwJgnucm3xvNmPn9Cjkxx1jZL5jYfzLS1etDEPn6slDj88NzZ8BvXHeSJUftpR352azOJpL0GbSKrwoPNYU9F2JCthilLqPZPC6LJzg0/4p5vUKA83gqnDnUxEHR+ZlNK0MRTQPEwrZitruOaP1ggWIsEJA/DZFMLA5oQrq4kp+p2E99YbsPNmV0LfvsA6LtqKv393jsuzSdJ94zeLhQlRRECiTQRVEee3ug5tJ0f34N7acRSv4EdgriKp8poIsE4miVK2xXwrek9jRxeXLHzu7Oo8yBkYaX2DqtXyxf+8Qq2WgvdRm9QgfGdiwftYsjXO52jhHI7z7bJ3R/W5BR+Z0QbR83LR3ZWdI+AYY9F2CkYF62nW5TVxiWU0M4DZ5KUgVo+iNgm+AV0nefgFvud6Ln3VtNcb2bT0RFAw4MJK0LyYlNewZBJQhlfRiN+3xJKqLbDKMlhGmSHF8RRNYN9MfKwOfF62jNFlwPjgyGCjuQL96L97z50Q63TMbpEciR0uNcMavIADPs1qDhCSkYO4rQO1IgOUF34trra9KKRfoP2UdDJhiqvlTQ2Ygu2uc164Vho0E003jlBBZBI+UUJhPz4cYTtFJjpinRCp/Q0mUeiTLbo1V7T32IQ+ZkUqO7FRuwxR282OzKQVRZiUxuOAa7WxdQzrcXbCOcCvJWX8hTVrSim9z+NAFxNyTCwVBhek9EfwCfxJSCuFuI9axlb6xA1sZ/61G5+wuKeEpUvlTbQC/ZT1QJqpKMmujEePAfW++dzJJgW/nX9o0CIt3KTzwVzImAe+/oNhzKn0TbzsvMqeOaTgymo2re0Rdw3P/Fk5SUQTM7+2GIFC+iRC5a3LN7UOFP+UdQ21J+WGkkl+yvJyyjY3IRSoFWqSCU7BAjULmJmtkl1Y/60vIj+GET5hu7VGaTlIgaWiVZc4eGAu6UAeGzasTjYWNQh50p44VxQlYBijJC+JUhzPkFOLjaLx6rRZzTQVl9NUxIxdkaOn+BImJQadaC2Ad3YOMpsLVJF/p25fi2L2e2MtsDdcx2ZNlpV1GIrYGqMIiujPdpoMzibj4VmObVMB6dazpyqgAf/QXdGguyv/4JJBcNUNPbqHqMl0yY+DzGzxPCKx6WViR63HZJeC5hJE39wNEgeYGJcXrL5K2siwrFENns8Os93aoE/8xgiPdj3vLQmcL0Z30XEqPl6/V4mTgc2Oa1SAi1NGLXnVsYJ/h1Rr4a9T11zkKhRCIvT4Jd5WpvGjG3f9t7YUigb6BqlMc0BTJ6ufQP/xBBOHqzoUs3gVk4HTMDfpYwO9k9SJNkfWBn1iV102BJ" -+DOTENV_VAULT_PRODUCTION_VERSION=8 ++DOTENV_VAULT_PRODUCTION="nwpPZ519tEw+H7zTYMlCASEl+aTG5sDW2aUPGzKkE595ORAtfhxrx+jpiWRFruCKX1SIbIjb6uFWcQXVO0nBcgIT69gDygY7vE5SjXe/KLyWiySsJ3gcpiuk3m/qSiYasEjOR8tKOdQji6omwCn8oYpK9K/joO9XE7U+HyVzqSMkuwrW2Gawnr/qmIcQRQFj03+Lm++2V/oDEbpFLW8JCO2TZUc62iJsKNV2g7AemTGb7O3b/rb1QuoXVikUVh+PhBRzIXSIrsEHevEV94fV9uozZ5EvBrEIUao8XH6o/qiuOUcbp2i8Nzdo0jKA0kxh7hPFJQHmfk7on5wJKW2PHXWLb0k+/gbDG23vnkU5ndunZvjRnMZg0w30qmA9z0kMus3qCHcKp88dVaFDyIyUBv/PoFrRJPSX8alOL2HWqr9gqGc/TvcLwNwEkM5IZFBNpC8Xj/ka/RNxabS9YnSMYCGe080los5juy46rNw6qSVf6LTPUCoO5KMi3WeeM0eGCoM8wFZQEZS8J6FHMSXQbjg4X2k71wq6DNhnMleG7qQHXECxKRPnnrveJdowAulRlHhVISqRRnMLQZ59PXw5dKg8jucwsaARvgNQvRf/6A/I6+FenX83Rej+hmpXaWR/NCSC/WMxbnRhdAKplv6G9pdKAz4pI3n+hNP+WNPeAnlVSdxGeWNGh9ndYOGzCG0om7McBu2h0NjzrViHlMz6WbY1UfCruJ9zRjlpu5LXQv77C52NSVPrkk0lMX0FrwvCM3qTwu56VYQ7/A9+ZEfi2X7iKXGBjzavVmncPDLy8M9QhA9A8mqzAlBRxnqM2xr4B16f2uKQyp2KQt43kNmfhUkFPNcyp5hrSFMKkHA85q9uemoDYNgC+09/0hkH1dcg6NoKGf18PZl3dKstOIBSQhU4tU0IyL6ZY6PKveMotuJxA0i15I/ClRbpffhfEL0vbWuBTfdh9J0qmvNJoHtPiZlLpojBvCpIKLkf5bHj8Ucg/EReJ7Rc1830N2cEj5YROH8YV7C4Lox8yPicjXlUk9OsvyKXOSXrWpdsUFKtWc2OVvI53OgrWp8Ct1kqTAxRTT9Dzp/PPKVapirulDa18DLL6qVu7elapRU+9wXB5bmwbtam75l6KK77EVsP9WVVuBY6ciiTWoCKDf1Y5tYo4rb/yABQzybcfBVl9dSz5smGZh1Lp3/HeOekCASJZ95WjGt2cBd6PeIjambljFrp+n0oUiDOdOVi5Wp0j6P8NkO5s7W+QzrnQJSlaN0M5iYhXz3VKo8gnTyrw0+iJ7xQBVFgDhzkfsG4YurbiUdOEcpMBvnxACMhlUl5mq6srh+QJSjUPJEviLXB3sk2Ok0JI/UkDq+CwpRtLdpdhNV1Tq12XjjCeB8uNpJt5zUWENnH4BVDHsSmheFczJ45zibCDI2pA7H4IwAgbDEUgvITHj8aBwhvXkAso+nte6GUQrv1Hy9qitpAmGTP0VZXsd7ZWa+kJjT3kJHYbgd8O10Tx+qu9lZ5Fx94wgIATdX7ey5G7D8rVNAsd8IL9PPgYyPcwNTsdmj9ms83pjmWL3FX" ++DOTENV_VAULT_PRODUCTION_VERSION=9 + +#/----------------settings/metadata-----------------/ +DOTENV_VAULT="vlt_47e3eeb0730e831e688049600e59f8975260a1f00302ae08684ed87ba67872d0" diff --git a/opensaas-sh/app_diff/README.md.diff b/opensaas-sh/app_diff/README.md.diff index fe343f709..7612ff16b 100644 --- a/opensaas-sh/app_diff/README.md.diff +++ b/opensaas-sh/app_diff/README.md.diff @@ -1,6 +1,6 @@ --- template/app/README.md +++ opensaas-sh/app/README.md -@@ -1,6 +1,8 @@ +@@ -1,12 +1,27 @@ -# +# opensaas.sh (demo) app @@ -9,10 +9,6 @@ + +It is deployed to https://opensaas.sh and serves both as a landing page for Open Saas and as a demo app. - ## UI Components - -@@ -8,9 +10,22 @@ - ## Development +### .env files diff --git a/opensaas-sh/app_diff/deletions b/opensaas-sh/app_diff/deletions index 90fe220da..870bd9c1f 100644 --- a/opensaas-sh/app_diff/deletions +++ b/opensaas-sh/app_diff/deletions @@ -1,9 +1,13 @@ -src/client/static/open-saas-banner-dark.png -src/client/static/open-saas-banner-light.png +src/client/static/open-saas-banner-dark.svg +src/client/static/open-saas-banner-light.svg src/landing-page/components/Hero.tsx -src/landing-page/contentSections.ts src/payment/lemonSqueezy/checkoutUtils.ts src/payment/lemonSqueezy/paymentDetails.ts src/payment/lemonSqueezy/paymentProcessor.ts src/payment/lemonSqueezy/webhook.ts +src/payment/lemonSqueezy/webhookPayload.ts +src/payment/polar/checkoutUtils.ts +src/payment/polar/paymentProcessor.ts +src/payment/polar/polarClient.ts +src/payment/polar/webhook.ts src/payment/webhook.ts diff --git a/opensaas-sh/app_diff/main.wasp.diff b/opensaas-sh/app_diff/main.wasp.diff index 66f1cec67..a52ff7b5d 100644 --- a/opensaas-sh/app_diff/main.wasp.diff +++ b/opensaas-sh/app_diff/main.wasp.diff @@ -1,7 +1,7 @@ --- template/app/main.wasp +++ opensaas-sh/app/main.wasp @@ -3,31 +3,32 @@ - version: "^0.18.0" + version: "^0.20.0" }, - title: "My Open SaaS App", @@ -13,11 +13,10 @@ - "", - "", - "", -- + "", + "", + "", -+ + + "", "", - "", @@ -107,7 +106,19 @@ }, }, } -@@ -207,9 +205,9 @@ +@@ -101,6 +99,11 @@ + component: import LandingPage from "@src/landing-page/LandingPage" + } + ++query getGithubRoadmap { ++ fn: import { getGithubRoadmap } from "@src/landing-page/operations", ++ entities: [] ++} ++ + //#region Auth Pages + route LoginRoute { path: "/login", to: LoginPage } + page LoginPage { +@@ -207,9 +210,9 @@ } api paymentsWebhook { @@ -119,7 +130,25 @@ httpRoute: (POST, "/payments-webhook") } //#endregion -@@ -281,7 +279,6 @@ +@@ -245,6 +248,17 @@ + fn: import { deleteFile } from "@src/file-upload/operations", + entities: [User, File] + } ++ ++job deleteFilesJob { ++ executor: PgBoss, ++ perform: { ++ fn: import { deleteFilesJob } from "@src/file-upload/workers" ++ }, ++ schedule: { ++ cron: "0 5 * * *" // every day at 5am ++ }, ++ entities: [File] ++} + //#endregion + + //#region Analytics +@@ -291,7 +305,6 @@ component: import AdminCalendar from "@src/admin/elements/calendar/CalendarPage" } diff --git a/opensaas-sh/app_diff/migrations/20250731133938_drop_upload_url_from_file/migration.sql.diff b/opensaas-sh/app_diff/migrations/20250731133938_drop_upload_url_from_file/migration.sql.diff new file mode 100644 index 000000000..1a2122bb3 --- /dev/null +++ b/opensaas-sh/app_diff/migrations/20250731133938_drop_upload_url_from_file/migration.sql.diff @@ -0,0 +1,11 @@ +--- template/app/migrations/20250731133938_drop_upload_url_from_file/migration.sql ++++ opensaas-sh/app/migrations/20250731133938_drop_upload_url_from_file/migration.sql +@@ -0,0 +1,8 @@ ++/* ++ Warnings: ++ ++ - You are about to drop the column `uploadUrl` on the `File` table. All the data in the column will be lost. ++ ++*/ ++-- AlterTable ++ALTER TABLE "File" DROP COLUMN "uploadUrl"; diff --git a/opensaas-sh/app_diff/migrations/20250806121259_add_s3_key_file/migration.sql.diff b/opensaas-sh/app_diff/migrations/20250806121259_add_s3_key_file/migration.sql.diff new file mode 100644 index 000000000..b72ee9c56 --- /dev/null +++ b/opensaas-sh/app_diff/migrations/20250806121259_add_s3_key_file/migration.sql.diff @@ -0,0 +1,14 @@ +--- template/app/migrations/20250806121259_add_s3_key_file/migration.sql ++++ opensaas-sh/app/migrations/20250806121259_add_s3_key_file/migration.sql +@@ -0,0 +1,11 @@ ++/* ++ Warnings: ++ ++ - You are about to drop the column `key` on the `File` table. All the data in the column will be lost. ++ - Added the required column `s3Key` to the `File` table without a default value. This is not possible if the table is not empty. ++ ++*/ ++-- AlterTable ++DELETE FROM "File"; ++ALTER TABLE "File" DROP COLUMN "key", ++ADD COLUMN "s3Key" TEXT NOT NULL; diff --git a/opensaas-sh/app_diff/package-lock.json.diff b/opensaas-sh/app_diff/package-lock.json.diff index 1ee8bd4f0..337712046 100644 --- a/opensaas-sh/app_diff/package-lock.json.diff +++ b/opensaas-sh/app_diff/package-lock.json.diff @@ -1,6 +1,6 @@ --- template/app/package-lock.json +++ opensaas-sh/app/package-lock.json -@@ -0,0 +1,14338 @@ +@@ -0,0 +1,13572 @@ +{ + "name": "opensaas", + "lockfileVersion": 3, @@ -8,14 +8,16 @@ + "packages": { + "": { + "name": "opensaas", ++ "workspaces": [ ++ ".wasp/build/*", ++ ".wasp/out/*" ++ ], + "dependencies": { + "@aws-sdk/client-s3": "^3.523.0", + "@aws-sdk/s3-presigned-post": "^3.750.0", + "@aws-sdk/s3-request-presigner": "^3.523.0", + "@google-analytics/data": "4.1.0", -+ "@headlessui/react": "1.7.13", + "@hookform/resolvers": "^5.1.1", -+ "@lemonsqueezy/lemonsqueezy.js": "^3.2.0", + "@radix-ui/react-accordion": "^1.2.11", + "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-checkbox": "^1.3.2", @@ -27,22 +29,20 @@ + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-switch": "^1.2.5", ++ "@radix-ui/react-toast": "^1.2.14", + "@tailwindcss/forms": "^0.5.3", + "@tailwindcss/typography": "^0.5.7", + "apexcharts": "3.41.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", -+ "headlessui": "^0.0.0", + "lucide-react": "^0.525.0", -+ "node-fetch": "3.3.0", + "openai": "^4.55.3", + "prettier": "3.1.1", + "prettier-plugin-tailwindcss": "0.5.11", -+ "react": "^18.2.0", ++ "react": "^19.2.1", + "react-apexcharts": "1.4.1", -+ "react-dom": "^18.2.0", ++ "react-dom": "^19.2.1", + "react-hook-form": "^7.60.0", -+ "react-hot-toast": "^2.4.1", + "react-icons": "^5.5.0", + "react-router-dom": "^6.26.2", + "stripe": "18.1.0", @@ -56,12 +56,61 @@ + "devDependencies": { + "@faker-js/faker": "8.3.1", + "@types/express": "^5.0.0", -+ "@types/react": "^18.0.37", ++ "@types/react": "^19.2.7", + "prisma": "5.19.1", + "typescript": "5.8.2", + "vite": "^7.0.6" + } + }, ++ ".wasp/build/server": { ++ "name": "@wasp.sh/generated-server-build", ++ "version": "0.0.0", ++ "dependencies": { ++ "cookie-parser": "~1.4.6", ++ "cors": "^2.8.5", ++ "dotenv": "^16.0.2", ++ "express": "~5.1.0", ++ "helmet": "^6.0.0", ++ "morgan": "~1.10.0", ++ "superjson": "^2.2.1" ++ }, ++ "devDependencies": { ++ "@rollup/plugin-node-resolve": "^16.0.0", ++ "@tsconfig/node22": "latest", ++ "@types/cors": "^2.8.5", ++ "@types/express": "^5.0.0", ++ "@types/express-serve-static-core": "^5.0.0", ++ "@types/node": "^22.0.0", ++ "nodemon": "^2.0.19", ++ "rollup": "^4.9.6", ++ "rollup-plugin-esbuild": "^6.1.1", ++ "typescript": "5.8.2" ++ }, ++ "engines": { ++ "node": ">=22.12.0" ++ } ++ }, ++ ".wasp/build/web-app": { ++ "name": "@wasp.sh/generated-webapp-build", ++ "version": "0.0.0", ++ "dependencies": { ++ "@tanstack/react-query": "~4.42.0", ++ "axios": "^1.4.0", ++ "react": "^19.2.1", ++ "react-dom": "^19.2.1", ++ "react-router-dom": "^6.26.2" ++ }, ++ "devDependencies": { ++ "@tsconfig/vite-react": "^7.0.0", ++ "@types/react": "^19.2.7", ++ "@types/react-dom": "^19.2.3", ++ "@vitejs/plugin-react": "^4.7.0", ++ "typescript": "5.8.2" ++ }, ++ "engines": { ++ "node": ">=22.12.0" ++ } ++ }, + ".wasp/out/sdk/wasp": { + "version": "1.0.0", + "license": "ISC", @@ -71,7 +120,7 @@ + "@prisma/client": "5.19.1", + "@sendgrid/mail": "^7.7.0", + "@testing-library/jest-dom": "^6.3.0", -+ "@testing-library/react": "^14.1.2", ++ "@testing-library/react": "^16.3.0", + "@vitest/ui": "^1.2.1", + "arctic": "^1.2.1", + "autoprefixer": "^10.4.13", @@ -85,11 +134,11 @@ + "pg-boss": "^8.4.2", + "postcss": "^8.4.21", + "prisma": "5.19.1", -+ "react": "^18.2.0", ++ "react": "^19.2.1", + "react-hook-form": "^7.45.4", + "react-router-dom": "^6.26.2", + "superjson": "^2.2.1", -+ "tailwindcss": "^3.2.7", ++ "tailwindcss": "^3.4.17", + "vitest": "^1.2.1", + "zod": "^3.23.8" + }, @@ -98,7 +147,56 @@ + "@types/express-serve-static-core": "^5.0.0" + }, + "peerDependencies": { -+ "@tanstack/react-query": "^4.39.1" ++ "@tanstack/react-query": "~4.42.0" ++ } ++ }, ++ ".wasp/out/server": { ++ "name": "@wasp.sh/generated-server-dev", ++ "version": "0.0.0", ++ "dependencies": { ++ "cookie-parser": "~1.4.6", ++ "cors": "^2.8.5", ++ "dotenv": "^16.0.2", ++ "express": "~5.1.0", ++ "helmet": "^6.0.0", ++ "morgan": "~1.10.0", ++ "superjson": "^2.2.1" ++ }, ++ "devDependencies": { ++ "@rollup/plugin-node-resolve": "^16.0.0", ++ "@tsconfig/node22": "latest", ++ "@types/cors": "^2.8.5", ++ "@types/express": "^5.0.0", ++ "@types/express-serve-static-core": "^5.0.0", ++ "@types/node": "^22.0.0", ++ "nodemon": "^2.0.19", ++ "rollup": "^4.9.6", ++ "rollup-plugin-esbuild": "^6.1.1", ++ "typescript": "5.8.2" ++ }, ++ "engines": { ++ "node": ">=22.12.0" ++ } ++ }, ++ ".wasp/out/web-app": { ++ "name": "@wasp.sh/generated-webapp-dev", ++ "version": "0.0.0", ++ "dependencies": { ++ "@tanstack/react-query": "~4.42.0", ++ "axios": "^1.4.0", ++ "react": "^19.2.1", ++ "react-dom": "^19.2.1", ++ "react-router-dom": "^6.26.2" ++ }, ++ "devDependencies": { ++ "@tsconfig/vite-react": "^7.0.0", ++ "@types/react": "^19.2.7", ++ "@types/react-dom": "^19.2.3", ++ "@vitejs/plugin-react": "^4.7.0", ++ "typescript": "5.8.2" ++ }, ++ "engines": { ++ "node": ">=22.12.0" + } + }, + "node_modules/@adobe/css-tools": { @@ -322,117 +420,114 @@ + } + }, + "node_modules/@aws-sdk/client-s3": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.888.0.tgz", -+ "integrity": "sha512-MgYyF/qpvCMYVSiOpRJ5C/EtdFxuYAeF5SprtMsbf71xBiiCH5GurB616i+ZxJqHlfhBQTTvR0qugnWvk1Wqvw==", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.940.0.tgz", ++ "integrity": "sha512-Wi4qnBT6shRRMXuuTgjMFTU5mu2KFWisgcigEMPptjPGUtJvBVi4PTGgS64qsLoUk/obqDAyOBOfEtRZ2ddC2w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", -+ "@aws-sdk/core": "3.888.0", -+ "@aws-sdk/credential-provider-node": "3.888.0", -+ "@aws-sdk/middleware-bucket-endpoint": "3.887.0", -+ "@aws-sdk/middleware-expect-continue": "3.887.0", -+ "@aws-sdk/middleware-flexible-checksums": "3.888.0", -+ "@aws-sdk/middleware-host-header": "3.887.0", -+ "@aws-sdk/middleware-location-constraint": "3.887.0", -+ "@aws-sdk/middleware-logger": "3.887.0", -+ "@aws-sdk/middleware-recursion-detection": "3.887.0", -+ "@aws-sdk/middleware-sdk-s3": "3.888.0", -+ "@aws-sdk/middleware-ssec": "3.887.0", -+ "@aws-sdk/middleware-user-agent": "3.888.0", -+ "@aws-sdk/region-config-resolver": "3.887.0", -+ "@aws-sdk/signature-v4-multi-region": "3.888.0", -+ "@aws-sdk/types": "3.887.0", -+ "@aws-sdk/util-endpoints": "3.887.0", -+ "@aws-sdk/util-user-agent-browser": "3.887.0", -+ "@aws-sdk/util-user-agent-node": "3.888.0", -+ "@aws-sdk/xml-builder": "3.887.0", -+ "@smithy/config-resolver": "^4.2.1", -+ "@smithy/core": "^3.11.0", -+ "@smithy/eventstream-serde-browser": "^4.1.1", -+ "@smithy/eventstream-serde-config-resolver": "^4.2.1", -+ "@smithy/eventstream-serde-node": "^4.1.1", -+ "@smithy/fetch-http-handler": "^5.2.1", -+ "@smithy/hash-blob-browser": "^4.1.1", -+ "@smithy/hash-node": "^4.1.1", -+ "@smithy/hash-stream-node": "^4.1.1", -+ "@smithy/invalid-dependency": "^4.1.1", -+ "@smithy/md5-js": "^4.1.1", -+ "@smithy/middleware-content-length": "^4.1.1", -+ "@smithy/middleware-endpoint": "^4.2.1", -+ "@smithy/middleware-retry": "^4.2.1", -+ "@smithy/middleware-serde": "^4.1.1", -+ "@smithy/middleware-stack": "^4.1.1", -+ "@smithy/node-config-provider": "^4.2.1", -+ "@smithy/node-http-handler": "^4.2.1", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/smithy-client": "^4.6.1", -+ "@smithy/types": "^4.5.0", -+ "@smithy/url-parser": "^4.1.1", -+ "@smithy/util-base64": "^4.1.0", -+ "@smithy/util-body-length-browser": "^4.1.0", -+ "@smithy/util-body-length-node": "^4.1.0", -+ "@smithy/util-defaults-mode-browser": "^4.1.1", -+ "@smithy/util-defaults-mode-node": "^4.1.1", -+ "@smithy/util-endpoints": "^3.1.1", -+ "@smithy/util-middleware": "^4.1.1", -+ "@smithy/util-retry": "^4.1.1", -+ "@smithy/util-stream": "^4.3.1", -+ "@smithy/util-utf8": "^4.1.0", -+ "@smithy/util-waiter": "^4.1.1", -+ "@types/uuid": "^9.0.1", -+ "tslib": "^2.6.2", -+ "uuid": "^9.0.1" ++ "@aws-sdk/core": "3.940.0", ++ "@aws-sdk/credential-provider-node": "3.940.0", ++ "@aws-sdk/middleware-bucket-endpoint": "3.936.0", ++ "@aws-sdk/middleware-expect-continue": "3.936.0", ++ "@aws-sdk/middleware-flexible-checksums": "3.940.0", ++ "@aws-sdk/middleware-host-header": "3.936.0", ++ "@aws-sdk/middleware-location-constraint": "3.936.0", ++ "@aws-sdk/middleware-logger": "3.936.0", ++ "@aws-sdk/middleware-recursion-detection": "3.936.0", ++ "@aws-sdk/middleware-sdk-s3": "3.940.0", ++ "@aws-sdk/middleware-ssec": "3.936.0", ++ "@aws-sdk/middleware-user-agent": "3.940.0", ++ "@aws-sdk/region-config-resolver": "3.936.0", ++ "@aws-sdk/signature-v4-multi-region": "3.940.0", ++ "@aws-sdk/types": "3.936.0", ++ "@aws-sdk/util-endpoints": "3.936.0", ++ "@aws-sdk/util-user-agent-browser": "3.936.0", ++ "@aws-sdk/util-user-agent-node": "3.940.0", ++ "@smithy/config-resolver": "^4.4.3", ++ "@smithy/core": "^3.18.5", ++ "@smithy/eventstream-serde-browser": "^4.2.5", ++ "@smithy/eventstream-serde-config-resolver": "^4.3.5", ++ "@smithy/eventstream-serde-node": "^4.2.5", ++ "@smithy/fetch-http-handler": "^5.3.6", ++ "@smithy/hash-blob-browser": "^4.2.6", ++ "@smithy/hash-node": "^4.2.5", ++ "@smithy/hash-stream-node": "^4.2.5", ++ "@smithy/invalid-dependency": "^4.2.5", ++ "@smithy/md5-js": "^4.2.5", ++ "@smithy/middleware-content-length": "^4.2.5", ++ "@smithy/middleware-endpoint": "^4.3.12", ++ "@smithy/middleware-retry": "^4.4.12", ++ "@smithy/middleware-serde": "^4.2.6", ++ "@smithy/middleware-stack": "^4.2.5", ++ "@smithy/node-config-provider": "^4.3.5", ++ "@smithy/node-http-handler": "^4.4.5", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/smithy-client": "^4.9.8", ++ "@smithy/types": "^4.9.0", ++ "@smithy/url-parser": "^4.2.5", ++ "@smithy/util-base64": "^4.3.0", ++ "@smithy/util-body-length-browser": "^4.2.0", ++ "@smithy/util-body-length-node": "^4.2.1", ++ "@smithy/util-defaults-mode-browser": "^4.3.11", ++ "@smithy/util-defaults-mode-node": "^4.2.14", ++ "@smithy/util-endpoints": "^3.2.5", ++ "@smithy/util-middleware": "^4.2.5", ++ "@smithy/util-retry": "^4.2.5", ++ "@smithy/util-stream": "^4.5.6", ++ "@smithy/util-utf8": "^4.2.0", ++ "@smithy/util-waiter": "^4.2.5", ++ "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.888.0.tgz", -+ "integrity": "sha512-8CLy/ehGKUmekjH+VtZJ4w40PqDg3u0K7uPziq/4P8Q7LLgsy8YQoHNbuY4am7JU3HWrqLXJI9aaz1+vPGPoWA==", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.940.0.tgz", ++ "integrity": "sha512-SdqJGWVhmIURvCSgkDditHRO+ozubwZk9aCX9MK8qxyOndhobCndW1ozl3hX9psvMAo9Q4bppjuqy/GHWpjB+A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", -+ "@aws-sdk/core": "3.888.0", -+ "@aws-sdk/middleware-host-header": "3.887.0", -+ "@aws-sdk/middleware-logger": "3.887.0", -+ "@aws-sdk/middleware-recursion-detection": "3.887.0", -+ "@aws-sdk/middleware-user-agent": "3.888.0", -+ "@aws-sdk/region-config-resolver": "3.887.0", -+ "@aws-sdk/types": "3.887.0", -+ "@aws-sdk/util-endpoints": "3.887.0", -+ "@aws-sdk/util-user-agent-browser": "3.887.0", -+ "@aws-sdk/util-user-agent-node": "3.888.0", -+ "@smithy/config-resolver": "^4.2.1", -+ "@smithy/core": "^3.11.0", -+ "@smithy/fetch-http-handler": "^5.2.1", -+ "@smithy/hash-node": "^4.1.1", -+ "@smithy/invalid-dependency": "^4.1.1", -+ "@smithy/middleware-content-length": "^4.1.1", -+ "@smithy/middleware-endpoint": "^4.2.1", -+ "@smithy/middleware-retry": "^4.2.1", -+ "@smithy/middleware-serde": "^4.1.1", -+ "@smithy/middleware-stack": "^4.1.1", -+ "@smithy/node-config-provider": "^4.2.1", -+ "@smithy/node-http-handler": "^4.2.1", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/smithy-client": "^4.6.1", -+ "@smithy/types": "^4.5.0", -+ "@smithy/url-parser": "^4.1.1", -+ "@smithy/util-base64": "^4.1.0", -+ "@smithy/util-body-length-browser": "^4.1.0", -+ "@smithy/util-body-length-node": "^4.1.0", -+ "@smithy/util-defaults-mode-browser": "^4.1.1", -+ "@smithy/util-defaults-mode-node": "^4.1.1", -+ "@smithy/util-endpoints": "^3.1.1", -+ "@smithy/util-middleware": "^4.1.1", -+ "@smithy/util-retry": "^4.1.1", -+ "@smithy/util-utf8": "^4.1.0", ++ "@aws-sdk/core": "3.940.0", ++ "@aws-sdk/middleware-host-header": "3.936.0", ++ "@aws-sdk/middleware-logger": "3.936.0", ++ "@aws-sdk/middleware-recursion-detection": "3.936.0", ++ "@aws-sdk/middleware-user-agent": "3.940.0", ++ "@aws-sdk/region-config-resolver": "3.936.0", ++ "@aws-sdk/types": "3.936.0", ++ "@aws-sdk/util-endpoints": "3.936.0", ++ "@aws-sdk/util-user-agent-browser": "3.936.0", ++ "@aws-sdk/util-user-agent-node": "3.940.0", ++ "@smithy/config-resolver": "^4.4.3", ++ "@smithy/core": "^3.18.5", ++ "@smithy/fetch-http-handler": "^5.3.6", ++ "@smithy/hash-node": "^4.2.5", ++ "@smithy/invalid-dependency": "^4.2.5", ++ "@smithy/middleware-content-length": "^4.2.5", ++ "@smithy/middleware-endpoint": "^4.3.12", ++ "@smithy/middleware-retry": "^4.4.12", ++ "@smithy/middleware-serde": "^4.2.6", ++ "@smithy/middleware-stack": "^4.2.5", ++ "@smithy/node-config-provider": "^4.3.5", ++ "@smithy/node-http-handler": "^4.4.5", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/smithy-client": "^4.9.8", ++ "@smithy/types": "^4.9.0", ++ "@smithy/url-parser": "^4.2.5", ++ "@smithy/util-base64": "^4.3.0", ++ "@smithy/util-body-length-browser": "^4.2.0", ++ "@smithy/util-body-length-node": "^4.2.1", ++ "@smithy/util-defaults-mode-browser": "^4.3.11", ++ "@smithy/util-defaults-mode-node": "^4.2.14", ++ "@smithy/util-endpoints": "^3.2.5", ++ "@smithy/util-middleware": "^4.2.5", ++ "@smithy/util-retry": "^4.2.5", ++ "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -440,25 +535,23 @@ + } + }, + "node_modules/@aws-sdk/core": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.888.0.tgz", -+ "integrity": "sha512-L3S2FZywACo4lmWv37Y4TbefuPJ1fXWyWwIJ3J4wkPYFJ47mmtUPqThlVrSbdTHkEjnZgJe5cRfxk0qCLsFh1w==", -+ "license": "Apache-2.0", -+ "dependencies": { -+ "@aws-sdk/types": "3.887.0", -+ "@aws-sdk/xml-builder": "3.887.0", -+ "@smithy/core": "^3.11.0", -+ "@smithy/node-config-provider": "^4.2.1", -+ "@smithy/property-provider": "^4.0.5", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/signature-v4": "^5.1.3", -+ "@smithy/smithy-client": "^4.6.1", -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-base64": "^4.1.0", -+ "@smithy/util-body-length-browser": "^4.1.0", -+ "@smithy/util-middleware": "^4.1.1", -+ "@smithy/util-utf8": "^4.1.0", -+ "fast-xml-parser": "5.2.5", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.940.0.tgz", ++ "integrity": "sha512-KsGD2FLaX5ngJao1mHxodIVU9VYd1E8810fcYiGwO1PFHDzf5BEkp6D9IdMeQwT8Q6JLYtiiT1Y/o3UCScnGoA==", ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@aws-sdk/types": "3.936.0", ++ "@aws-sdk/xml-builder": "3.930.0", ++ "@smithy/core": "^3.18.5", ++ "@smithy/node-config-provider": "^4.3.5", ++ "@smithy/property-provider": "^4.2.5", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/signature-v4": "^5.3.5", ++ "@smithy/smithy-client": "^4.9.8", ++ "@smithy/types": "^4.9.0", ++ "@smithy/util-base64": "^4.3.0", ++ "@smithy/util-middleware": "^4.2.5", ++ "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -466,15 +559,15 @@ + } + }, + "node_modules/@aws-sdk/credential-provider-env": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.888.0.tgz", -+ "integrity": "sha512-shPi4AhUKbIk7LugJWvNpeZA8va7e5bOHAEKo89S0Ac8WDZt2OaNzbh/b9l0iSL2eEyte8UgIsYGcFxOwIF1VA==", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.940.0.tgz", ++ "integrity": "sha512-/G3l5/wbZYP2XEQiOoIkRJmlv15f1P3MSd1a0gz27lHEMrOJOGq66rF1Ca4OJLzapWt3Fy9BPrZAepoAX11kMw==", + "license": "Apache-2.0", + "dependencies": { -+ "@aws-sdk/core": "3.888.0", -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/property-provider": "^4.0.5", -+ "@smithy/types": "^4.5.0", ++ "@aws-sdk/core": "3.940.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/property-provider": "^4.2.5", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -482,20 +575,20 @@ + } + }, + "node_modules/@aws-sdk/credential-provider-http": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.888.0.tgz", -+ "integrity": "sha512-Jvuk6nul0lE7o5qlQutcqlySBHLXOyoPtiwE6zyKbGc7RVl0//h39Lab7zMeY2drMn8xAnIopL4606Fd8JI/Hw==", -+ "license": "Apache-2.0", -+ "dependencies": { -+ "@aws-sdk/core": "3.888.0", -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/fetch-http-handler": "^5.2.1", -+ "@smithy/node-http-handler": "^4.2.1", -+ "@smithy/property-provider": "^4.0.5", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/smithy-client": "^4.6.1", -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-stream": "^4.3.1", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.940.0.tgz", ++ "integrity": "sha512-dOrc03DHElNBD6N9Okt4U0zhrG4Wix5QUBSZPr5VN8SvmjD9dkrrxOkkJaMCl/bzrW7kbQEp7LuBdbxArMmOZQ==", ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@aws-sdk/core": "3.940.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/fetch-http-handler": "^5.3.6", ++ "@smithy/node-http-handler": "^4.4.5", ++ "@smithy/property-provider": "^4.2.5", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/smithy-client": "^4.9.8", ++ "@smithy/types": "^4.9.0", ++ "@smithy/util-stream": "^4.5.6", + "tslib": "^2.6.2" + }, + "engines": { @@ -503,23 +596,43 @@ + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.888.0.tgz", -+ "integrity": "sha512-M82ItvS5yq+tO6ZOV1ruaVs2xOne+v8HW85GFCXnz8pecrzYdgxh6IsVqEbbWruryG/mUGkWMbkBZoEsy4MgyA==", -+ "license": "Apache-2.0", -+ "dependencies": { -+ "@aws-sdk/core": "3.888.0", -+ "@aws-sdk/credential-provider-env": "3.888.0", -+ "@aws-sdk/credential-provider-http": "3.888.0", -+ "@aws-sdk/credential-provider-process": "3.888.0", -+ "@aws-sdk/credential-provider-sso": "3.888.0", -+ "@aws-sdk/credential-provider-web-identity": "3.888.0", -+ "@aws-sdk/nested-clients": "3.888.0", -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/credential-provider-imds": "^4.0.7", -+ "@smithy/property-provider": "^4.0.5", -+ "@smithy/shared-ini-file-loader": "^4.0.5", -+ "@smithy/types": "^4.5.0", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.940.0.tgz", ++ "integrity": "sha512-gn7PJQEzb/cnInNFTOaDoCN/hOKqMejNmLof1W5VW95Qk0TPO52lH8R4RmJPnRrwFMswOWswTOpR1roKNLIrcw==", ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@aws-sdk/core": "3.940.0", ++ "@aws-sdk/credential-provider-env": "3.940.0", ++ "@aws-sdk/credential-provider-http": "3.940.0", ++ "@aws-sdk/credential-provider-login": "3.940.0", ++ "@aws-sdk/credential-provider-process": "3.940.0", ++ "@aws-sdk/credential-provider-sso": "3.940.0", ++ "@aws-sdk/credential-provider-web-identity": "3.940.0", ++ "@aws-sdk/nested-clients": "3.940.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/credential-provider-imds": "^4.2.5", ++ "@smithy/property-provider": "^4.2.5", ++ "@smithy/shared-ini-file-loader": "^4.4.0", ++ "@smithy/types": "^4.9.0", ++ "tslib": "^2.6.2" ++ }, ++ "engines": { ++ "node": ">=18.0.0" ++ } ++ }, ++ "node_modules/@aws-sdk/credential-provider-login": { ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.940.0.tgz", ++ "integrity": "sha512-fOKC3VZkwa9T2l2VFKWRtfHQPQuISqqNl35ZhcXjWKVwRwl/o7THPMkqI4XwgT2noGa7LLYVbWMwnsgSsBqglg==", ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@aws-sdk/core": "3.940.0", ++ "@aws-sdk/nested-clients": "3.940.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/property-provider": "^4.2.5", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/shared-ini-file-loader": "^4.4.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -527,22 +640,22 @@ + } + }, + "node_modules/@aws-sdk/credential-provider-node": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.888.0.tgz", -+ "integrity": "sha512-KCrQh1dCDC8Y+Ap3SZa6S81kHk+p+yAaOQ5jC3dak4zhHW3RCrsGR/jYdemTOgbEGcA6ye51UbhWfrrlMmeJSA==", -+ "license": "Apache-2.0", -+ "dependencies": { -+ "@aws-sdk/credential-provider-env": "3.888.0", -+ "@aws-sdk/credential-provider-http": "3.888.0", -+ "@aws-sdk/credential-provider-ini": "3.888.0", -+ "@aws-sdk/credential-provider-process": "3.888.0", -+ "@aws-sdk/credential-provider-sso": "3.888.0", -+ "@aws-sdk/credential-provider-web-identity": "3.888.0", -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/credential-provider-imds": "^4.0.7", -+ "@smithy/property-provider": "^4.0.5", -+ "@smithy/shared-ini-file-loader": "^4.0.5", -+ "@smithy/types": "^4.5.0", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.940.0.tgz", ++ "integrity": "sha512-M8NFAvgvO6xZjiti5kztFiAYmSmSlG3eUfr4ZHSfXYZUA/KUdZU/D6xJyaLnU8cYRWBludb6K9XPKKVwKfqm4g==", ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@aws-sdk/credential-provider-env": "3.940.0", ++ "@aws-sdk/credential-provider-http": "3.940.0", ++ "@aws-sdk/credential-provider-ini": "3.940.0", ++ "@aws-sdk/credential-provider-process": "3.940.0", ++ "@aws-sdk/credential-provider-sso": "3.940.0", ++ "@aws-sdk/credential-provider-web-identity": "3.940.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/credential-provider-imds": "^4.2.5", ++ "@smithy/property-provider": "^4.2.5", ++ "@smithy/shared-ini-file-loader": "^4.4.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -550,16 +663,16 @@ + } + }, + "node_modules/@aws-sdk/credential-provider-process": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.888.0.tgz", -+ "integrity": "sha512-+aX6piSukPQ8DUS4JAH344GePg8/+Q1t0+kvSHAZHhYvtQ/1Zek3ySOJWH2TuzTPCafY4nmWLcQcqvU1w9+4Lw==", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.940.0.tgz", ++ "integrity": "sha512-pILBzt5/TYCqRsJb7vZlxmRIe0/T+FZPeml417EK75060ajDGnVJjHcuVdLVIeKoTKm9gmJc9l45gon6PbHyUQ==", + "license": "Apache-2.0", + "dependencies": { -+ "@aws-sdk/core": "3.888.0", -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/property-provider": "^4.0.5", -+ "@smithy/shared-ini-file-loader": "^4.0.5", -+ "@smithy/types": "^4.5.0", ++ "@aws-sdk/core": "3.940.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/property-provider": "^4.2.5", ++ "@smithy/shared-ini-file-loader": "^4.4.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -567,18 +680,18 @@ + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.888.0.tgz", -+ "integrity": "sha512-b1ZJji7LJ6E/j1PhFTyvp51in2iCOQ3VP6mj5H6f5OUnqn7efm41iNMoinKr87n0IKZw7qput5ggXVxEdPhouA==", -+ "license": "Apache-2.0", -+ "dependencies": { -+ "@aws-sdk/client-sso": "3.888.0", -+ "@aws-sdk/core": "3.888.0", -+ "@aws-sdk/token-providers": "3.888.0", -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/property-provider": "^4.0.5", -+ "@smithy/shared-ini-file-loader": "^4.0.5", -+ "@smithy/types": "^4.5.0", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.940.0.tgz", ++ "integrity": "sha512-q6JMHIkBlDCOMnA3RAzf8cGfup+8ukhhb50fNpghMs1SNBGhanmaMbZSgLigBRsPQW7fOk2l8jnzdVLS+BB9Uw==", ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@aws-sdk/client-sso": "3.940.0", ++ "@aws-sdk/core": "3.940.0", ++ "@aws-sdk/token-providers": "3.940.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/property-provider": "^4.2.5", ++ "@smithy/shared-ini-file-loader": "^4.4.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -586,16 +699,17 @@ + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.888.0.tgz", -+ "integrity": "sha512-7P0QNtsDzMZdmBAaY/vY1BsZHwTGvEz3bsn2bm5VSKFAeMmZqsHK1QeYdNsFjLtegnVh+wodxMq50jqLv3LFlA==", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.940.0.tgz", ++ "integrity": "sha512-9QLTIkDJHHaYL0nyymO41H8g3ui1yz6Y3GmAN1gYQa6plXisuFBnGAbmKVj7zNvjWaOKdF0dV3dd3AFKEDoJ/w==", + "license": "Apache-2.0", + "dependencies": { -+ "@aws-sdk/core": "3.888.0", -+ "@aws-sdk/nested-clients": "3.888.0", -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/property-provider": "^4.0.5", -+ "@smithy/types": "^4.5.0", ++ "@aws-sdk/core": "3.940.0", ++ "@aws-sdk/nested-clients": "3.940.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/property-provider": "^4.2.5", ++ "@smithy/shared-ini-file-loader": "^4.4.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -603,17 +717,17 @@ + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { -+ "version": "3.887.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.887.0.tgz", -+ "integrity": "sha512-qRCte/3MtNiMhPh4ZEGk9cHfAXq6IDTflvi2t1tkOIVZFyshkSCvNQNJrrE2D/ljVbOK1f3XbBDaF43EoQzIRQ==", ++ "version": "3.936.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.936.0.tgz", ++ "integrity": "sha512-XLSVVfAorUxZh6dzF+HTOp4R1B5EQcdpGcPliWr0KUj2jukgjZEcqbBmjyMF/p9bmyQsONX80iURF1HLAlW0qg==", + "license": "Apache-2.0", + "dependencies": { -+ "@aws-sdk/types": "3.887.0", -+ "@aws-sdk/util-arn-parser": "3.873.0", -+ "@smithy/node-config-provider": "^4.2.1", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-config-provider": "^4.0.0", ++ "@aws-sdk/types": "3.936.0", ++ "@aws-sdk/util-arn-parser": "3.893.0", ++ "@smithy/node-config-provider": "^4.3.5", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/types": "^4.9.0", ++ "@smithy/util-config-provider": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -621,14 +735,14 @@ + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { -+ "version": "3.887.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.887.0.tgz", -+ "integrity": "sha512-AlrTZZScDTG9SYeT82BC5cK/6Q4N0miN5xqMW/pbBqP3fNXlsdJOWKB+EKD3V6DV41EV5GVKHKe/1065xKSQ4w==", ++ "version": "3.936.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.936.0.tgz", ++ "integrity": "sha512-Eb4ELAC23bEQLJmUMYnPWcjD3FZIsmz2svDiXEcxRkQU9r7NRID7pM7C5NPH94wOfiCk0b2Y8rVyFXW0lGQwbA==", + "license": "Apache-2.0", + "dependencies": { -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/types": "^4.5.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -636,23 +750,23 @@ + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.888.0.tgz", -+ "integrity": "sha512-vdwd4wMAlXSg1bldhXyTsDSnyPP+bbEVihapejGKNd4gLfyyHwjTfbli+B/hEONGttQs5Dp54UMn8yW/UA189g==", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.940.0.tgz", ++ "integrity": "sha512-WdsxDAVj5qaa5ApAP+JbpCOMHFGSmzjs2Y2OBSbWPeR9Ew7t/Okj+kUub94QJPsgzhvU1/cqNejhsw5VxeFKSQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", -+ "@aws-sdk/core": "3.888.0", -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/is-array-buffer": "^4.0.0", -+ "@smithy/node-config-provider": "^4.2.1", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-middleware": "^4.1.1", -+ "@smithy/util-stream": "^4.3.1", -+ "@smithy/util-utf8": "^4.1.0", ++ "@aws-sdk/core": "3.940.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/is-array-buffer": "^4.2.0", ++ "@smithy/node-config-provider": "^4.3.5", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/types": "^4.9.0", ++ "@smithy/util-middleware": "^4.2.5", ++ "@smithy/util-stream": "^4.5.6", ++ "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -660,14 +774,14 @@ + } + }, + "node_modules/@aws-sdk/middleware-host-header": { -+ "version": "3.887.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.887.0.tgz", -+ "integrity": "sha512-ulzqXv6NNqdu/kr0sgBYupWmahISHY+azpJidtK6ZwQIC+vBUk9NdZeqQpy7KVhIk2xd4+5Oq9rxapPwPI21CA==", ++ "version": "3.936.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", ++ "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", + "license": "Apache-2.0", + "dependencies": { -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/types": "^4.5.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -675,13 +789,13 @@ + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { -+ "version": "3.887.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.887.0.tgz", -+ "integrity": "sha512-eU/9Cq4gg2sS32bOomxdx2YF43kb+o70pMhnEBBnVVeqzE8co78SO5FQdWfRTfhNJgTyQ6Vgosx//CNMPIfZPg==", ++ "version": "3.936.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.936.0.tgz", ++ "integrity": "sha512-SCMPenDtQMd9o5da9JzkHz838w3327iqXk3cbNnXWqnNRx6unyW8FL0DZ84gIY12kAyVHz5WEqlWuekc15ehfw==", + "license": "Apache-2.0", + "dependencies": { -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/types": "^4.5.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -689,13 +803,13 @@ + } + }, + "node_modules/@aws-sdk/middleware-logger": { -+ "version": "3.887.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.887.0.tgz", -+ "integrity": "sha512-YbbgLI6jKp2qSoAcHnXrQ5jcuc5EYAmGLVFgMVdk8dfCfJLfGGSaOLxF4CXC7QYhO50s+mPPkhBYejCik02Kug==", ++ "version": "3.936.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", ++ "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", + "license": "Apache-2.0", + "dependencies": { -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/types": "^4.5.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -703,15 +817,15 @@ + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { -+ "version": "3.887.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.887.0.tgz", -+ "integrity": "sha512-tjrUXFtQnFLo+qwMveq5faxP5MQakoLArXtqieHphSqZTXm21wDJM73hgT4/PQQGTwgYjDKqnqsE1hvk0hcfDw==", ++ "version": "3.936.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.936.0.tgz", ++ "integrity": "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==", + "license": "Apache-2.0", + "dependencies": { -+ "@aws-sdk/types": "3.887.0", -+ "@aws/lambda-invoke-store": "^0.0.1", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/types": "^4.5.0", ++ "@aws-sdk/types": "3.936.0", ++ "@aws/lambda-invoke-store": "^0.2.0", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -719,24 +833,24 @@ + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.888.0.tgz", -+ "integrity": "sha512-rKOFNfqgqOfrdcLGF8fcO75azWS2aq2ksRHFoIEFru5FJxzu/yDAhY4C2FKiP/X34xeIUS2SbE/gQgrgWHSN2g==", -+ "license": "Apache-2.0", -+ "dependencies": { -+ "@aws-sdk/core": "3.888.0", -+ "@aws-sdk/types": "3.887.0", -+ "@aws-sdk/util-arn-parser": "3.873.0", -+ "@smithy/core": "^3.11.0", -+ "@smithy/node-config-provider": "^4.2.1", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/signature-v4": "^5.1.3", -+ "@smithy/smithy-client": "^4.6.1", -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-config-provider": "^4.0.0", -+ "@smithy/util-middleware": "^4.1.1", -+ "@smithy/util-stream": "^4.3.1", -+ "@smithy/util-utf8": "^4.1.0", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.940.0.tgz", ++ "integrity": "sha512-JYkLjgS1wLoKHJ40G63+afM1ehmsPsjcmrHirKh8+kSCx4ip7+nL1e/twV4Zicxr8RJi9Y0Ahq5mDvneilDDKQ==", ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@aws-sdk/core": "3.940.0", ++ "@aws-sdk/types": "3.936.0", ++ "@aws-sdk/util-arn-parser": "3.893.0", ++ "@smithy/core": "^3.18.5", ++ "@smithy/node-config-provider": "^4.3.5", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/signature-v4": "^5.3.5", ++ "@smithy/smithy-client": "^4.9.8", ++ "@smithy/types": "^4.9.0", ++ "@smithy/util-config-provider": "^4.2.0", ++ "@smithy/util-middleware": "^4.2.5", ++ "@smithy/util-stream": "^4.5.6", ++ "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -744,13 +858,13 @@ + } + }, + "node_modules/@aws-sdk/middleware-ssec": { -+ "version": "3.887.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.887.0.tgz", -+ "integrity": "sha512-1ixZks0IDkdac1hjPe4vdLSuD9HznkhblCEb4T0wNyw3Ee1fdXg+MlcPWywzG5zkPXLcIrULUzJg/OSYfaDXcQ==", ++ "version": "3.936.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.936.0.tgz", ++ "integrity": "sha512-/GLC9lZdVp05ozRik5KsuODR/N7j+W+2TbfdFL3iS+7un+gnP6hC8RDOZd6WhpZp7drXQ9guKiTAxkZQwzS8DA==", + "license": "Apache-2.0", + "dependencies": { -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/types": "^4.5.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -758,17 +872,17 @@ + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.888.0.tgz", -+ "integrity": "sha512-ZkcUkoys8AdrNNG7ATjqw2WiXqrhTvT+r4CIK3KhOqIGPHX0p0DQWzqjaIl7ZhSUToKoZ4Ud7MjF795yUr73oA==", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.940.0.tgz", ++ "integrity": "sha512-nJbLrUj6fY+l2W2rIB9P4Qvpiy0tnTdg/dmixRxrU1z3e8wBdspJlyE+AZN4fuVbeL6rrRrO/zxQC1bB3cw5IA==", + "license": "Apache-2.0", + "dependencies": { -+ "@aws-sdk/core": "3.888.0", -+ "@aws-sdk/types": "3.887.0", -+ "@aws-sdk/util-endpoints": "3.887.0", -+ "@smithy/core": "^3.11.0", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/types": "^4.5.0", ++ "@aws-sdk/core": "3.940.0", ++ "@aws-sdk/types": "3.936.0", ++ "@aws-sdk/util-endpoints": "3.936.0", ++ "@smithy/core": "^3.18.5", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -776,48 +890,48 @@ + } + }, + "node_modules/@aws-sdk/nested-clients": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.888.0.tgz", -+ "integrity": "sha512-py4o4RPSGt+uwGvSBzR6S6cCBjS4oTX5F8hrHFHfPCdIOMVjyOBejn820jXkCrcdpSj3Qg1yUZXxsByvxc9Lyg==", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.940.0.tgz", ++ "integrity": "sha512-x0mdv6DkjXqXEcQj3URbCltEzW6hoy/1uIL+i8gExP6YKrnhiZ7SzuB4gPls2UOpK5UqLiqXjhRLfBb1C9i4Dw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", -+ "@aws-sdk/core": "3.888.0", -+ "@aws-sdk/middleware-host-header": "3.887.0", -+ "@aws-sdk/middleware-logger": "3.887.0", -+ "@aws-sdk/middleware-recursion-detection": "3.887.0", -+ "@aws-sdk/middleware-user-agent": "3.888.0", -+ "@aws-sdk/region-config-resolver": "3.887.0", -+ "@aws-sdk/types": "3.887.0", -+ "@aws-sdk/util-endpoints": "3.887.0", -+ "@aws-sdk/util-user-agent-browser": "3.887.0", -+ "@aws-sdk/util-user-agent-node": "3.888.0", -+ "@smithy/config-resolver": "^4.2.1", -+ "@smithy/core": "^3.11.0", -+ "@smithy/fetch-http-handler": "^5.2.1", -+ "@smithy/hash-node": "^4.1.1", -+ "@smithy/invalid-dependency": "^4.1.1", -+ "@smithy/middleware-content-length": "^4.1.1", -+ "@smithy/middleware-endpoint": "^4.2.1", -+ "@smithy/middleware-retry": "^4.2.1", -+ "@smithy/middleware-serde": "^4.1.1", -+ "@smithy/middleware-stack": "^4.1.1", -+ "@smithy/node-config-provider": "^4.2.1", -+ "@smithy/node-http-handler": "^4.2.1", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/smithy-client": "^4.6.1", -+ "@smithy/types": "^4.5.0", -+ "@smithy/url-parser": "^4.1.1", -+ "@smithy/util-base64": "^4.1.0", -+ "@smithy/util-body-length-browser": "^4.1.0", -+ "@smithy/util-body-length-node": "^4.1.0", -+ "@smithy/util-defaults-mode-browser": "^4.1.1", -+ "@smithy/util-defaults-mode-node": "^4.1.1", -+ "@smithy/util-endpoints": "^3.1.1", -+ "@smithy/util-middleware": "^4.1.1", -+ "@smithy/util-retry": "^4.1.1", -+ "@smithy/util-utf8": "^4.1.0", ++ "@aws-sdk/core": "3.940.0", ++ "@aws-sdk/middleware-host-header": "3.936.0", ++ "@aws-sdk/middleware-logger": "3.936.0", ++ "@aws-sdk/middleware-recursion-detection": "3.936.0", ++ "@aws-sdk/middleware-user-agent": "3.940.0", ++ "@aws-sdk/region-config-resolver": "3.936.0", ++ "@aws-sdk/types": "3.936.0", ++ "@aws-sdk/util-endpoints": "3.936.0", ++ "@aws-sdk/util-user-agent-browser": "3.936.0", ++ "@aws-sdk/util-user-agent-node": "3.940.0", ++ "@smithy/config-resolver": "^4.4.3", ++ "@smithy/core": "^3.18.5", ++ "@smithy/fetch-http-handler": "^5.3.6", ++ "@smithy/hash-node": "^4.2.5", ++ "@smithy/invalid-dependency": "^4.2.5", ++ "@smithy/middleware-content-length": "^4.2.5", ++ "@smithy/middleware-endpoint": "^4.3.12", ++ "@smithy/middleware-retry": "^4.4.12", ++ "@smithy/middleware-serde": "^4.2.6", ++ "@smithy/middleware-stack": "^4.2.5", ++ "@smithy/node-config-provider": "^4.3.5", ++ "@smithy/node-http-handler": "^4.4.5", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/smithy-client": "^4.9.8", ++ "@smithy/types": "^4.9.0", ++ "@smithy/url-parser": "^4.2.5", ++ "@smithy/util-base64": "^4.3.0", ++ "@smithy/util-body-length-browser": "^4.2.0", ++ "@smithy/util-body-length-node": "^4.2.1", ++ "@smithy/util-defaults-mode-browser": "^4.3.11", ++ "@smithy/util-defaults-mode-node": "^4.2.14", ++ "@smithy/util-endpoints": "^3.2.5", ++ "@smithy/util-middleware": "^4.2.5", ++ "@smithy/util-retry": "^4.2.5", ++ "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -825,16 +939,15 @@ + } + }, + "node_modules/@aws-sdk/region-config-resolver": { -+ "version": "3.887.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.887.0.tgz", -+ "integrity": "sha512-VdSMrIqJ3yjJb/fY+YAxrH/lCVv0iL8uA+lbMNfQGtO5tB3Zx6SU9LEpUwBNX8fPK1tUpI65CNE4w42+MY/7Mg==", ++ "version": "3.936.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", ++ "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", + "license": "Apache-2.0", + "dependencies": { -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/node-config-provider": "^4.2.1", -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-config-provider": "^4.0.0", -+ "@smithy/util-middleware": "^4.1.1", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/config-resolver": "^4.4.3", ++ "@smithy/node-config-provider": "^4.3.5", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -842,19 +955,19 @@ + } + }, + "node_modules/@aws-sdk/s3-presigned-post": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/s3-presigned-post/-/s3-presigned-post-3.888.0.tgz", -+ "integrity": "sha512-gY8cmB2eR9o4a0Y3Eshfe9LTJLXH1PELH9Rdc7hr4UNubIu4QbBI6HIXOv39HwQpil1q9SXUTSpuKbZG4DhxGQ==", -+ "license": "Apache-2.0", -+ "dependencies": { -+ "@aws-sdk/client-s3": "3.888.0", -+ "@aws-sdk/types": "3.887.0", -+ "@aws-sdk/util-format-url": "3.887.0", -+ "@smithy/middleware-endpoint": "^4.2.1", -+ "@smithy/signature-v4": "^5.1.3", -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-hex-encoding": "^4.0.0", -+ "@smithy/util-utf8": "^4.1.0", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/s3-presigned-post/-/s3-presigned-post-3.940.0.tgz", ++ "integrity": "sha512-ue4MmUl9JvNmqe3kHAT0YlGHqHZ2ZB5Fog8JFu0TLZjMzxE5JfGWFJFLLntahe6OVulKD7KwwnpTjJlUnn3DtQ==", ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@aws-sdk/client-s3": "3.940.0", ++ "@aws-sdk/types": "3.936.0", ++ "@aws-sdk/util-format-url": "3.936.0", ++ "@smithy/middleware-endpoint": "^4.3.12", ++ "@smithy/signature-v4": "^5.3.5", ++ "@smithy/types": "^4.9.0", ++ "@smithy/util-hex-encoding": "^4.2.0", ++ "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -862,18 +975,18 @@ + } + }, + "node_modules/@aws-sdk/s3-request-presigner": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.888.0.tgz", -+ "integrity": "sha512-3VAF0tJxW0p/ttUzJEgrMe52zZVoEG9dcJGdp4N0RG+LD41lp7QuQEYAZ/LGn7mwJsT0q18+tEJ5XzKmJFrOiA==", -+ "license": "Apache-2.0", -+ "dependencies": { -+ "@aws-sdk/signature-v4-multi-region": "3.888.0", -+ "@aws-sdk/types": "3.887.0", -+ "@aws-sdk/util-format-url": "3.887.0", -+ "@smithy/middleware-endpoint": "^4.2.1", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/smithy-client": "^4.6.1", -+ "@smithy/types": "^4.5.0", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.940.0.tgz", ++ "integrity": "sha512-TgTUDM2H7revReDfkVwVtIqxV3K0cJLdyuLDIkefVHRUNKwU1Vd5FB2TaFrs6STO0kx5pTckDCOLh0iy7nW5WQ==", ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@aws-sdk/signature-v4-multi-region": "3.940.0", ++ "@aws-sdk/types": "3.936.0", ++ "@aws-sdk/util-format-url": "3.936.0", ++ "@smithy/middleware-endpoint": "^4.3.12", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/smithy-client": "^4.9.8", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -881,16 +994,16 @@ + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.888.0.tgz", -+ "integrity": "sha512-FmOHUaJzEhqfcpyh0L7HLwYcYopK13Dbmuf+oUyu56/RoeB1nLnltH1VMQVj8v3Am2IwlGR+/JpFyrdkErN+cA==", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.940.0.tgz", ++ "integrity": "sha512-ugHZEoktD/bG6mdgmhzLDjMP2VrYRAUPRPF1DpCyiZexkH7DCU7XrSJyXMvkcf0DHV+URk0q2sLf/oqn1D2uYw==", + "license": "Apache-2.0", + "dependencies": { -+ "@aws-sdk/middleware-sdk-s3": "3.888.0", -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/signature-v4": "^5.1.3", -+ "@smithy/types": "^4.5.0", ++ "@aws-sdk/middleware-sdk-s3": "3.940.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/signature-v4": "^5.3.5", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -898,17 +1011,17 @@ + } + }, + "node_modules/@aws-sdk/token-providers": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.888.0.tgz", -+ "integrity": "sha512-WA3NF+3W8GEuCMG1WvkDYbB4z10G3O8xuhT7QSjhvLYWQ9CPt3w4VpVIfdqmUn131TCIbhCzD0KN/1VJTjAjyw==", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.940.0.tgz", ++ "integrity": "sha512-k5qbRe/ZFjW9oWEdzLIa2twRVIEx7p/9rutofyrRysrtEnYh3HAWCngAnwbgKMoiwa806UzcTRx0TjyEpnKcCg==", + "license": "Apache-2.0", + "dependencies": { -+ "@aws-sdk/core": "3.888.0", -+ "@aws-sdk/nested-clients": "3.888.0", -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/property-provider": "^4.0.5", -+ "@smithy/shared-ini-file-loader": "^4.0.5", -+ "@smithy/types": "^4.5.0", ++ "@aws-sdk/core": "3.940.0", ++ "@aws-sdk/nested-clients": "3.940.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/property-provider": "^4.2.5", ++ "@smithy/shared-ini-file-loader": "^4.4.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -916,12 +1029,12 @@ + } + }, + "node_modules/@aws-sdk/types": { -+ "version": "3.887.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.887.0.tgz", -+ "integrity": "sha512-fmTEJpUhsPsovQ12vZSpVTEP/IaRoJAMBGQXlQNjtCpkBp6Iq3KQDa/HDaPINE+3xxo6XvTdtibsNOd5zJLV9A==", ++ "version": "3.936.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", ++ "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/types": "^4.5.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -929,9 +1042,9 @@ + } + }, + "node_modules/@aws-sdk/util-arn-parser": { -+ "version": "3.873.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.873.0.tgz", -+ "integrity": "sha512-qag+VTqnJWDn8zTAXX4wiVioa0hZDQMtbZcGRERVnLar4/3/VIKBhxX2XibNQXFu1ufgcRn4YntT/XEPecFWcg==", ++ "version": "3.893.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.893.0.tgz", ++ "integrity": "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" @@ -941,15 +1054,15 @@ + } + }, + "node_modules/@aws-sdk/util-endpoints": { -+ "version": "3.887.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.887.0.tgz", -+ "integrity": "sha512-kpegvT53KT33BMeIcGLPA65CQVxLUL/C3gTz9AzlU/SDmeusBHX4nRApAicNzI/ltQ5lxZXbQn18UczzBuwF1w==", ++ "version": "3.936.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", ++ "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", + "license": "Apache-2.0", + "dependencies": { -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/types": "^4.5.0", -+ "@smithy/url-parser": "^4.1.1", -+ "@smithy/util-endpoints": "^3.1.1", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/types": "^4.9.0", ++ "@smithy/url-parser": "^4.2.5", ++ "@smithy/util-endpoints": "^3.2.5", + "tslib": "^2.6.2" + }, + "engines": { @@ -957,14 +1070,14 @@ + } + }, + "node_modules/@aws-sdk/util-format-url": { -+ "version": "3.887.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.887.0.tgz", -+ "integrity": "sha512-ABDSP6KsrdD+JC7qwMqUpLXqPidvfgT+Q+W8sGGuk/IBy7smgZDOdYSZLE4VBbQpH3N/zSJuslAWhL2x37Qwww==", ++ "version": "3.936.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.936.0.tgz", ++ "integrity": "sha512-MS5eSEtDUFIAMHrJaMERiHAvDPdfxc/T869ZjDNFAIiZhyc037REw0aoTNeimNXDNy2txRNZJaAUn/kE4RwN+g==", + "license": "Apache-2.0", + "dependencies": { -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/querystring-builder": "^4.1.1", -+ "@smithy/types": "^4.5.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/querystring-builder": "^4.2.5", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -972,9 +1085,9 @@ + } + }, + "node_modules/@aws-sdk/util-locate-window": { -+ "version": "3.873.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.873.0.tgz", -+ "integrity": "sha512-xcVhZF6svjM5Rj89T1WzkjQmrTF6dpR2UvIHPMTnSZoNe6CixejPZ6f0JJ2kAhO8H+dUHwNBlsUgOTIKiK/Syg==", ++ "version": "3.893.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", ++ "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" @@ -984,27 +1097,27 @@ + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { -+ "version": "3.887.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.887.0.tgz", -+ "integrity": "sha512-X71UmVsYc6ZTH4KU6hA5urOzYowSXc3qvroagJNLJYU1ilgZ529lP4J9XOYfEvTXkLR1hPFSRxa43SrwgelMjA==", ++ "version": "3.936.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", ++ "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", + "license": "Apache-2.0", + "dependencies": { -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/types": "^4.5.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/types": "^4.9.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { -+ "version": "3.888.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.888.0.tgz", -+ "integrity": "sha512-rSB3OHyuKXotIGfYEo//9sU0lXAUrTY28SUUnxzOGYuQsAt0XR5iYwBAp+RjV6x8f+Hmtbg0PdCsy1iNAXa0UQ==", ++ "version": "3.940.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.940.0.tgz", ++ "integrity": "sha512-dlD/F+L/jN26I8Zg5x0oDGJiA+/WEQmnSE27fi5ydvYnpfQLwThtQo9SsNS47XSR/SOULaaoC9qx929rZuo74A==", + "license": "Apache-2.0", + "dependencies": { -+ "@aws-sdk/middleware-user-agent": "3.888.0", -+ "@aws-sdk/types": "3.887.0", -+ "@smithy/node-config-provider": "^4.2.1", -+ "@smithy/types": "^4.5.0", ++ "@aws-sdk/middleware-user-agent": "3.940.0", ++ "@aws-sdk/types": "3.936.0", ++ "@smithy/node-config-provider": "^4.3.5", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -1020,12 +1133,13 @@ + } + }, + "node_modules/@aws-sdk/xml-builder": { -+ "version": "3.887.0", -+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.887.0.tgz", -+ "integrity": "sha512-lMwgWK1kNgUhHGfBvO/5uLe7TKhycwOn3eRCqsKPT9aPCx/HWuTlpcQp8oW2pCRGLS7qzcxqpQulcD+bbUL7XQ==", ++ "version": "3.930.0", ++ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz", ++ "integrity": "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/types": "^4.5.0", ++ "@smithy/types": "^4.9.0", ++ "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { @@ -1033,9 +1147,9 @@ + } + }, + "node_modules/@aws/lambda-invoke-store": { -+ "version": "0.0.1", -+ "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.0.1.tgz", -+ "integrity": "sha512-ORHRQ2tmvnBXc8t/X9Z8IcSbBA4xTLKuN873FopzklHMeqBst7YG0d+AX97inkvDX+NChYtSr+qGfcqGFaI8Zw==", ++ "version": "0.2.1", ++ "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.1.tgz", ++ "integrity": "sha512-sIyFcoPZkTtNu9xFeEoynMef3bPJIAbOfUh+ueYcfhVl6xm2VRtMcMclSxmZCMnHHd4hlYKJeq/aggmBEWynww==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" @@ -1055,13 +1169,242 @@ + "node": ">=6.9.0" + } + }, ++ "node_modules/@babel/compat-data": { ++ "version": "7.28.5", ++ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", ++ "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/core": { ++ "version": "7.28.5", ++ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", ++ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/code-frame": "^7.27.1", ++ "@babel/generator": "^7.28.5", ++ "@babel/helper-compilation-targets": "^7.27.2", ++ "@babel/helper-module-transforms": "^7.28.3", ++ "@babel/helpers": "^7.28.4", ++ "@babel/parser": "^7.28.5", ++ "@babel/template": "^7.27.2", ++ "@babel/traverse": "^7.28.5", ++ "@babel/types": "^7.28.5", ++ "@jridgewell/remapping": "^2.3.5", ++ "convert-source-map": "^2.0.0", ++ "debug": "^4.1.0", ++ "gensync": "^1.0.0-beta.2", ++ "json5": "^2.2.3", ++ "semver": "^6.3.1" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/babel" ++ } ++ }, ++ "node_modules/@babel/core/node_modules/semver": { ++ "version": "6.3.1", ++ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", ++ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", ++ "dev": true, ++ "license": "ISC", ++ "bin": { ++ "semver": "bin/semver.js" ++ } ++ }, ++ "node_modules/@babel/generator": { ++ "version": "7.28.5", ++ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", ++ "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/parser": "^7.28.5", ++ "@babel/types": "^7.28.5", ++ "@jridgewell/gen-mapping": "^0.3.12", ++ "@jridgewell/trace-mapping": "^0.3.28", ++ "jsesc": "^3.0.2" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/helper-compilation-targets": { ++ "version": "7.27.2", ++ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", ++ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/compat-data": "^7.27.2", ++ "@babel/helper-validator-option": "^7.27.1", ++ "browserslist": "^4.24.0", ++ "lru-cache": "^5.1.1", ++ "semver": "^6.3.1" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/helper-compilation-targets/node_modules/semver": { ++ "version": "6.3.1", ++ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", ++ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", ++ "dev": true, ++ "license": "ISC", ++ "bin": { ++ "semver": "bin/semver.js" ++ } ++ }, ++ "node_modules/@babel/helper-globals": { ++ "version": "7.28.0", ++ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", ++ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/helper-module-imports": { ++ "version": "7.27.1", ++ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", ++ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/traverse": "^7.27.1", ++ "@babel/types": "^7.27.1" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/helper-module-transforms": { ++ "version": "7.28.3", ++ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", ++ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/helper-module-imports": "^7.27.1", ++ "@babel/helper-validator-identifier": "^7.27.1", ++ "@babel/traverse": "^7.28.3" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ }, ++ "peerDependencies": { ++ "@babel/core": "^7.0.0" ++ } ++ }, ++ "node_modules/@babel/helper-plugin-utils": { ++ "version": "7.27.1", ++ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", ++ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/helper-string-parser": { ++ "version": "7.27.1", ++ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", ++ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, + "node_modules/@babel/helper-validator-identifier": { ++ "version": "7.28.5", ++ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", ++ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", ++ "license": "MIT", ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/helper-validator-option": { ++ "version": "7.27.1", ++ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", ++ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/helpers": { ++ "version": "7.28.4", ++ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", ++ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/template": "^7.27.2", ++ "@babel/types": "^7.28.4" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/parser": { ++ "version": "7.28.5", ++ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", ++ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/types": "^7.28.5" ++ }, ++ "bin": { ++ "parser": "bin/babel-parser.js" ++ }, ++ "engines": { ++ "node": ">=6.0.0" ++ } ++ }, ++ "node_modules/@babel/plugin-transform-react-jsx-self": { ++ "version": "7.27.1", ++ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", ++ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/helper-plugin-utils": "^7.27.1" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ }, ++ "peerDependencies": { ++ "@babel/core": "^7.0.0-0" ++ } ++ }, ++ "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", -+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", -+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", ++ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", ++ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", ++ "dev": true, + "license": "MIT", ++ "dependencies": { ++ "@babel/helper-plugin-utils": "^7.27.1" ++ }, + "engines": { + "node": ">=6.9.0" ++ }, ++ "peerDependencies": { ++ "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { @@ -1073,10 +1416,58 @@ + "node": ">=6.9.0" + } + }, ++ "node_modules/@babel/template": { ++ "version": "7.27.2", ++ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", ++ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/code-frame": "^7.27.1", ++ "@babel/parser": "^7.27.2", ++ "@babel/types": "^7.27.1" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/traverse": { ++ "version": "7.28.5", ++ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", ++ "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/code-frame": "^7.27.1", ++ "@babel/generator": "^7.28.5", ++ "@babel/helper-globals": "^7.28.0", ++ "@babel/parser": "^7.28.5", ++ "@babel/template": "^7.27.2", ++ "@babel/types": "^7.28.5", ++ "debug": "^4.3.1" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/types": { ++ "version": "7.28.5", ++ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", ++ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/helper-string-parser": "^7.27.1", ++ "@babel/helper-validator-identifier": "^7.28.5" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, + "node_modules/@emnapi/core": { -+ "version": "1.5.0", -+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", -+ "integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==", ++ "version": "1.7.1", ++ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", ++ "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "license": "MIT", + "optional": true, + "dependencies": { @@ -1085,9 +1476,9 @@ + } + }, + "node_modules/@emnapi/runtime": { -+ "version": "1.5.0", -+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", -+ "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", ++ "version": "1.7.1", ++ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", ++ "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "license": "MIT", + "optional": true, + "dependencies": { @@ -1105,9 +1496,9 @@ + } + }, + "node_modules/@esbuild/aix-ppc64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", -+ "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", ++ "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], @@ -1122,9 +1513,9 @@ + } + }, + "node_modules/@esbuild/android-arm": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", -+ "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", ++ "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], @@ -1139,9 +1530,9 @@ + } + }, + "node_modules/@esbuild/android-arm64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", -+ "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", ++ "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], @@ -1156,9 +1547,9 @@ + } + }, + "node_modules/@esbuild/android-x64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", -+ "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", ++ "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], @@ -1173,9 +1564,9 @@ + } + }, + "node_modules/@esbuild/darwin-arm64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", -+ "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", ++ "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], @@ -1190,9 +1581,9 @@ + } + }, + "node_modules/@esbuild/darwin-x64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", -+ "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", ++ "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], @@ -1207,9 +1598,9 @@ + } + }, + "node_modules/@esbuild/freebsd-arm64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", -+ "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", ++ "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], @@ -1224,9 +1615,9 @@ + } + }, + "node_modules/@esbuild/freebsd-x64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", -+ "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", ++ "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], @@ -1241,9 +1632,9 @@ + } + }, + "node_modules/@esbuild/linux-arm": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", -+ "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", ++ "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], @@ -1258,9 +1649,9 @@ + } + }, + "node_modules/@esbuild/linux-arm64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", -+ "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", ++ "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], @@ -1275,9 +1666,9 @@ + } + }, + "node_modules/@esbuild/linux-ia32": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", -+ "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", ++ "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], @@ -1292,9 +1683,9 @@ + } + }, + "node_modules/@esbuild/linux-loong64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", -+ "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", ++ "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], @@ -1309,9 +1700,9 @@ + } + }, + "node_modules/@esbuild/linux-mips64el": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", -+ "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", ++ "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], @@ -1326,9 +1717,9 @@ + } + }, + "node_modules/@esbuild/linux-ppc64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", -+ "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", ++ "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], @@ -1343,9 +1734,9 @@ + } + }, + "node_modules/@esbuild/linux-riscv64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", -+ "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", ++ "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], @@ -1360,9 +1751,9 @@ + } + }, + "node_modules/@esbuild/linux-s390x": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", -+ "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", ++ "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], @@ -1377,9 +1768,9 @@ + } + }, + "node_modules/@esbuild/linux-x64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", -+ "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", ++ "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], @@ -1394,9 +1785,9 @@ + } + }, + "node_modules/@esbuild/netbsd-arm64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", -+ "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", ++ "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], @@ -1411,9 +1802,9 @@ + } + }, + "node_modules/@esbuild/netbsd-x64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", -+ "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", ++ "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], @@ -1428,9 +1819,9 @@ + } + }, + "node_modules/@esbuild/openbsd-arm64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", -+ "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", ++ "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], @@ -1445,9 +1836,9 @@ + } + }, + "node_modules/@esbuild/openbsd-x64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", -+ "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", ++ "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], @@ -1462,9 +1853,9 @@ + } + }, + "node_modules/@esbuild/openharmony-arm64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", -+ "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", ++ "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], @@ -1479,9 +1870,9 @@ + } + }, + "node_modules/@esbuild/sunos-x64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", -+ "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", ++ "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], @@ -1496,9 +1887,9 @@ + } + }, + "node_modules/@esbuild/win32-arm64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", -+ "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", ++ "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], @@ -1513,9 +1904,9 @@ + } + }, + "node_modules/@esbuild/win32-ia32": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", -+ "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", ++ "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], @@ -1530,9 +1921,9 @@ + } + }, + "node_modules/@esbuild/win32-x64": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", -+ "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", ++ "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], @@ -1615,27 +2006,27 @@ + } + }, + "node_modules/@grpc/grpc-js": { -+ "version": "1.13.4", -+ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz", -+ "integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==", ++ "version": "1.14.1", ++ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.1.tgz", ++ "integrity": "sha512-sPxgEWtPUR3EnRJCEtbGZG2iX8LQDUls2wUS3o27jg07KqJFMq6YDeWvMo1wfpmy3rqRdS0rivpLwhqQtEyCuQ==", + "license": "Apache-2.0", + "dependencies": { -+ "@grpc/proto-loader": "^0.7.13", ++ "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, -+ "node_modules/@grpc/proto-loader": { -+ "version": "0.7.15", -+ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", -+ "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", ++ "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { ++ "version": "0.8.0", ++ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", ++ "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", -+ "protobufjs": "^7.2.5", ++ "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "bin": { @@ -1645,20 +2036,22 @@ + "node": ">=6" + } + }, -+ "node_modules/@headlessui/react": { -+ "version": "1.7.13", -+ "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.13.tgz", -+ "integrity": "sha512-9n+EQKRtD9266xIHXdY5MfiXPDfYwl7zBM7KOx2Ae3Gdgxy8QML1FkCMjq6AsOf0l6N9uvI4HcFtuFlenaldKg==", -+ "license": "MIT", ++ "node_modules/@grpc/proto-loader": { ++ "version": "0.7.15", ++ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", ++ "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", ++ "license": "Apache-2.0", + "dependencies": { -+ "client-only": "^0.0.1" ++ "lodash.camelcase": "^4.3.0", ++ "long": "^5.0.0", ++ "protobufjs": "^7.2.5", ++ "yargs": "^17.7.2" + }, -+ "engines": { -+ "node": ">=10" ++ "bin": { ++ "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, -+ "peerDependencies": { -+ "react": "^16 || ^17 || ^18", -+ "react-dom": "^16 || ^17 || ^18" ++ "engines": { ++ "node": ">=6" + } + }, + "node_modules/@hookform/resolvers": { @@ -1674,12 +2067,12 @@ + } + }, + "node_modules/@inquirer/external-editor": { -+ "version": "1.0.2", -+ "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.2.tgz", -+ "integrity": "sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ==", ++ "version": "1.0.3", ++ "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", ++ "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "license": "MIT", + "dependencies": { -+ "chardet": "^2.1.0", ++ "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { @@ -1694,39 +2087,6 @@ + } + } + }, -+ "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { -+ "version": "0.7.0", -+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", -+ "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", -+ "license": "MIT", -+ "dependencies": { -+ "safer-buffer": ">= 2.1.2 < 3.0.0" -+ }, -+ "engines": { -+ "node": ">=0.10.0" -+ }, -+ "funding": { -+ "type": "opencollective", -+ "url": "https://opencollective.com/express" -+ } -+ }, -+ "node_modules/@isaacs/cliui": { -+ "version": "8.0.2", -+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", -+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", -+ "license": "ISC", -+ "dependencies": { -+ "string-width": "^5.1.2", -+ "string-width-cjs": "npm:string-width@^4.2.0", -+ "strip-ansi": "^7.0.1", -+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", -+ "wrap-ansi": "^8.1.0", -+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" -+ }, -+ "engines": { -+ "node": ">=12" -+ } -+ }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -1749,6 +2109,17 @@ + "@jridgewell/trace-mapping": "^0.3.24" + } + }, ++ "node_modules/@jridgewell/remapping": { ++ "version": "2.3.5", ++ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", ++ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@jridgewell/gen-mapping": "^0.3.5", ++ "@jridgewell/trace-mapping": "^0.3.24" ++ } ++ }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -1784,15 +2155,6 @@ + "url": "https://opencollective.com/js-sdsl" + } + }, -+ "node_modules/@lemonsqueezy/lemonsqueezy.js": { -+ "version": "3.3.1", -+ "resolved": "https://registry.npmjs.org/@lemonsqueezy/lemonsqueezy.js/-/lemonsqueezy.js-3.3.1.tgz", -+ "integrity": "sha512-gM/FdNsK3BlrD6JRrhmiyqBXQsCpzSUdKSoZwJMQfXqfqcK321og+uMssc6HYcygUMrGvPnNJyJ1RqZPFDrgtg==", -+ "license": "MIT", -+ "engines": { -+ "node": ">=20" -+ } -+ }, + "node_modules/@lucia-auth/adapter-prisma": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@lucia-auth/adapter-prisma/-/adapter-prisma-4.0.1.tgz", @@ -2464,16 +2826,6 @@ + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "license": "MIT" + }, -+ "node_modules/@pkgjs/parseargs": { -+ "version": "0.11.0", -+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", -+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", -+ "license": "MIT", -+ "optional": true, -+ "engines": { -+ "node": ">=14" -+ } -+ }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", @@ -2650,20 +3002,13 @@ + } + } + }, -+ "node_modules/@radix-ui/react-accordion/node_modules/@radix-ui/react-collapsible": { -+ "version": "1.1.12", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", -+ "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", ++ "node_modules/@radix-ui/react-arrow": { ++ "version": "1.1.7", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", ++ "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/primitive": "1.1.3", -+ "@radix-ui/react-compose-refs": "1.1.2", -+ "@radix-ui/react-context": "1.1.2", -+ "@radix-ui/react-id": "1.1.1", -+ "@radix-ui/react-presence": "1.1.5", -+ "@radix-ui/react-primitive": "2.1.3", -+ "@radix-ui/react-use-controllable-state": "1.2.2", -+ "@radix-ui/react-use-layout-effect": "1.1.1" ++ "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", @@ -2680,13 +3025,16 @@ + } + } + }, -+ "node_modules/@radix-ui/react-accordion/node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-presence": { -+ "version": "1.1.5", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", -+ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", ++ "node_modules/@radix-ui/react-avatar": { ++ "version": "1.1.11", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.11.tgz", ++ "integrity": "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-compose-refs": "1.1.2", ++ "@radix-ui/react-context": "1.1.3", ++ "@radix-ui/react-primitive": "2.1.4", ++ "@radix-ui/react-use-callback-ref": "1.1.1", ++ "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { @@ -2704,39 +3052,28 @@ + } + } + }, -+ "node_modules/@radix-ui/react-accordion/node_modules/@radix-ui/react-collection": { -+ "version": "1.1.7", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", -+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", ++ "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-context": { ++ "version": "1.1.3", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", ++ "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", + "license": "MIT", -+ "dependencies": { -+ "@radix-ui/react-compose-refs": "1.1.2", -+ "@radix-ui/react-context": "1.1.2", -+ "@radix-ui/react-primitive": "2.1.3", -+ "@radix-ui/react-slot": "1.2.3" -+ }, + "peerDependencies": { + "@types/react": "*", -+ "@types/react-dom": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", -+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true -+ }, -+ "@types/react-dom": { -+ "optional": true + } + } + }, -+ "node_modules/@radix-ui/react-accordion/node_modules/@radix-ui/react-primitive": { -+ "version": "2.1.3", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", -+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", ++ "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-primitive": { ++ "version": "2.1.4", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", ++ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-slot": "1.2.3" ++ "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", @@ -2753,40 +3090,20 @@ + } + } + }, -+ "node_modules/@radix-ui/react-avatar": { -+ "version": "1.1.10", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz", -+ "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==", ++ "node_modules/@radix-ui/react-checkbox": { ++ "version": "1.3.3", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", ++ "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", + "dependencies": { ++ "@radix-ui/primitive": "1.1.3", ++ "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", ++ "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", -+ "@radix-ui/react-use-callback-ref": "1.1.1", -+ "@radix-ui/react-use-is-hydrated": "0.1.0", -+ "@radix-ui/react-use-layout-effect": "1.1.1" -+ }, -+ "peerDependencies": { -+ "@types/react": "*", -+ "@types/react-dom": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", -+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" -+ }, -+ "peerDependenciesMeta": { -+ "@types/react": { -+ "optional": true -+ }, -+ "@types/react-dom": { -+ "optional": true -+ } -+ } -+ }, -+ "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-primitive": { -+ "version": "2.1.3", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", -+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", -+ "license": "MIT", -+ "dependencies": { -+ "@radix-ui/react-slot": "1.2.3" ++ "@radix-ui/react-use-controllable-state": "1.2.2", ++ "@radix-ui/react-use-previous": "1.1.1", ++ "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", @@ -2803,20 +3120,20 @@ + } + } + }, -+ "node_modules/@radix-ui/react-checkbox": { -+ "version": "1.3.3", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", -+ "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", ++ "node_modules/@radix-ui/react-collapsible": { ++ "version": "1.1.12", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", ++ "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", ++ "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", -+ "@radix-ui/react-use-previous": "1.1.1", -+ "@radix-ui/react-use-size": "1.1.1" ++ "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", @@ -2833,14 +3150,16 @@ + } + } + }, -+ "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-presence": { -+ "version": "1.1.5", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", -+ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", ++ "node_modules/@radix-ui/react-collection": { ++ "version": "1.1.7", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", ++ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", -+ "@radix-ui/react-use-layout-effect": "1.1.1" ++ "@radix-ui/react-context": "1.1.2", ++ "@radix-ui/react-primitive": "2.1.3", ++ "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", @@ -2857,26 +3176,21 @@ + } + } + }, -+ "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-primitive": { -+ "version": "2.1.3", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", -+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", ++ "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { ++ "version": "1.2.3", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", ++ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-slot": "1.2.3" ++ "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", -+ "@types/react-dom": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", -+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true -+ }, -+ "@types/react-dom": { -+ "optional": true + } + } + }, @@ -2946,42 +3260,50 @@ + } + } + }, -+ "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { -+ "version": "1.1.11", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", -+ "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", ++ "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { ++ "version": "1.2.3", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", ++ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/primitive": "1.1.3", -+ "@radix-ui/react-compose-refs": "1.1.2", -+ "@radix-ui/react-primitive": "2.1.3", -+ "@radix-ui/react-use-callback-ref": "1.1.1", -+ "@radix-ui/react-use-escape-keydown": "1.1.1" ++ "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", -+ "@types/react-dom": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", -+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true -+ }, -+ "@types/react-dom": { ++ } ++ } ++ }, ++ "node_modules/@radix-ui/react-direction": { ++ "version": "1.1.1", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", ++ "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", ++ "license": "MIT", ++ "peerDependencies": { ++ "@types/react": "*", ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ }, ++ "peerDependenciesMeta": { ++ "@types/react": { + "optional": true + } + } + }, -+ "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-scope": { -+ "version": "1.1.7", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", -+ "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", ++ "node_modules/@radix-ui/react-dismissable-layer": { ++ "version": "1.1.11", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", ++ "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { ++ "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", -+ "@radix-ui/react-use-callback-ref": "1.1.1" ++ "@radix-ui/react-use-callback-ref": "1.1.1", ++ "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", @@ -2998,14 +3320,19 @@ + } + } + }, -+ "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": { -+ "version": "1.1.9", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", -+ "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", ++ "node_modules/@radix-ui/react-dropdown-menu": { ++ "version": "2.1.16", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", ++ "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { ++ "@radix-ui/primitive": "1.1.3", ++ "@radix-ui/react-compose-refs": "1.1.2", ++ "@radix-ui/react-context": "1.1.2", ++ "@radix-ui/react-id": "1.1.1", ++ "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", -+ "@radix-ui/react-use-layout-effect": "1.1.1" ++ "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", @@ -3022,37 +3349,30 @@ + } + } + }, -+ "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": { -+ "version": "1.1.5", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", -+ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", ++ "node_modules/@radix-ui/react-focus-guards": { ++ "version": "1.1.3", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", ++ "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", -+ "dependencies": { -+ "@radix-ui/react-compose-refs": "1.1.2", -+ "@radix-ui/react-use-layout-effect": "1.1.1" -+ }, + "peerDependencies": { + "@types/react": "*", -+ "@types/react-dom": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", -+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true -+ }, -+ "@types/react-dom": { -+ "optional": true + } + } + }, -+ "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": { -+ "version": "2.1.3", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", -+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", ++ "node_modules/@radix-ui/react-focus-scope": { ++ "version": "1.1.7", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", ++ "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-slot": "1.2.3" ++ "@radix-ui/react-compose-refs": "1.1.2", ++ "@radix-ui/react-primitive": "2.1.3", ++ "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", @@ -3069,11 +3389,14 @@ + } + } + }, -+ "node_modules/@radix-ui/react-direction": { ++ "node_modules/@radix-ui/react-id": { + "version": "1.1.1", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", -+ "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", ++ "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", ++ "dependencies": { ++ "@radix-ui/react-use-layout-effect": "1.1.1" ++ }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -3084,19 +3407,13 @@ + } + } + }, -+ "node_modules/@radix-ui/react-dropdown-menu": { -+ "version": "2.1.16", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", -+ "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", ++ "node_modules/@radix-ui/react-label": { ++ "version": "2.1.8", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", ++ "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/primitive": "1.1.3", -+ "@radix-ui/react-compose-refs": "1.1.2", -+ "@radix-ui/react-context": "1.1.2", -+ "@radix-ui/react-id": "1.1.1", -+ "@radix-ui/react-menu": "2.1.16", -+ "@radix-ui/react-primitive": "2.1.3", -+ "@radix-ui/react-use-controllable-state": "1.2.2" ++ "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", @@ -3113,7 +3430,30 @@ + } + } + }, -+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-menu": { ++ "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { ++ "version": "2.1.4", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", ++ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", ++ "license": "MIT", ++ "dependencies": { ++ "@radix-ui/react-slot": "1.2.4" ++ }, ++ "peerDependencies": { ++ "@types/react": "*", ++ "@types/react-dom": "*", ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", ++ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ }, ++ "peerDependenciesMeta": { ++ "@types/react": { ++ "optional": true ++ }, ++ "@types/react-dom": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", @@ -3153,43 +3493,40 @@ + } + } + }, -+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-collection": { -+ "version": "1.1.7", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", -+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", ++ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { ++ "version": "1.2.3", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", ++ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-compose-refs": "1.1.2", -+ "@radix-ui/react-context": "1.1.2", -+ "@radix-ui/react-primitive": "2.1.3", -+ "@radix-ui/react-slot": "1.2.3" ++ "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", -+ "@types/react-dom": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", -+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true -+ }, -+ "@types/react-dom": { -+ "optional": true + } + } + }, -+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-dismissable-layer": { -+ "version": "1.1.11", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", -+ "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", ++ "node_modules/@radix-ui/react-popper": { ++ "version": "1.2.8", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", ++ "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/primitive": "1.1.3", ++ "@floating-ui/react-dom": "^2.0.0", ++ "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", ++ "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", -+ "@radix-ui/react-use-escape-keydown": "1.1.1" ++ "@radix-ui/react-use-layout-effect": "1.1.1", ++ "@radix-ui/react-use-rect": "1.1.1", ++ "@radix-ui/react-use-size": "1.1.1", ++ "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", @@ -3206,15 +3543,14 @@ + } + } + }, -+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-scope": { -+ "version": "1.1.7", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", -+ "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", ++ "node_modules/@radix-ui/react-portal": { ++ "version": "1.1.9", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", ++ "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", -+ "@radix-ui/react-use-callback-ref": "1.1.1" ++ "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", @@ -3231,22 +3567,14 @@ + } + } + }, -+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-popper": { -+ "version": "1.2.8", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", -+ "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", ++ "node_modules/@radix-ui/react-presence": { ++ "version": "1.1.5", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", ++ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { -+ "@floating-ui/react-dom": "^2.0.0", -+ "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", -+ "@radix-ui/react-context": "1.1.2", -+ "@radix-ui/react-primitive": "2.1.3", -+ "@radix-ui/react-use-callback-ref": "1.1.1", -+ "@radix-ui/react-use-layout-effect": "1.1.1", -+ "@radix-ui/react-use-rect": "1.1.1", -+ "@radix-ui/react-use-size": "1.1.1", -+ "@radix-ui/rect": "1.1.1" ++ "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", @@ -3263,13 +3591,13 @@ + } + } + }, -+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-arrow": { -+ "version": "1.1.7", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", -+ "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", ++ "node_modules/@radix-ui/react-primitive": { ++ "version": "2.1.3", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", ++ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-primitive": "2.1.3" ++ "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", @@ -3286,14 +3614,32 @@ + } + } + }, -+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-portal": { -+ "version": "1.1.9", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", -+ "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", ++ "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { ++ "version": "1.2.3", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", ++ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-primitive": "2.1.3", -+ "@radix-ui/react-use-layout-effect": "1.1.1" ++ "@radix-ui/react-compose-refs": "1.1.2" ++ }, ++ "peerDependencies": { ++ "@types/react": "*", ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ }, ++ "peerDependenciesMeta": { ++ "@types/react": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/@radix-ui/react-progress": { ++ "version": "1.1.8", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.8.tgz", ++ "integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==", ++ "license": "MIT", ++ "dependencies": { ++ "@radix-ui/react-context": "1.1.3", ++ "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", @@ -3310,14 +3656,28 @@ + } + } + }, -+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-presence": { -+ "version": "1.1.5", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", -+ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", ++ "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": { ++ "version": "1.1.3", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", ++ "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", ++ "license": "MIT", ++ "peerDependencies": { ++ "@types/react": "*", ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ }, ++ "peerDependenciesMeta": { ++ "@types/react": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": { ++ "version": "2.1.4", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", ++ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-compose-refs": "1.1.2", -+ "@radix-ui/react-use-layout-effect": "1.1.1" ++ "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", @@ -3334,7 +3694,7 @@ + } + } + }, -+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-roving-focus": { ++ "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", @@ -3365,13 +3725,33 @@ + } + } + }, -+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-primitive": { -+ "version": "2.1.3", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", -+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", ++ "node_modules/@radix-ui/react-select": { ++ "version": "2.2.6", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", ++ "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-slot": "1.2.3" ++ "@radix-ui/number": "1.1.1", ++ "@radix-ui/primitive": "1.1.3", ++ "@radix-ui/react-collection": "1.1.7", ++ "@radix-ui/react-compose-refs": "1.1.2", ++ "@radix-ui/react-context": "1.1.2", ++ "@radix-ui/react-direction": "1.1.1", ++ "@radix-ui/react-dismissable-layer": "1.1.11", ++ "@radix-ui/react-focus-guards": "1.1.3", ++ "@radix-ui/react-focus-scope": "1.1.7", ++ "@radix-ui/react-id": "1.1.1", ++ "@radix-ui/react-popper": "1.2.8", ++ "@radix-ui/react-portal": "1.1.9", ++ "@radix-ui/react-primitive": "2.1.3", ++ "@radix-ui/react-slot": "1.2.3", ++ "@radix-ui/react-use-callback-ref": "1.1.1", ++ "@radix-ui/react-use-controllable-state": "1.2.2", ++ "@radix-ui/react-use-layout-effect": "1.1.1", ++ "@radix-ui/react-use-previous": "1.1.1", ++ "@radix-ui/react-visually-hidden": "1.2.3", ++ "aria-hidden": "^1.2.4", ++ "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", @@ -3388,28 +3768,13 @@ + } + } + }, -+ "node_modules/@radix-ui/react-focus-guards": { -+ "version": "1.1.3", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", -+ "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", -+ "license": "MIT", -+ "peerDependencies": { -+ "@types/react": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" -+ }, -+ "peerDependenciesMeta": { -+ "@types/react": { -+ "optional": true -+ } -+ } -+ }, -+ "node_modules/@radix-ui/react-id": { -+ "version": "1.1.1", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", -+ "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", ++ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { ++ "version": "1.2.3", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", ++ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-use-layout-effect": "1.1.1" ++ "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", @@ -3421,13 +3786,13 @@ + } + } + }, -+ "node_modules/@radix-ui/react-label": { -+ "version": "2.1.7", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", -+ "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", ++ "node_modules/@radix-ui/react-separator": { ++ "version": "1.1.8", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", ++ "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-primitive": "2.1.3" ++ "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", @@ -3444,13 +3809,13 @@ + } + } + }, -+ "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { -+ "version": "2.1.3", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", -+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", ++ "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { ++ "version": "2.1.4", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", ++ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-slot": "1.2.3" ++ "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", @@ -3467,37 +3832,37 @@ + } + } + }, -+ "node_modules/@radix-ui/react-progress": { -+ "version": "1.1.7", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", -+ "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", ++ "node_modules/@radix-ui/react-slot": { ++ "version": "1.2.4", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", ++ "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-context": "1.1.2", -+ "@radix-ui/react-primitive": "2.1.3" ++ "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", -+ "@types/react-dom": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", -+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true -+ }, -+ "@types/react-dom": { -+ "optional": true + } + } + }, -+ "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": { -+ "version": "2.1.3", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", -+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", ++ "node_modules/@radix-ui/react-switch": { ++ "version": "1.2.6", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", ++ "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-slot": "1.2.3" ++ "@radix-ui/primitive": "1.1.3", ++ "@radix-ui/react-compose-refs": "1.1.2", ++ "@radix-ui/react-context": "1.1.2", ++ "@radix-ui/react-primitive": "2.1.3", ++ "@radix-ui/react-use-controllable-state": "1.2.2", ++ "@radix-ui/react-use-previous": "1.1.1", ++ "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", @@ -3514,33 +3879,24 @@ + } + } + }, -+ "node_modules/@radix-ui/react-select": { -+ "version": "2.2.6", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", -+ "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", ++ "node_modules/@radix-ui/react-toast": { ++ "version": "1.2.15", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", ++ "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", -+ "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", -+ "@radix-ui/react-focus-guards": "1.1.3", -+ "@radix-ui/react-focus-scope": "1.1.7", -+ "@radix-ui/react-id": "1.1.1", -+ "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", ++ "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", -+ "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", -+ "@radix-ui/react-use-previous": "1.1.1", -+ "@radix-ui/react-visually-hidden": "1.2.3", -+ "aria-hidden": "^1.2.4", -+ "react-remove-scroll": "^2.6.3" ++ "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", @@ -3557,239 +3913,167 @@ + } + } + }, -+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-collection": { -+ "version": "1.1.7", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", -+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", ++ "node_modules/@radix-ui/react-use-callback-ref": { ++ "version": "1.1.1", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", ++ "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", -+ "dependencies": { -+ "@radix-ui/react-compose-refs": "1.1.2", -+ "@radix-ui/react-context": "1.1.2", -+ "@radix-ui/react-primitive": "2.1.3", -+ "@radix-ui/react-slot": "1.2.3" -+ }, + "peerDependencies": { + "@types/react": "*", -+ "@types/react-dom": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", -+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true -+ }, -+ "@types/react-dom": { -+ "optional": true + } + } + }, -+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-dismissable-layer": { -+ "version": "1.1.11", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", -+ "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", ++ "node_modules/@radix-ui/react-use-controllable-state": { ++ "version": "1.2.2", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", ++ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/primitive": "1.1.3", -+ "@radix-ui/react-compose-refs": "1.1.2", -+ "@radix-ui/react-primitive": "2.1.3", -+ "@radix-ui/react-use-callback-ref": "1.1.1", -+ "@radix-ui/react-use-escape-keydown": "1.1.1" ++ "@radix-ui/react-use-effect-event": "0.0.2", ++ "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", -+ "@types/react-dom": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", -+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true -+ }, -+ "@types/react-dom": { -+ "optional": true + } + } + }, -+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-focus-scope": { -+ "version": "1.1.7", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", -+ "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", ++ "node_modules/@radix-ui/react-use-effect-event": { ++ "version": "0.0.2", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", ++ "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-compose-refs": "1.1.2", -+ "@radix-ui/react-primitive": "2.1.3", -+ "@radix-ui/react-use-callback-ref": "1.1.1" ++ "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", -+ "@types/react-dom": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", -+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true -+ }, -+ "@types/react-dom": { -+ "optional": true + } + } + }, -+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-popper": { -+ "version": "1.2.8", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", -+ "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", ++ "node_modules/@radix-ui/react-use-escape-keydown": { ++ "version": "1.1.1", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", ++ "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { -+ "@floating-ui/react-dom": "^2.0.0", -+ "@radix-ui/react-arrow": "1.1.7", -+ "@radix-ui/react-compose-refs": "1.1.2", -+ "@radix-ui/react-context": "1.1.2", -+ "@radix-ui/react-primitive": "2.1.3", -+ "@radix-ui/react-use-callback-ref": "1.1.1", -+ "@radix-ui/react-use-layout-effect": "1.1.1", -+ "@radix-ui/react-use-rect": "1.1.1", -+ "@radix-ui/react-use-size": "1.1.1", -+ "@radix-ui/rect": "1.1.1" ++ "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", -+ "@types/react-dom": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", -+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true -+ }, -+ "@types/react-dom": { -+ "optional": true + } + } + }, -+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-arrow": { -+ "version": "1.1.7", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", -+ "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", ++ "node_modules/@radix-ui/react-use-is-hydrated": { ++ "version": "0.1.0", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", ++ "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-primitive": "2.1.3" ++ "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@types/react": "*", -+ "@types/react-dom": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", -+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true -+ }, -+ "@types/react-dom": { -+ "optional": true + } + } + }, -+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-portal": { -+ "version": "1.1.9", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", -+ "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", ++ "node_modules/@radix-ui/react-use-layout-effect": { ++ "version": "1.1.1", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", ++ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", -+ "dependencies": { -+ "@radix-ui/react-primitive": "2.1.3", -+ "@radix-ui/react-use-layout-effect": "1.1.1" -+ }, + "peerDependencies": { + "@types/react": "*", -+ "@types/react-dom": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", -+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true -+ }, -+ "@types/react-dom": { -+ "optional": true + } + } + }, -+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-primitive": { -+ "version": "2.1.3", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", -+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", ++ "node_modules/@radix-ui/react-use-previous": { ++ "version": "1.1.1", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", ++ "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", -+ "dependencies": { -+ "@radix-ui/react-slot": "1.2.3" -+ }, + "peerDependencies": { + "@types/react": "*", -+ "@types/react-dom": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", -+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true -+ }, -+ "@types/react-dom": { -+ "optional": true + } + } + }, -+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-visually-hidden": { -+ "version": "1.2.3", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", -+ "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", ++ "node_modules/@radix-ui/react-use-rect": { ++ "version": "1.1.1", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", ++ "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-primitive": "2.1.3" ++ "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", -+ "@types/react-dom": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", -+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true -+ }, -+ "@types/react-dom": { -+ "optional": true + } + } + }, -+ "node_modules/@radix-ui/react-separator": { -+ "version": "1.1.7", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", -+ "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", ++ "node_modules/@radix-ui/react-use-size": { ++ "version": "1.1.1", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", ++ "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-primitive": "2.1.3" ++ "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", -+ "@types/react-dom": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", -+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true -+ }, -+ "@types/react-dom": { -+ "optional": true + } + } + }, -+ "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { -+ "version": "2.1.3", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", -+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", ++ "node_modules/@radix-ui/react-visually-hidden": { ++ "version": "1.2.3", ++ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", ++ "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-slot": "1.2.3" ++ "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", @@ -3806,265 +4090,96 @@ + } + } + }, -+ "node_modules/@radix-ui/react-slot": { -+ "version": "1.2.3", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", -+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", ++ "node_modules/@radix-ui/rect": { ++ "version": "1.1.1", ++ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", ++ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", ++ "license": "MIT" ++ }, ++ "node_modules/@remix-run/router": { ++ "version": "1.23.1", ++ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.1.tgz", ++ "integrity": "sha512-vDbaOzF7yT2Qs4vO6XV1MHcJv+3dgR1sT+l3B8xxOVhUC336prMvqrvsLL/9Dnw2xr6Qhz4J0dmS0llNAbnUmQ==", + "license": "MIT", -+ "dependencies": { -+ "@radix-ui/react-compose-refs": "1.1.2" -+ }, -+ "peerDependencies": { -+ "@types/react": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" -+ }, -+ "peerDependenciesMeta": { -+ "@types/react": { -+ "optional": true -+ } ++ "engines": { ++ "node": ">=14.0.0" + } + }, -+ "node_modules/@radix-ui/react-switch": { -+ "version": "1.2.6", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", -+ "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", ++ "node_modules/@rolldown/pluginutils": { ++ "version": "1.0.0-beta.27", ++ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", ++ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/@rollup/plugin-node-resolve": { ++ "version": "16.0.3", ++ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", ++ "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", ++ "dev": true, + "license": "MIT", + "dependencies": { -+ "@radix-ui/primitive": "1.1.3", -+ "@radix-ui/react-compose-refs": "1.1.2", -+ "@radix-ui/react-context": "1.1.2", -+ "@radix-ui/react-primitive": "2.1.3", -+ "@radix-ui/react-use-controllable-state": "1.2.2", -+ "@radix-ui/react-use-previous": "1.1.1", -+ "@radix-ui/react-use-size": "1.1.1" ++ "@rollup/pluginutils": "^5.0.1", ++ "@types/resolve": "1.20.2", ++ "deepmerge": "^4.2.2", ++ "is-module": "^1.0.0", ++ "resolve": "^1.22.1" ++ }, ++ "engines": { ++ "node": ">=14.0.0" + }, + "peerDependencies": { -+ "@types/react": "*", -+ "@types/react-dom": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", -+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { -+ "@types/react": { -+ "optional": true -+ }, -+ "@types/react-dom": { ++ "rollup": { + "optional": true + } + } + }, -+ "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-primitive": { -+ "version": "2.1.3", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", -+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", ++ "node_modules/@rollup/pluginutils": { ++ "version": "5.3.0", ++ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", ++ "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", ++ "dev": true, + "license": "MIT", + "dependencies": { -+ "@radix-ui/react-slot": "1.2.3" ++ "@types/estree": "^1.0.0", ++ "estree-walker": "^2.0.2", ++ "picomatch": "^4.0.2" ++ }, ++ "engines": { ++ "node": ">=14.0.0" + }, + "peerDependencies": { -+ "@types/react": "*", -+ "@types/react-dom": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", -+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" ++ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { -+ "@types/react": { -+ "optional": true -+ }, -+ "@types/react-dom": { ++ "rollup": { + "optional": true + } + } + }, -+ "node_modules/@radix-ui/react-use-callback-ref": { -+ "version": "1.1.1", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", -+ "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", ++ "node_modules/@rollup/rollup-android-arm-eabi": { ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", ++ "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", ++ "cpu": [ ++ "arm" ++ ], + "license": "MIT", -+ "peerDependencies": { -+ "@types/react": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" -+ }, -+ "peerDependenciesMeta": { -+ "@types/react": { -+ "optional": true -+ } -+ } ++ "optional": true, ++ "os": [ ++ "android" ++ ] + }, -+ "node_modules/@radix-ui/react-use-controllable-state": { -+ "version": "1.2.2", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", -+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", -+ "license": "MIT", -+ "dependencies": { -+ "@radix-ui/react-use-effect-event": "0.0.2", -+ "@radix-ui/react-use-layout-effect": "1.1.1" -+ }, -+ "peerDependencies": { -+ "@types/react": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" -+ }, -+ "peerDependenciesMeta": { -+ "@types/react": { -+ "optional": true -+ } -+ } -+ }, -+ "node_modules/@radix-ui/react-use-effect-event": { -+ "version": "0.0.2", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", -+ "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", -+ "license": "MIT", -+ "dependencies": { -+ "@radix-ui/react-use-layout-effect": "1.1.1" -+ }, -+ "peerDependencies": { -+ "@types/react": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" -+ }, -+ "peerDependenciesMeta": { -+ "@types/react": { -+ "optional": true -+ } -+ } -+ }, -+ "node_modules/@radix-ui/react-use-escape-keydown": { -+ "version": "1.1.1", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", -+ "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", -+ "license": "MIT", -+ "dependencies": { -+ "@radix-ui/react-use-callback-ref": "1.1.1" -+ }, -+ "peerDependencies": { -+ "@types/react": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" -+ }, -+ "peerDependenciesMeta": { -+ "@types/react": { -+ "optional": true -+ } -+ } -+ }, -+ "node_modules/@radix-ui/react-use-is-hydrated": { -+ "version": "0.1.0", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", -+ "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", -+ "license": "MIT", -+ "dependencies": { -+ "use-sync-external-store": "^1.5.0" -+ }, -+ "peerDependencies": { -+ "@types/react": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" -+ }, -+ "peerDependenciesMeta": { -+ "@types/react": { -+ "optional": true -+ } -+ } -+ }, -+ "node_modules/@radix-ui/react-use-layout-effect": { -+ "version": "1.1.1", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", -+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", -+ "license": "MIT", -+ "peerDependencies": { -+ "@types/react": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" -+ }, -+ "peerDependenciesMeta": { -+ "@types/react": { -+ "optional": true -+ } -+ } -+ }, -+ "node_modules/@radix-ui/react-use-previous": { -+ "version": "1.1.1", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", -+ "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", -+ "license": "MIT", -+ "peerDependencies": { -+ "@types/react": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" -+ }, -+ "peerDependenciesMeta": { -+ "@types/react": { -+ "optional": true -+ } -+ } -+ }, -+ "node_modules/@radix-ui/react-use-rect": { -+ "version": "1.1.1", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", -+ "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", -+ "license": "MIT", -+ "dependencies": { -+ "@radix-ui/rect": "1.1.1" -+ }, -+ "peerDependencies": { -+ "@types/react": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" -+ }, -+ "peerDependenciesMeta": { -+ "@types/react": { -+ "optional": true -+ } -+ } -+ }, -+ "node_modules/@radix-ui/react-use-size": { -+ "version": "1.1.1", -+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", -+ "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", -+ "license": "MIT", -+ "dependencies": { -+ "@radix-ui/react-use-layout-effect": "1.1.1" -+ }, -+ "peerDependencies": { -+ "@types/react": "*", -+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" -+ }, -+ "peerDependenciesMeta": { -+ "@types/react": { -+ "optional": true -+ } -+ } -+ }, -+ "node_modules/@radix-ui/rect": { -+ "version": "1.1.1", -+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", -+ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", -+ "license": "MIT" -+ }, -+ "node_modules/@remix-run/router": { -+ "version": "1.23.0", -+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", -+ "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", -+ "license": "MIT", -+ "engines": { -+ "node": ">=14.0.0" -+ } -+ }, -+ "node_modules/@rollup/rollup-android-arm-eabi": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.2.tgz", -+ "integrity": "sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==", -+ "cpu": [ -+ "arm" -+ ], -+ "license": "MIT", -+ "optional": true, -+ "os": [ -+ "android" -+ ] -+ }, -+ "node_modules/@rollup/rollup-android-arm64": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.2.tgz", -+ "integrity": "sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g==", -+ "cpu": [ -+ "arm64" -+ ], ++ "node_modules/@rollup/rollup-android-arm64": { ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", ++ "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", ++ "cpu": [ ++ "arm64" ++ ], + "license": "MIT", + "optional": true, + "os": [ @@ -4072,9 +4187,9 @@ + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.2.tgz", -+ "integrity": "sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", ++ "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], @@ -4085,9 +4200,9 @@ + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.2.tgz", -+ "integrity": "sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", ++ "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], @@ -4098,9 +4213,9 @@ + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.2.tgz", -+ "integrity": "sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", ++ "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], @@ -4111,9 +4226,9 @@ + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.2.tgz", -+ "integrity": "sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", ++ "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], @@ -4124,9 +4239,9 @@ + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.2.tgz", -+ "integrity": "sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", ++ "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], @@ -4137,9 +4252,9 @@ + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.2.tgz", -+ "integrity": "sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", ++ "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], @@ -4150,9 +4265,9 @@ + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.2.tgz", -+ "integrity": "sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", ++ "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], @@ -4163,9 +4278,9 @@ + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.2.tgz", -+ "integrity": "sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", ++ "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], @@ -4176,9 +4291,9 @@ + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.50.2.tgz", -+ "integrity": "sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", ++ "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], @@ -4189,9 +4304,9 @@ + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.2.tgz", -+ "integrity": "sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", ++ "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], @@ -4202,9 +4317,9 @@ + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.2.tgz", -+ "integrity": "sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", ++ "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], @@ -4215,9 +4330,9 @@ + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.2.tgz", -+ "integrity": "sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", ++ "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], @@ -4228,9 +4343,9 @@ + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.2.tgz", -+ "integrity": "sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", ++ "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], @@ -4241,9 +4356,9 @@ + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.2.tgz", -+ "integrity": "sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", ++ "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], @@ -4254,9 +4369,9 @@ + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.2.tgz", -+ "integrity": "sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", ++ "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], @@ -4267,9 +4382,9 @@ + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.2.tgz", -+ "integrity": "sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", ++ "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], @@ -4280,9 +4395,9 @@ + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.2.tgz", -+ "integrity": "sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", ++ "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], @@ -4293,9 +4408,9 @@ + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.2.tgz", -+ "integrity": "sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", ++ "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], @@ -4305,10 +4420,23 @@ + "win32" + ] + }, ++ "node_modules/@rollup/rollup-win32-x64-gnu": { ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", ++ "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", ++ "cpu": [ ++ "x64" ++ ], ++ "license": "MIT", ++ "optional": true, ++ "os": [ ++ "win32" ++ ] ++ }, + "node_modules/@rollup/rollup-win32-x64-msvc": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.2.tgz", -+ "integrity": "sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", ++ "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], @@ -4372,12 +4500,12 @@ + "license": "MIT" + }, + "node_modules/@smithy/abort-controller": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.1.1.tgz", -+ "integrity": "sha512-vkzula+IwRvPR6oKQhMYioM3A/oX/lFCZiwuxkQbRhqJS2S4YRY2k7k/SyR2jMf3607HLtbEwlRxi0ndXHMjRg==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz", ++ "integrity": "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/types": "^4.5.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4385,9 +4513,9 @@ + } + }, + "node_modules/@smithy/chunked-blob-reader": { -+ "version": "5.1.0", -+ "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.1.0.tgz", -+ "integrity": "sha512-a36AtR7Q7XOhRPt6F/7HENmTWcB8kN7mDJcOFM/+FuKO6x88w8MQJfYCufMWh4fGyVkPjUh3Rrz/dnqFQdo6OQ==", ++ "version": "5.2.0", ++ "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz", ++ "integrity": "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" @@ -4397,12 +4525,12 @@ + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { -+ "version": "4.1.0", -+ "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.1.0.tgz", -+ "integrity": "sha512-Bnv0B3nSlfB2mPO0WgM49I/prl7+kamF042rrf3ezJ3Z4C7csPYvyYgZfXTGXwXfj1mAwDWjE/ybIf49PzFzvA==", ++ "version": "4.2.1", ++ "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.1.tgz", ++ "integrity": "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/util-base64": "^4.1.0", ++ "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4410,15 +4538,16 @@ + } + }, + "node_modules/@smithy/config-resolver": { -+ "version": "4.2.2", -+ "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.2.2.tgz", -+ "integrity": "sha512-IT6MatgBWagLybZl1xQcURXRICvqz1z3APSCAI9IqdvfCkrA7RaQIEfgC6G/KvfxnDfQUDqFV+ZlixcuFznGBQ==", ++ "version": "4.4.3", ++ "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz", ++ "integrity": "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/node-config-provider": "^4.2.2", -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-config-provider": "^4.1.0", -+ "@smithy/util-middleware": "^4.1.1", ++ "@smithy/node-config-provider": "^4.3.5", ++ "@smithy/types": "^4.9.0", ++ "@smithy/util-config-provider": "^4.2.0", ++ "@smithy/util-endpoints": "^3.2.5", ++ "@smithy/util-middleware": "^4.2.5", + "tslib": "^2.6.2" + }, + "engines": { @@ -4426,37 +4555,36 @@ + } + }, + "node_modules/@smithy/core": { -+ "version": "3.11.0", -+ "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.11.0.tgz", -+ "integrity": "sha512-Abs5rdP1o8/OINtE49wwNeWuynCu0kme1r4RI3VXVrHr4odVDG7h7mTnw1WXXfN5Il+c25QOnrdL2y56USfxkA==", -+ "license": "Apache-2.0", -+ "dependencies": { -+ "@smithy/middleware-serde": "^4.1.1", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-base64": "^4.1.0", -+ "@smithy/util-body-length-browser": "^4.1.0", -+ "@smithy/util-middleware": "^4.1.1", -+ "@smithy/util-stream": "^4.3.1", -+ "@smithy/util-utf8": "^4.1.0", -+ "@types/uuid": "^9.0.1", -+ "tslib": "^2.6.2", -+ "uuid": "^9.0.1" ++ "version": "3.18.5", ++ "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.18.5.tgz", ++ "integrity": "sha512-6gnIz3h+PEPQGDj8MnRSjDvKBah042jEoPgjFGJ4iJLBE78L4lY/n98x14XyPF4u3lN179Ub/ZKFY5za9GeLQw==", ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@smithy/middleware-serde": "^4.2.6", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/types": "^4.9.0", ++ "@smithy/util-base64": "^4.3.0", ++ "@smithy/util-body-length-browser": "^4.2.0", ++ "@smithy/util-middleware": "^4.2.5", ++ "@smithy/util-stream": "^4.5.6", ++ "@smithy/util-utf8": "^4.2.0", ++ "@smithy/uuid": "^1.1.0", ++ "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { -+ "version": "4.1.2", -+ "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.1.2.tgz", -+ "integrity": "sha512-JlYNq8TShnqCLg0h+afqe2wLAwZpuoSgOyzhYvTgbiKBWRov+uUve+vrZEQO6lkdLOWPh7gK5dtb9dS+KGendg==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz", ++ "integrity": "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/node-config-provider": "^4.2.2", -+ "@smithy/property-provider": "^4.1.1", -+ "@smithy/types": "^4.5.0", -+ "@smithy/url-parser": "^4.1.1", ++ "@smithy/node-config-provider": "^4.3.5", ++ "@smithy/property-provider": "^4.2.5", ++ "@smithy/types": "^4.9.0", ++ "@smithy/url-parser": "^4.2.5", + "tslib": "^2.6.2" + }, + "engines": { @@ -4464,14 +4592,14 @@ + } + }, + "node_modules/@smithy/eventstream-codec": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.1.1.tgz", -+ "integrity": "sha512-PwkQw1hZwHTQB6X5hSUWz2OSeuj5Z6enWuAqke7DgWoP3t6vg3ktPpqPz3Erkn6w+tmsl8Oss6nrgyezoea2Iw==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.5.tgz", ++ "integrity": "sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-hex-encoding": "^4.1.0", ++ "@smithy/types": "^4.9.0", ++ "@smithy/util-hex-encoding": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4479,13 +4607,13 @@ + } + }, + "node_modules/@smithy/eventstream-serde-browser": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.1.1.tgz", -+ "integrity": "sha512-Q9QWdAzRaIuVkefupRPRFAasaG/droBqn1feiMnmLa+LLEUG45pqX1+FurHFmlqiCfobB3nUlgoJfeXZsr7MPA==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.5.tgz", ++ "integrity": "sha512-HohfmCQZjppVnKX2PnXlf47CW3j92Ki6T/vkAT2DhBR47e89pen3s4fIa7otGTtrVxmj7q+IhH0RnC5kpR8wtw==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/eventstream-serde-universal": "^4.1.1", -+ "@smithy/types": "^4.5.0", ++ "@smithy/eventstream-serde-universal": "^4.2.5", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4493,12 +4621,12 @@ + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { -+ "version": "4.2.1", -+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.2.1.tgz", -+ "integrity": "sha512-oSUkF9zDN9zcOUBMtxp8RewJlh71E9NoHWU8jE3hU9JMYCsmW4assVTpgic/iS3/dM317j6hO5x18cc3XrfvEw==", ++ "version": "4.3.5", ++ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.5.tgz", ++ "integrity": "sha512-ibjQjM7wEXtECiT6my1xfiMH9IcEczMOS6xiCQXoUIYSj5b1CpBbJ3VYbdwDy8Vcg5JHN7eFpOCGk8nyZAltNQ==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/types": "^4.5.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4506,13 +4634,13 @@ + } + }, + "node_modules/@smithy/eventstream-serde-node": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.1.1.tgz", -+ "integrity": "sha512-tn6vulwf/ScY0vjhzptSJuDJJqlhNtUjkxJ4wiv9E3SPoEqTEKbaq6bfqRO7nvhTG29ALICRcvfFheOUPl8KNA==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.5.tgz", ++ "integrity": "sha512-+elOuaYx6F2H6x1/5BQP5ugv12nfJl66GhxON8+dWVUEDJ9jah/A0tayVdkLRP0AeSac0inYkDz5qBFKfVp2Gg==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/eventstream-serde-universal": "^4.1.1", -+ "@smithy/types": "^4.5.0", ++ "@smithy/eventstream-serde-universal": "^4.2.5", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4520,13 +4648,13 @@ + } + }, + "node_modules/@smithy/eventstream-serde-universal": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.1.1.tgz", -+ "integrity": "sha512-uLOAiM/Dmgh2CbEXQx+6/ssK7fbzFhd+LjdyFxXid5ZBCbLHTFHLdD/QbXw5aEDsLxQhgzDxLLsZhsftAYwHJA==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.5.tgz", ++ "integrity": "sha512-G9WSqbST45bmIFaeNuP/EnC19Rhp54CcVdX9PDL1zyEB514WsDVXhlyihKlGXnRycmHNmVv88Bvvt4EYxWef/Q==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/eventstream-codec": "^4.1.1", -+ "@smithy/types": "^4.5.0", ++ "@smithy/eventstream-codec": "^4.2.5", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4534,15 +4662,15 @@ + } + }, + "node_modules/@smithy/fetch-http-handler": { -+ "version": "5.2.1", -+ "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.2.1.tgz", -+ "integrity": "sha512-5/3wxKNtV3wO/hk1is+CZUhL8a1yy/U+9u9LKQ9kZTkMsHaQjJhc3stFfiujtMnkITjzWfndGA2f7g9Uh9vKng==", ++ "version": "5.3.6", ++ "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz", ++ "integrity": "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/querystring-builder": "^4.1.1", -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-base64": "^4.1.0", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/querystring-builder": "^4.2.5", ++ "@smithy/types": "^4.9.0", ++ "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4550,14 +4678,14 @@ + } + }, + "node_modules/@smithy/hash-blob-browser": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.1.1.tgz", -+ "integrity": "sha512-avAtk++s1e/1VODf+rg7c9R2pB5G9y8yaJaGY4lPZI2+UIqVyuSDMikWjeWfBVmFZ3O7NpDxBbUCyGhThVUKWQ==", ++ "version": "4.2.6", ++ "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.6.tgz", ++ "integrity": "sha512-8P//tA8DVPk+3XURk2rwcKgYwFvwGwmJH/wJqQiSKwXZtf/LiZK+hbUZmPj/9KzM+OVSwe4o85KTp5x9DUZTjw==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/chunked-blob-reader": "^5.1.0", -+ "@smithy/chunked-blob-reader-native": "^4.1.0", -+ "@smithy/types": "^4.5.0", ++ "@smithy/chunked-blob-reader": "^5.2.0", ++ "@smithy/chunked-blob-reader-native": "^4.2.1", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4565,14 +4693,14 @@ + } + }, + "node_modules/@smithy/hash-node": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.1.1.tgz", -+ "integrity": "sha512-H9DIU9WBLhYrvPs9v4sYvnZ1PiAI0oc8CgNQUJ1rpN3pP7QADbTOUjchI2FB764Ub0DstH5xbTqcMJu1pnVqxA==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.5.tgz", ++ "integrity": "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-buffer-from": "^4.1.0", -+ "@smithy/util-utf8": "^4.1.0", ++ "@smithy/types": "^4.9.0", ++ "@smithy/util-buffer-from": "^4.2.0", ++ "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4580,13 +4708,13 @@ + } + }, + "node_modules/@smithy/hash-stream-node": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.1.1.tgz", -+ "integrity": "sha512-3ztT4pV0Moazs3JAYFdfKk11kYFDo4b/3R3+xVjIm6wY9YpJf+xfz+ocEnNKcWAdcmSMqi168i2EMaKmJHbJMA==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.5.tgz", ++ "integrity": "sha512-6+do24VnEyvWcGdHXomlpd0m8bfZePpUKBy7m311n+JuRwug8J4dCanJdTymx//8mi0nlkflZBvJe+dEO/O12Q==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-utf8": "^4.1.0", ++ "@smithy/types": "^4.9.0", ++ "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4594,12 +4722,12 @@ + } + }, + "node_modules/@smithy/invalid-dependency": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.1.1.tgz", -+ "integrity": "sha512-1AqLyFlfrrDkyES8uhINRlJXmHA2FkG+3DY8X+rmLSqmFwk3DJnvhyGzyByPyewh2jbmV+TYQBEfngQax8IFGg==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.5.tgz", ++ "integrity": "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/types": "^4.5.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4607,9 +4735,9 @@ + } + }, + "node_modules/@smithy/is-array-buffer": { -+ "version": "4.1.0", -+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.1.0.tgz", -+ "integrity": "sha512-ePTYUOV54wMogio+he4pBybe8fwg4sDvEVDBU8ZlHOZXbXK3/C0XfJgUCu6qAZcawv05ZhZzODGUerFBPsPUDQ==", ++ "version": "4.2.0", ++ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", ++ "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" @@ -4619,13 +4747,13 @@ + } + }, + "node_modules/@smithy/md5-js": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.1.1.tgz", -+ "integrity": "sha512-MvWXKK743BuHjr/hnWuT6uStdKEaoqxHAQUvbKJPPZM5ZojTNFI5D+47BoQfBE5RgGlRRty05EbWA+NXDv+hIA==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.5.tgz", ++ "integrity": "sha512-Bt6jpSTMWfjCtC0s79gZ/WZ1w90grfmopVOWqkI2ovhjpD5Q2XRXuecIPB9689L2+cCySMbaXDhBPU56FKNDNg==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-utf8": "^4.1.0", ++ "@smithy/types": "^4.9.0", ++ "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4633,13 +4761,13 @@ + } + }, + "node_modules/@smithy/middleware-content-length": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.1.1.tgz", -+ "integrity": "sha512-9wlfBBgTsRvC2JxLJxv4xDGNBrZuio3AgSl0lSFX7fneW2cGskXTYpFxCdRYD2+5yzmsiTuaAJD1Wp7gWt9y9w==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.5.tgz", ++ "integrity": "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/types": "^4.5.0", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4647,18 +4775,18 @@ + } + }, + "node_modules/@smithy/middleware-endpoint": { -+ "version": "4.2.2", -+ "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.2.2.tgz", -+ "integrity": "sha512-M51KcwD+UeSOFtpALGf5OijWt915aQT5eJhqnMKJt7ZTfDfNcvg2UZgIgTZUoiORawb6o5lk4n3rv7vnzQXgsA==", -+ "license": "Apache-2.0", -+ "dependencies": { -+ "@smithy/core": "^3.11.0", -+ "@smithy/middleware-serde": "^4.1.1", -+ "@smithy/node-config-provider": "^4.2.2", -+ "@smithy/shared-ini-file-loader": "^4.2.0", -+ "@smithy/types": "^4.5.0", -+ "@smithy/url-parser": "^4.1.1", -+ "@smithy/util-middleware": "^4.1.1", ++ "version": "4.3.12", ++ "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.12.tgz", ++ "integrity": "sha512-9pAX/H+VQPzNbouhDhkW723igBMLgrI8OtX+++M7iKJgg/zY/Ig3i1e6seCcx22FWhE6Q/S61BRdi2wXBORT+A==", ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@smithy/core": "^3.18.5", ++ "@smithy/middleware-serde": "^4.2.6", ++ "@smithy/node-config-provider": "^4.3.5", ++ "@smithy/shared-ini-file-loader": "^4.4.0", ++ "@smithy/types": "^4.9.0", ++ "@smithy/url-parser": "^4.2.5", ++ "@smithy/util-middleware": "^4.2.5", + "tslib": "^2.6.2" + }, + "engines": { @@ -4666,34 +4794,33 @@ + } + }, + "node_modules/@smithy/middleware-retry": { -+ "version": "4.2.2", -+ "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.2.2.tgz", -+ "integrity": "sha512-KZJueEOO+PWqflv2oGx9jICpHdBYXwCI19j7e2V3IMwKgFcXc9D9q/dsTf4B+uCnYxjNoS1jpyv6pGNGRsKOXA==", -+ "license": "Apache-2.0", -+ "dependencies": { -+ "@smithy/node-config-provider": "^4.2.2", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/service-error-classification": "^4.1.1", -+ "@smithy/smithy-client": "^4.6.2", -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-middleware": "^4.1.1", -+ "@smithy/util-retry": "^4.1.1", -+ "@types/uuid": "^9.0.1", -+ "tslib": "^2.6.2", -+ "uuid": "^9.0.1" ++ "version": "4.4.12", ++ "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.12.tgz", ++ "integrity": "sha512-S4kWNKFowYd0lID7/DBqWHOQxmxlsf0jBaos9chQZUWTVOjSW1Ogyh8/ib5tM+agFDJ/TCxuCTvrnlc+9cIBcQ==", ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@smithy/node-config-provider": "^4.3.5", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/service-error-classification": "^4.2.5", ++ "@smithy/smithy-client": "^4.9.8", ++ "@smithy/types": "^4.9.0", ++ "@smithy/util-middleware": "^4.2.5", ++ "@smithy/util-retry": "^4.2.5", ++ "@smithy/uuid": "^1.1.0", ++ "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.1.1.tgz", -+ "integrity": "sha512-lh48uQdbCoj619kRouev5XbWhCwRKLmphAif16c4J6JgJ4uXjub1PI6RL38d3BLliUvSso6klyB/LTNpWSNIyg==", ++ "version": "4.2.6", ++ "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.6.tgz", ++ "integrity": "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/types": "^4.5.0", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4701,12 +4828,12 @@ + } + }, + "node_modules/@smithy/middleware-stack": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.1.1.tgz", -+ "integrity": "sha512-ygRnniqNcDhHzs6QAPIdia26M7e7z9gpkIMUe/pK0RsrQ7i5MblwxY8078/QCnGq6AmlUUWgljK2HlelsKIb/A==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.5.tgz", ++ "integrity": "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/types": "^4.5.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4714,14 +4841,14 @@ + } + }, + "node_modules/@smithy/node-config-provider": { -+ "version": "4.2.2", -+ "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.2.2.tgz", -+ "integrity": "sha512-SYGTKyPvyCfEzIN5rD8q/bYaOPZprYUPD2f5g9M7OjaYupWOoQFYJ5ho+0wvxIRf471i2SR4GoiZ2r94Jq9h6A==", ++ "version": "4.3.5", ++ "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.5.tgz", ++ "integrity": "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/property-provider": "^4.1.1", -+ "@smithy/shared-ini-file-loader": "^4.2.0", -+ "@smithy/types": "^4.5.0", ++ "@smithy/property-provider": "^4.2.5", ++ "@smithy/shared-ini-file-loader": "^4.4.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4729,15 +4856,15 @@ + } + }, + "node_modules/@smithy/node-http-handler": { -+ "version": "4.2.1", -+ "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.2.1.tgz", -+ "integrity": "sha512-REyybygHlxo3TJICPF89N2pMQSf+p+tBJqpVe1+77Cfi9HBPReNjTgtZ1Vg73exq24vkqJskKDpfF74reXjxfw==", ++ "version": "4.4.5", ++ "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz", ++ "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/abort-controller": "^4.1.1", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/querystring-builder": "^4.1.1", -+ "@smithy/types": "^4.5.0", ++ "@smithy/abort-controller": "^4.2.5", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/querystring-builder": "^4.2.5", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4745,12 +4872,12 @@ + } + }, + "node_modules/@smithy/property-provider": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.1.1.tgz", -+ "integrity": "sha512-gm3ZS7DHxUbzC2wr8MUCsAabyiXY0gaj3ROWnhSx/9sPMc6eYLMM4rX81w1zsMaObj2Lq3PZtNCC1J6lpEY7zg==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz", ++ "integrity": "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/types": "^4.5.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4758,12 +4885,12 @@ + } + }, + "node_modules/@smithy/protocol-http": { -+ "version": "5.2.1", -+ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.2.1.tgz", -+ "integrity": "sha512-T8SlkLYCwfT/6m33SIU/JOVGNwoelkrvGjFKDSDtVvAXj/9gOT78JVJEas5a+ETjOu4SVvpCstKgd0PxSu/aHw==", ++ "version": "5.3.5", ++ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz", ++ "integrity": "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/types": "^4.5.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4771,13 +4898,13 @@ + } + }, + "node_modules/@smithy/querystring-builder": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.1.1.tgz", -+ "integrity": "sha512-J9b55bfimP4z/Jg1gNo+AT84hr90p716/nvxDkPGCD4W70MPms0h8KF50RDRgBGZeL83/u59DWNqJv6tEP/DHA==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz", ++ "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-uri-escape": "^4.1.0", ++ "@smithy/types": "^4.9.0", ++ "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4785,12 +4912,12 @@ + } + }, + "node_modules/@smithy/querystring-parser": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.1.1.tgz", -+ "integrity": "sha512-63TEp92YFz0oQ7Pj9IuI3IgnprP92LrZtRAkE3c6wLWJxfy/yOPRt39IOKerVr0JS770olzl0kGafXlAXZ1vng==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz", ++ "integrity": "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/types": "^4.5.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4798,24 +4925,24 @@ + } + }, + "node_modules/@smithy/service-error-classification": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.1.1.tgz", -+ "integrity": "sha512-Iam75b/JNXyDE41UvrlM6n8DNOa/r1ylFyvgruTUx7h2Uk7vDNV9AAwP1vfL1fOL8ls0xArwEGVcGZVd7IO/Cw==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz", ++ "integrity": "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/types": "^4.5.0" ++ "@smithy/types": "^4.9.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { -+ "version": "4.2.0", -+ "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.2.0.tgz", -+ "integrity": "sha512-OQTfmIEp2LLuWdxa8nEEPhZmiOREO6bcB6pjs0AySf4yiZhl6kMOfqmcwcY8BaBPX+0Tb+tG7/Ia/6mwpoZ7Pw==", ++ "version": "4.4.0", ++ "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz", ++ "integrity": "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/types": "^4.5.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4823,18 +4950,18 @@ + } + }, + "node_modules/@smithy/signature-v4": { -+ "version": "5.2.1", -+ "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.2.1.tgz", -+ "integrity": "sha512-M9rZhWQLjlQVCCR37cSjHfhriGRN+FQ8UfgrYNufv66TJgk+acaggShl3KS5U/ssxivvZLlnj7QH2CUOKlxPyA==", ++ "version": "5.3.5", ++ "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz", ++ "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/is-array-buffer": "^4.1.0", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-hex-encoding": "^4.1.0", -+ "@smithy/util-middleware": "^4.1.1", -+ "@smithy/util-uri-escape": "^4.1.0", -+ "@smithy/util-utf8": "^4.1.0", ++ "@smithy/is-array-buffer": "^4.2.0", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/types": "^4.9.0", ++ "@smithy/util-hex-encoding": "^4.2.0", ++ "@smithy/util-middleware": "^4.2.5", ++ "@smithy/util-uri-escape": "^4.2.0", ++ "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4842,17 +4969,17 @@ + } + }, + "node_modules/@smithy/smithy-client": { -+ "version": "4.6.2", -+ "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.6.2.tgz", -+ "integrity": "sha512-u82cjh/x7MlMat76Z38TRmEcG6JtrrxN4N2CSNG5o2v2S3hfLAxRgSgFqf0FKM3dglH41Evknt/HOX+7nfzZ3g==", ++ "version": "4.9.8", ++ "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.8.tgz", ++ "integrity": "sha512-8xgq3LgKDEFoIrLWBho/oYKyWByw9/corz7vuh1upv7ZBm0ZMjGYBhbn6v643WoIqA9UTcx5A5htEp/YatUwMA==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/core": "^3.11.0", -+ "@smithy/middleware-endpoint": "^4.2.2", -+ "@smithy/middleware-stack": "^4.1.1", -+ "@smithy/protocol-http": "^5.2.1", -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-stream": "^4.3.1", ++ "@smithy/core": "^3.18.5", ++ "@smithy/middleware-endpoint": "^4.3.12", ++ "@smithy/middleware-stack": "^4.2.5", ++ "@smithy/protocol-http": "^5.3.5", ++ "@smithy/types": "^4.9.0", ++ "@smithy/util-stream": "^4.5.6", + "tslib": "^2.6.2" + }, + "engines": { @@ -4860,9 +4987,9 @@ + } + }, + "node_modules/@smithy/types": { -+ "version": "4.5.0", -+ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.5.0.tgz", -+ "integrity": "sha512-RkUpIOsVlAwUIZXO1dsz8Zm+N72LClFfsNqf173catVlvRZiwPy0x2u0JLEA4byreOPKDZPGjmPDylMoP8ZJRg==", ++ "version": "4.9.0", ++ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", ++ "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" @@ -4872,13 +4999,13 @@ + } + }, + "node_modules/@smithy/url-parser": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.1.1.tgz", -+ "integrity": "sha512-bx32FUpkhcaKlEoOMbScvc93isaSiRM75pQ5IgIBaMkT7qMlIibpPRONyx/0CvrXHzJLpOn/u6YiDX2hcvs7Dg==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz", ++ "integrity": "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/querystring-parser": "^4.1.1", -+ "@smithy/types": "^4.5.0", ++ "@smithy/querystring-parser": "^4.2.5", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4886,13 +5013,13 @@ + } + }, + "node_modules/@smithy/util-base64": { -+ "version": "4.1.0", -+ "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.1.0.tgz", -+ "integrity": "sha512-RUGd4wNb8GeW7xk+AY5ghGnIwM96V0l2uzvs/uVHf+tIuVX2WSvynk5CxNoBCsM2rQRSZElAo9rt3G5mJ/gktQ==", ++ "version": "4.3.0", ++ "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", ++ "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/util-buffer-from": "^4.1.0", -+ "@smithy/util-utf8": "^4.1.0", ++ "@smithy/util-buffer-from": "^4.2.0", ++ "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4900,9 +5027,9 @@ + } + }, + "node_modules/@smithy/util-body-length-browser": { -+ "version": "4.1.0", -+ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.1.0.tgz", -+ "integrity": "sha512-V2E2Iez+bo6bUMOTENPr6eEmepdY8Hbs+Uc1vkDKgKNA/brTJqOW/ai3JO1BGj9GbCeLqw90pbbH7HFQyFotGQ==", ++ "version": "4.2.0", ++ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", ++ "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" @@ -4912,9 +5039,9 @@ + } + }, + "node_modules/@smithy/util-body-length-node": { -+ "version": "4.1.0", -+ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.1.0.tgz", -+ "integrity": "sha512-BOI5dYjheZdgR9XiEM3HJcEMCXSoqbzu7CzIgYrx0UtmvtC3tC2iDGpJLsSRFffUpy8ymsg2ARMP5fR8mtuUQQ==", ++ "version": "4.2.1", ++ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", ++ "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" @@ -4924,12 +5051,12 @@ + } + }, + "node_modules/@smithy/util-buffer-from": { -+ "version": "4.1.0", -+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.1.0.tgz", -+ "integrity": "sha512-N6yXcjfe/E+xKEccWEKzK6M+crMrlwaCepKja0pNnlSkm6SjAeLKKA++er5Ba0I17gvKfN/ThV+ZOx/CntKTVw==", ++ "version": "4.2.0", ++ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", ++ "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/is-array-buffer": "^4.1.0", ++ "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4937,9 +5064,9 @@ + } + }, + "node_modules/@smithy/util-config-provider": { -+ "version": "4.1.0", -+ "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.1.0.tgz", -+ "integrity": "sha512-swXz2vMjrP1ZusZWVTB/ai5gK+J8U0BWvP10v9fpcFvg+Xi/87LHvHfst2IgCs1i0v4qFZfGwCmeD/KNCdJZbQ==", ++ "version": "4.2.0", ++ "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", ++ "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" @@ -4949,15 +5076,14 @@ + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { -+ "version": "4.1.2", -+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.1.2.tgz", -+ "integrity": "sha512-QKrOw01DvNHKgY+3p4r9Ut4u6EHLVZ01u6SkOMe6V6v5C+nRPXJeWh72qCT1HgwU3O7sxAIu23nNh+FOpYVZKA==", ++ "version": "4.3.11", ++ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.11.tgz", ++ "integrity": "sha512-yHv+r6wSQXEXTPVCIQTNmXVWs7ekBTpMVErjqZoWkYN75HIFN5y9+/+sYOejfAuvxWGvgzgxbTHa/oz61YTbKw==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/property-provider": "^4.1.1", -+ "@smithy/smithy-client": "^4.6.2", -+ "@smithy/types": "^4.5.0", -+ "bowser": "^2.11.0", ++ "@smithy/property-provider": "^4.2.5", ++ "@smithy/smithy-client": "^4.9.8", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4965,17 +5091,17 @@ + } + }, + "node_modules/@smithy/util-defaults-mode-node": { -+ "version": "4.1.2", -+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.1.2.tgz", -+ "integrity": "sha512-l2yRmSfx5haYHswPxMmCR6jGwgPs5LjHLuBwlj9U7nNBMS43YV/eevj+Xq1869UYdiynnMrCKtoOYQcwtb6lKg==", ++ "version": "4.2.14", ++ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.14.tgz", ++ "integrity": "sha512-ljZN3iRvaJUgulfvobIuG97q1iUuCMrvXAlkZ4msY+ZuVHQHDIqn7FKZCEj+bx8omz6kF5yQXms/xhzjIO5XiA==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/config-resolver": "^4.2.2", -+ "@smithy/credential-provider-imds": "^4.1.2", -+ "@smithy/node-config-provider": "^4.2.2", -+ "@smithy/property-provider": "^4.1.1", -+ "@smithy/smithy-client": "^4.6.2", -+ "@smithy/types": "^4.5.0", ++ "@smithy/config-resolver": "^4.4.3", ++ "@smithy/credential-provider-imds": "^4.2.5", ++ "@smithy/node-config-provider": "^4.3.5", ++ "@smithy/property-provider": "^4.2.5", ++ "@smithy/smithy-client": "^4.9.8", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4983,13 +5109,13 @@ + } + }, + "node_modules/@smithy/util-endpoints": { -+ "version": "3.1.2", -+ "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.1.2.tgz", -+ "integrity": "sha512-+AJsaaEGb5ySvf1SKMRrPZdYHRYSzMkCoK16jWnIMpREAnflVspMIDeCVSZJuj+5muZfgGpNpijE3mUNtjv01Q==", ++ "version": "3.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz", ++ "integrity": "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/node-config-provider": "^4.2.2", -+ "@smithy/types": "^4.5.0", ++ "@smithy/node-config-provider": "^4.3.5", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -4997,9 +5123,9 @@ + } + }, + "node_modules/@smithy/util-hex-encoding": { -+ "version": "4.1.0", -+ "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.1.0.tgz", -+ "integrity": "sha512-1LcueNN5GYC4tr8mo14yVYbh/Ur8jHhWOxniZXii+1+ePiIbsLZ5fEI0QQGtbRRP5mOhmooos+rLmVASGGoq5w==", ++ "version": "4.2.0", ++ "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", ++ "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" @@ -5009,12 +5135,12 @@ + } + }, + "node_modules/@smithy/util-middleware": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.1.1.tgz", -+ "integrity": "sha512-CGmZ72mL29VMfESz7S6dekqzCh8ZISj3B+w0g1hZFXaOjGTVaSqfAEFAq8EGp8fUL+Q2l8aqNmt8U1tglTikeg==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz", ++ "integrity": "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/types": "^4.5.0", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -5022,13 +5148,13 @@ + } + }, + "node_modules/@smithy/util-retry": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.1.1.tgz", -+ "integrity": "sha512-jGeybqEZ/LIordPLMh5bnmnoIgsqnp4IEimmUp5c5voZ8yx+5kAlN5+juyr7p+f7AtZTgvhmInQk4Q0UVbrZ0Q==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz", ++ "integrity": "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/service-error-classification": "^4.1.1", -+ "@smithy/types": "^4.5.0", ++ "@smithy/service-error-classification": "^4.2.5", ++ "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -5036,18 +5162,18 @@ + } + }, + "node_modules/@smithy/util-stream": { -+ "version": "4.3.1", -+ "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.3.1.tgz", -+ "integrity": "sha512-khKkW/Jqkgh6caxMWbMuox9+YfGlsk9OnHOYCGVEdYQb/XVzcORXHLYUubHmmda0pubEDncofUrPNniS9d+uAA==", ++ "version": "4.5.6", ++ "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz", ++ "integrity": "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/fetch-http-handler": "^5.2.1", -+ "@smithy/node-http-handler": "^4.2.1", -+ "@smithy/types": "^4.5.0", -+ "@smithy/util-base64": "^4.1.0", -+ "@smithy/util-buffer-from": "^4.1.0", -+ "@smithy/util-hex-encoding": "^4.1.0", -+ "@smithy/util-utf8": "^4.1.0", ++ "@smithy/fetch-http-handler": "^5.3.6", ++ "@smithy/node-http-handler": "^4.4.5", ++ "@smithy/types": "^4.9.0", ++ "@smithy/util-base64": "^4.3.0", ++ "@smithy/util-buffer-from": "^4.2.0", ++ "@smithy/util-hex-encoding": "^4.2.0", ++ "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -5055,9 +5181,9 @@ + } + }, + "node_modules/@smithy/util-uri-escape": { -+ "version": "4.1.0", -+ "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.1.0.tgz", -+ "integrity": "sha512-b0EFQkq35K5NHUYxU72JuoheM6+pytEVUGlTwiFxWFpmddA+Bpz3LgsPRIpBk8lnPE47yT7AF2Egc3jVnKLuPg==", ++ "version": "4.2.0", ++ "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", ++ "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" @@ -5067,12 +5193,12 @@ + } + }, + "node_modules/@smithy/util-utf8": { -+ "version": "4.1.0", -+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.1.0.tgz", -+ "integrity": "sha512-mEu1/UIXAdNYuBcyEPbjScKi/+MQVXNIuY/7Cm5XLIWe319kDrT5SizBE95jqtmEXoDbGoZxKLCMttdZdqTZKQ==", ++ "version": "4.2.0", ++ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", ++ "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/util-buffer-from": "^4.1.0", ++ "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -5080,13 +5206,25 @@ + } + }, + "node_modules/@smithy/util-waiter": { -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.1.1.tgz", -+ "integrity": "sha512-PJBmyayrlfxM7nbqjomF4YcT1sApQwZio0NHSsT0EzhJqljRmvhzqZua43TyEs80nJk2Cn2FGPg/N8phH6KeCQ==", ++ "version": "4.2.5", ++ "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.5.tgz", ++ "integrity": "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g==", ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@smithy/abort-controller": "^4.2.5", ++ "@smithy/types": "^4.9.0", ++ "tslib": "^2.6.2" ++ }, ++ "engines": { ++ "node": ">=18.0.0" ++ } ++ }, ++ "node_modules/@smithy/uuid": { ++ "version": "1.1.0", ++ "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", ++ "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "license": "Apache-2.0", + "dependencies": { -+ "@smithy/abort-controller": "^4.1.1", -+ "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { @@ -5112,14 +5250,11 @@ + } + }, + "node_modules/@tailwindcss/typography": { -+ "version": "0.5.16", -+ "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz", -+ "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==", ++ "version": "0.5.19", ++ "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", ++ "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", + "license": "MIT", + "dependencies": { -+ "lodash.castarray": "^4.4.0", -+ "lodash.isplainobject": "^4.0.6", -+ "lodash.merge": "^4.6.2", + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { @@ -5131,18 +5266,16 @@ + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.41.0.tgz", + "integrity": "sha512-193R4Jp9hjvlij6LryxrB5Mpbffd2L9PeWh3KlIy/hJV4SkBOfiQZ+jc5qAZLDCrdbkA5FjGj+UoDYw6TcNnyA==", + "license": "MIT", -+ "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { -+ "version": "4.41.0", -+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.41.0.tgz", -+ "integrity": "sha512-4/euCZAv8zeaB5P/nQiySzB0JHM3tiraU9KjSvSlJAX7oIE9uPDZlHCkDg/bHYNXewzvsg0FtOMq0VUq8XMMOQ==", ++ "version": "4.42.0", ++ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.42.0.tgz", ++ "integrity": "sha512-j0tiofkzE3CSrYKmVRaKuwGgvCE+P2OOEDlhmfjeZf5ufcuFHwYwwgw3j08n4WYPVZ+OpsHblcFYezhKA3jDwg==", + "license": "MIT", -+ "peer": true, + "dependencies": { + "@tanstack/query-core": "4.41.0", + "use-sync-external-store": "^1.2.0" @@ -5152,8 +5285,8 @@ + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { -+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0", -+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", ++ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", ++ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-native": "*" + }, + "peerDependenciesMeta": { @@ -5166,43 +5299,36 @@ + } + }, + "node_modules/@testing-library/dom": { -+ "version": "9.3.4", -+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", -+ "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==", ++ "version": "10.4.1", ++ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", ++ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "license": "MIT", ++ "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", -+ "aria-query": "5.1.3", -+ "chalk": "^4.1.0", ++ "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", ++ "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { -+ "node": ">=14" -+ } -+ }, -+ "node_modules/@testing-library/dom/node_modules/aria-query": { -+ "version": "5.1.3", -+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", -+ "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", -+ "license": "Apache-2.0", -+ "dependencies": { -+ "deep-equal": "^2.0.5" ++ "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", -+ "license": "MIT" ++ "license": "MIT", ++ "peer": true + }, + "node_modules/@testing-library/jest-dom": { -+ "version": "6.8.0", -+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.8.0.tgz", -+ "integrity": "sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ==", ++ "version": "6.9.1", ++ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", ++ "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", @@ -5219,21 +5345,30 @@ + } + }, + "node_modules/@testing-library/react": { -+ "version": "14.3.1", -+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.1.tgz", -+ "integrity": "sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==", ++ "version": "16.3.1", ++ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.1.tgz", ++ "integrity": "sha512-gr4KtAWqIOQoucWYD/f6ki+j5chXfcPc74Col/6poTyqTmn7zRmodWahWRCp8tYd+GMqBonw6hstNzqjbs6gjw==", + "license": "MIT", + "dependencies": { -+ "@babel/runtime": "^7.12.5", -+ "@testing-library/dom": "^9.0.0", -+ "@types/react-dom": "^18.0.0" ++ "@babel/runtime": "^7.12.5" + }, + "engines": { -+ "node": ">=14" ++ "node": ">=18" + }, + "peerDependencies": { -+ "react": "^18.0.0", -+ "react-dom": "^18.0.0" ++ "@testing-library/dom": "^10.0.0", ++ "@types/react": "^18.0.0 || ^19.0.0", ++ "@types/react-dom": "^18.0.0 || ^19.0.0", ++ "react": "^18.0.0 || ^19.0.0", ++ "react-dom": "^18.0.0 || ^19.0.0" ++ }, ++ "peerDependenciesMeta": { ++ "@types/react": { ++ "optional": true ++ }, ++ "@types/react-dom": { ++ "optional": true ++ } + } + }, + "node_modules/@tootallnate/once": { @@ -5245,6 +5380,20 @@ + "node": ">= 10" + } + }, ++ "node_modules/@tsconfig/node22": { ++ "version": "22.0.5", ++ "resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.5.tgz", ++ "integrity": "sha512-hLf2ld+sYN/BtOJjHUWOk568dvjFQkHnLNa6zce25GIH+vxKfvTgm3qpaH6ToF5tu/NN0IH66s+Bb5wElHrLcw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/@tsconfig/vite-react": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@tsconfig/vite-react/-/vite-react-7.0.2.tgz", ++ "integrity": "sha512-lEj4y5SPRcH+bjw0tyuxrEnPqQUwfQzBKgd1YamD9xyet9zLwh2gwy5F8w/Nxg5DjdgYVjjKo5aLJUf0BTDz4w==", ++ "dev": true, ++ "license": "MIT" ++ }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -5259,7 +5408,53 @@ + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", -+ "license": "MIT" ++ "license": "MIT", ++ "peer": true ++ }, ++ "node_modules/@types/babel__core": { ++ "version": "7.20.5", ++ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", ++ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/parser": "^7.20.7", ++ "@babel/types": "^7.20.7", ++ "@types/babel__generator": "*", ++ "@types/babel__template": "*", ++ "@types/babel__traverse": "*" ++ } ++ }, ++ "node_modules/@types/babel__generator": { ++ "version": "7.27.0", ++ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", ++ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/types": "^7.0.0" ++ } ++ }, ++ "node_modules/@types/babel__template": { ++ "version": "7.4.4", ++ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", ++ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/parser": "^7.1.0", ++ "@babel/types": "^7.0.0" ++ } ++ }, ++ "node_modules/@types/babel__traverse": { ++ "version": "7.28.0", ++ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", ++ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/types": "^7.28.2" ++ } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", @@ -5294,6 +5489,16 @@ + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "license": "MIT" + }, ++ "node_modules/@types/cors": { ++ "version": "2.8.19", ++ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", ++ "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@types/node": "*" ++ } ++ }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -5310,21 +5515,21 @@ + "license": "MIT" + }, + "node_modules/@types/express": { -+ "version": "5.0.3", -+ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", -+ "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", ++ "version": "5.0.5", ++ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz", ++ "integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", -+ "@types/serve-static": "*" ++ "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { -+ "version": "5.0.7", -+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", -+ "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", ++ "version": "5.1.0", ++ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", ++ "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", + "dev": true, + "license": "MIT", + "dependencies": { @@ -5367,12 +5572,12 @@ + "license": "MIT" + }, + "node_modules/@types/node": { -+ "version": "24.5.0", -+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.0.tgz", -+ "integrity": "sha512-y1dMvuvJspJiPSDZUQ+WMBvF7dpnEqN4x9DDC9ie5Fs/HUZJA3wFp7EhHoVaKX/iI0cRoECV8X2jL8zi0xrHCg==", ++ "version": "22.19.3", ++ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", ++ "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", + "license": "MIT", + "dependencies": { -+ "undici-types": "~7.12.0" ++ "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-fetch": { @@ -5385,12 +5590,6 @@ + "form-data": "^4.0.4" + } + }, -+ "node_modules/@types/prop-types": { -+ "version": "15.7.15", -+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", -+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", -+ "license": "MIT" -+ }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -5406,22 +5605,23 @@ + "license": "MIT" + }, + "node_modules/@types/react": { -+ "version": "18.3.24", -+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz", -+ "integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==", ++ "version": "19.2.7", ++ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", ++ "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", ++ "devOptional": true, + "license": "MIT", + "dependencies": { -+ "@types/prop-types": "*", -+ "csstype": "^3.0.2" ++ "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { -+ "version": "18.3.7", -+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", -+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", ++ "version": "19.2.3", ++ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", ++ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", ++ "devOptional": true, + "license": "MIT", + "peerDependencies": { -+ "@types/react": "^18.0.0" ++ "@types/react": "^19.2.0" + } + }, + "node_modules/@types/request": { @@ -5453,10 +5653,38 @@ + "node": ">= 0.12" + } + }, ++ "node_modules/@types/request/node_modules/mime-db": { ++ "version": "1.52.0", ++ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", ++ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", ++ "license": "MIT", ++ "engines": { ++ "node": ">= 0.6" ++ } ++ }, ++ "node_modules/@types/request/node_modules/mime-types": { ++ "version": "2.1.35", ++ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", ++ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", ++ "license": "MIT", ++ "dependencies": { ++ "mime-db": "1.52.0" ++ }, ++ "engines": { ++ "node": ">= 0.6" ++ } ++ }, ++ "node_modules/@types/resolve": { ++ "version": "1.20.2", ++ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", ++ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", ++ "dev": true, ++ "license": "MIT" ++ }, + "node_modules/@types/send": { -+ "version": "0.17.5", -+ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", -+ "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", ++ "version": "0.17.6", ++ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", ++ "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { @@ -5465,15 +5693,15 @@ + } + }, + "node_modules/@types/serve-static": { -+ "version": "1.15.8", -+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", -+ "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", ++ "version": "1.15.10", ++ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", ++ "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", -+ "@types/send": "*" ++ "@types/send": "<1" + } + }, + "node_modules/@types/set-cookie-parser": { @@ -5491,11 +5719,26 @@ + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT" + }, -+ "node_modules/@types/uuid": { -+ "version": "9.0.8", -+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", -+ "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", -+ "license": "MIT" ++ "node_modules/@vitejs/plugin-react": { ++ "version": "4.7.0", ++ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", ++ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/core": "^7.28.0", ++ "@babel/plugin-transform-react-jsx-self": "^7.27.1", ++ "@babel/plugin-transform-react-jsx-source": "^7.27.1", ++ "@rolldown/pluginutils": "1.0.0-beta.27", ++ "@types/babel__core": "^7.20.5", ++ "react-refresh": "^0.17.0" ++ }, ++ "engines": { ++ "node": "^14.18.0 || >=16.0.0" ++ }, ++ "peerDependencies": { ++ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" ++ } + }, + "node_modules/@vitest/expect": { + "version": "1.6.1", @@ -5525,6 +5768,12 @@ + "url": "https://opencollective.com/vitest" + } + }, ++ "node_modules/@vitest/runner/node_modules/pathe": { ++ "version": "1.1.2", ++ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", ++ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", ++ "license": "MIT" ++ }, + "node_modules/@vitest/snapshot": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", @@ -5551,6 +5800,12 @@ + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, ++ "node_modules/@vitest/snapshot/node_modules/pathe": { ++ "version": "1.1.2", ++ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", ++ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", ++ "license": "MIT" ++ }, + "node_modules/@vitest/snapshot/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -5604,6 +5859,12 @@ + "vitest": "1.6.1" + } + }, ++ "node_modules/@vitest/ui/node_modules/pathe": { ++ "version": "1.1.2", ++ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", ++ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", ++ "license": "MIT" ++ }, + "node_modules/@vitest/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", @@ -5631,6 +5892,15 @@ + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, ++ "node_modules/@vitest/utils/node_modules/estree-walker": { ++ "version": "3.0.3", ++ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", ++ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", ++ "license": "MIT", ++ "dependencies": { ++ "@types/estree": "^1.0.0" ++ } ++ }, + "node_modules/@vitest/utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -5651,6 +5921,22 @@ + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, ++ "node_modules/@wasp.sh/generated-server-build": { ++ "resolved": ".wasp/build/server", ++ "link": true ++ }, ++ "node_modules/@wasp.sh/generated-server-dev": { ++ "resolved": ".wasp/out/server", ++ "link": true ++ }, ++ "node_modules/@wasp.sh/generated-webapp-build": { ++ "resolved": ".wasp/build/web-app", ++ "link": true ++ }, ++ "node_modules/@wasp.sh/generated-webapp-dev": { ++ "resolved": ".wasp/out/web-app", ++ "link": true ++ }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", @@ -5699,27 +5985,6 @@ + "node": ">= 0.6" + } + }, -+ "node_modules/accepts/node_modules/mime-db": { -+ "version": "1.54.0", -+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", -+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", -+ "license": "MIT", -+ "engines": { -+ "node": ">= 0.6" -+ } -+ }, -+ "node_modules/accepts/node_modules/mime-types": { -+ "version": "3.0.1", -+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", -+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", -+ "license": "MIT", -+ "dependencies": { -+ "mime-db": "^1.54.0" -+ }, -+ "engines": { -+ "node": ">= 0.6" -+ } -+ }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -5748,430 +6013,149 @@ + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "license": "MIT", + "dependencies": { -+ "acorn": "^8.11.0" -+ }, -+ "engines": { -+ "node": ">=0.4.0" -+ } -+ }, -+ "node_modules/agent-base": { -+ "version": "7.1.4", -+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", -+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", -+ "license": "MIT", -+ "engines": { -+ "node": ">= 14" -+ } -+ }, -+ "node_modules/agentkeepalive": { -+ "version": "4.6.0", -+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", -+ "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", -+ "license": "MIT", -+ "dependencies": { -+ "humanize-ms": "^1.2.1" -+ }, -+ "engines": { -+ "node": ">= 8.0.0" -+ } -+ }, -+ "node_modules/aggregate-error": { -+ "version": "3.1.0", -+ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", -+ "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", -+ "license": "MIT", -+ "dependencies": { -+ "clean-stack": "^2.0.0", -+ "indent-string": "^4.0.0" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/ansi-escapes": { -+ "version": "4.3.2", -+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", -+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", -+ "license": "MIT", -+ "dependencies": { -+ "type-fest": "^0.21.3" -+ }, -+ "engines": { -+ "node": ">=8" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/sindresorhus" -+ } -+ }, -+ "node_modules/ansi-escapes/node_modules/type-fest": { -+ "version": "0.21.3", -+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", -+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", -+ "license": "(MIT OR CC0-1.0)", -+ "engines": { -+ "node": ">=10" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/sindresorhus" -+ } -+ }, -+ "node_modules/ansi-regex": { -+ "version": "6.2.2", -+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", -+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", -+ "license": "MIT", -+ "engines": { -+ "node": ">=12" -+ }, -+ "funding": { -+ "url": "https://github.com/chalk/ansi-regex?sponsor=1" -+ } -+ }, -+ "node_modules/ansi-styles": { -+ "version": "6.2.3", -+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", -+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", -+ "license": "MIT", -+ "engines": { -+ "node": ">=12" -+ }, -+ "funding": { -+ "url": "https://github.com/chalk/ansi-styles?sponsor=1" -+ } -+ }, -+ "node_modules/any-promise": { -+ "version": "1.3.0", -+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", -+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", -+ "license": "MIT" -+ }, -+ "node_modules/anymatch": { -+ "version": "3.1.3", -+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", -+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", -+ "license": "ISC", -+ "dependencies": { -+ "normalize-path": "^3.0.0", -+ "picomatch": "^2.0.4" -+ }, -+ "engines": { -+ "node": ">= 8" -+ } -+ }, -+ "node_modules/apexcharts": { -+ "version": "3.41.0", -+ "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.41.0.tgz", -+ "integrity": "sha512-FJXA7NVjxs1q+ptR3b1I+pN8K/gWuXn+qLZjFz8EHvJOokdgcuwa/HSe5aC465HW/LWnrjWLSTsOQejQbQ42hQ==", -+ "license": "MIT", -+ "dependencies": { -+ "svg.draggable.js": "^2.2.2", -+ "svg.easing.js": "^2.0.0", -+ "svg.filter.js": "^2.0.2", -+ "svg.pathmorphing.js": "^0.1.3", -+ "svg.resize.js": "^1.4.3", -+ "svg.select.js": "^3.0.1" -+ } -+ }, -+ "node_modules/arctic": { -+ "version": "1.9.2", -+ "resolved": "https://registry.npmjs.org/arctic/-/arctic-1.9.2.tgz", -+ "integrity": "sha512-VTnGpYx+ypboJdNrWnK17WeD7zN/xSCHnpecd5QYsBfVZde/5i+7DJ1wrf/ioSDMiEjagXmyNWAE3V2C9f1hNg==", -+ "license": "MIT", -+ "dependencies": { -+ "oslo": "1.2.0" -+ } -+ }, -+ "node_modules/arctic/node_modules/@emnapi/core": { -+ "version": "0.45.0", -+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-0.45.0.tgz", -+ "integrity": "sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==", -+ "license": "MIT", -+ "optional": true, -+ "dependencies": { -+ "tslib": "^2.4.0" -+ } -+ }, -+ "node_modules/arctic/node_modules/@emnapi/runtime": { -+ "version": "0.45.0", -+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.45.0.tgz", -+ "integrity": "sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==", -+ "license": "MIT", -+ "optional": true, -+ "dependencies": { -+ "tslib": "^2.4.0" -+ } -+ }, -+ "node_modules/arctic/node_modules/@node-rs/argon2": { -+ "version": "1.7.0", -+ "resolved": "https://registry.npmjs.org/@node-rs/argon2/-/argon2-1.7.0.tgz", -+ "integrity": "sha512-zfULc+/tmcWcxn+nHkbyY8vP3+MpEqKORbszt4UkpqZgBgDAAIYvuDN/zukfTgdmo6tmJKKVfzigZOPk4LlIog==", -+ "license": "MIT", -+ "engines": { -+ "node": ">= 10" -+ }, -+ "optionalDependencies": { -+ "@node-rs/argon2-android-arm-eabi": "1.7.0", -+ "@node-rs/argon2-android-arm64": "1.7.0", -+ "@node-rs/argon2-darwin-arm64": "1.7.0", -+ "@node-rs/argon2-darwin-x64": "1.7.0", -+ "@node-rs/argon2-freebsd-x64": "1.7.0", -+ "@node-rs/argon2-linux-arm-gnueabihf": "1.7.0", -+ "@node-rs/argon2-linux-arm64-gnu": "1.7.0", -+ "@node-rs/argon2-linux-arm64-musl": "1.7.0", -+ "@node-rs/argon2-linux-x64-gnu": "1.7.0", -+ "@node-rs/argon2-linux-x64-musl": "1.7.0", -+ "@node-rs/argon2-wasm32-wasi": "1.7.0", -+ "@node-rs/argon2-win32-arm64-msvc": "1.7.0", -+ "@node-rs/argon2-win32-ia32-msvc": "1.7.0", -+ "@node-rs/argon2-win32-x64-msvc": "1.7.0" -+ } -+ }, -+ "node_modules/arctic/node_modules/@node-rs/argon2-android-arm-eabi": { -+ "version": "1.7.0", -+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm-eabi/-/argon2-android-arm-eabi-1.7.0.tgz", -+ "integrity": "sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==", -+ "cpu": [ -+ "arm" -+ ], -+ "license": "MIT", -+ "optional": true, -+ "os": [ -+ "android" -+ ], -+ "engines": { -+ "node": ">= 10" -+ } -+ }, -+ "node_modules/arctic/node_modules/@node-rs/argon2-android-arm64": { -+ "version": "1.7.0", -+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm64/-/argon2-android-arm64-1.7.0.tgz", -+ "integrity": "sha512-s9j/G30xKUx8WU50WIhF0fIl1EdhBGq0RQ06lEhZ0Gi0ap8lhqbE2Bn5h3/G2D1k0Dx+yjeVVNmt/xOQIRG38A==", -+ "cpu": [ -+ "arm64" -+ ], -+ "license": "MIT", -+ "optional": true, -+ "os": [ -+ "android" -+ ], -+ "engines": { -+ "node": ">= 10" -+ } -+ }, -+ "node_modules/arctic/node_modules/@node-rs/argon2-darwin-arm64": { -+ "version": "1.7.0", -+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-arm64/-/argon2-darwin-arm64-1.7.0.tgz", -+ "integrity": "sha512-ZIz4L6HGOB9U1kW23g+m7anGNuTZ0RuTw0vNp3o+2DWpb8u8rODq6A8tH4JRL79S+Co/Nq608m9uackN2pe0Rw==", -+ "cpu": [ -+ "arm64" -+ ], -+ "license": "MIT", -+ "optional": true, -+ "os": [ -+ "darwin" -+ ], -+ "engines": { -+ "node": ">= 10" -+ } -+ }, -+ "node_modules/arctic/node_modules/@node-rs/argon2-darwin-x64": { -+ "version": "1.7.0", -+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-x64/-/argon2-darwin-x64-1.7.0.tgz", -+ "integrity": "sha512-5oi/pxqVhODW/pj1+3zElMTn/YukQeywPHHYDbcAW3KsojFjKySfhcJMd1DjKTc+CHQI+4lOxZzSUzK7mI14Hw==", -+ "cpu": [ -+ "x64" -+ ], -+ "license": "MIT", -+ "optional": true, -+ "os": [ -+ "darwin" -+ ], -+ "engines": { -+ "node": ">= 10" -+ } -+ }, -+ "node_modules/arctic/node_modules/@node-rs/argon2-freebsd-x64": { -+ "version": "1.7.0", -+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-freebsd-x64/-/argon2-freebsd-x64-1.7.0.tgz", -+ "integrity": "sha512-Ify08683hA4QVXYoIm5SUWOY5DPIT/CMB0CQT+IdxQAg/F+qp342+lUkeAtD5bvStQuCx/dFO3bnnzoe2clMhA==", -+ "cpu": [ -+ "x64" -+ ], -+ "license": "MIT", -+ "optional": true, -+ "os": [ -+ "freebsd" -+ ], ++ "acorn": "^8.11.0" ++ }, + "engines": { -+ "node": ">= 10" ++ "node": ">=0.4.0" + } + }, -+ "node_modules/arctic/node_modules/@node-rs/argon2-linux-arm-gnueabihf": { -+ "version": "1.7.0", -+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm-gnueabihf/-/argon2-linux-arm-gnueabihf-1.7.0.tgz", -+ "integrity": "sha512-7DjDZ1h5AUHAtRNjD19RnQatbhL+uuxBASuuXIBu4/w6Dx8n7YPxwTP4MXfsvuRgKuMWiOb/Ub/HJ3kXVCXRkg==", -+ "cpu": [ -+ "arm" -+ ], ++ "node_modules/agent-base": { ++ "version": "7.1.4", ++ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", ++ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", -+ "optional": true, -+ "os": [ -+ "linux" -+ ], + "engines": { -+ "node": ">= 10" ++ "node": ">= 14" + } + }, -+ "node_modules/arctic/node_modules/@node-rs/argon2-linux-arm64-gnu": { -+ "version": "1.7.0", -+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-gnu/-/argon2-linux-arm64-gnu-1.7.0.tgz", -+ "integrity": "sha512-nJDoMP4Y3YcqGswE4DvP080w6O24RmnFEDnL0emdI8Nou17kNYBzP2546Nasx9GCyLzRcYQwZOUjrtUuQ+od2g==", -+ "cpu": [ -+ "arm64" -+ ], ++ "node_modules/agentkeepalive": { ++ "version": "4.6.0", ++ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", ++ "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", -+ "optional": true, -+ "os": [ -+ "linux" -+ ], ++ "dependencies": { ++ "humanize-ms": "^1.2.1" ++ }, + "engines": { -+ "node": ">= 10" ++ "node": ">= 8.0.0" + } + }, -+ "node_modules/arctic/node_modules/@node-rs/argon2-linux-arm64-musl": { -+ "version": "1.7.0", -+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-musl/-/argon2-linux-arm64-musl-1.7.0.tgz", -+ "integrity": "sha512-BKWS8iVconhE3jrb9mj6t1J9vwUqQPpzCbUKxfTGJfc+kNL58F1SXHBoe2cDYGnHrFEHTY0YochzXoAfm4Dm/A==", -+ "cpu": [ -+ "arm64" -+ ], ++ "node_modules/aggregate-error": { ++ "version": "3.1.0", ++ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", ++ "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", -+ "optional": true, -+ "os": [ -+ "linux" -+ ], ++ "dependencies": { ++ "clean-stack": "^2.0.0", ++ "indent-string": "^4.0.0" ++ }, + "engines": { -+ "node": ">= 10" ++ "node": ">=8" + } + }, -+ "node_modules/arctic/node_modules/@node-rs/argon2-linux-x64-gnu": { -+ "version": "1.7.0", -+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-gnu/-/argon2-linux-x64-gnu-1.7.0.tgz", -+ "integrity": "sha512-EmgqZOlf4Jurk/szW1iTsVISx25bKksVC5uttJDUloTgsAgIGReCpUUO1R24pBhu9ESJa47iv8NSf3yAfGv6jQ==", -+ "cpu": [ -+ "x64" -+ ], ++ "node_modules/ansi-escapes": { ++ "version": "4.3.2", ++ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", ++ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", -+ "optional": true, -+ "os": [ -+ "linux" -+ ], ++ "dependencies": { ++ "type-fest": "^0.21.3" ++ }, + "engines": { -+ "node": ">= 10" ++ "node": ">=8" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/sindresorhus" + } + }, -+ "node_modules/arctic/node_modules/@node-rs/argon2-linux-x64-musl": { -+ "version": "1.7.0", -+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-musl/-/argon2-linux-x64-musl-1.7.0.tgz", -+ "integrity": "sha512-/o1efYCYIxjfuoRYyBTi2Iy+1iFfhqHCvvVsnjNSgO1xWiWrX0Rrt/xXW5Zsl7vS2Y+yu8PL8KFWRzZhaVxfKA==", -+ "cpu": [ -+ "x64" -+ ], -+ "license": "MIT", -+ "optional": true, -+ "os": [ -+ "linux" -+ ], ++ "node_modules/ansi-escapes/node_modules/type-fest": { ++ "version": "0.21.3", ++ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", ++ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", ++ "license": "(MIT OR CC0-1.0)", + "engines": { -+ "node": ">= 10" ++ "node": ">=10" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/sindresorhus" + } + }, -+ "node_modules/arctic/node_modules/@node-rs/argon2-wasm32-wasi": { -+ "version": "1.7.0", -+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-wasm32-wasi/-/argon2-wasm32-wasi-1.7.0.tgz", -+ "integrity": "sha512-Evmk9VcxqnuwQftfAfYEr6YZYSPLzmKUsbFIMep5nTt9PT4XYRFAERj7wNYp+rOcBenF3X4xoB+LhwcOMTNE5w==", -+ "cpu": [ -+ "wasm32" -+ ], ++ "node_modules/ansi-regex": { ++ "version": "5.0.1", ++ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", ++ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", -+ "optional": true, -+ "dependencies": { -+ "@emnapi/core": "^0.45.0", -+ "@emnapi/runtime": "^0.45.0", -+ "@tybys/wasm-util": "^0.8.1", -+ "memfs-browser": "^3.4.13000" -+ }, + "engines": { -+ "node": ">=14.0.0" ++ "node": ">=8" + } + }, -+ "node_modules/arctic/node_modules/@node-rs/argon2-win32-arm64-msvc": { -+ "version": "1.7.0", -+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-arm64-msvc/-/argon2-win32-arm64-msvc-1.7.0.tgz", -+ "integrity": "sha512-qgsU7T004COWWpSA0tppDqDxbPLgg8FaU09krIJ7FBl71Sz8SFO40h7fDIjfbTT5w7u6mcaINMQ5bSHu75PCaA==", -+ "cpu": [ -+ "arm64" -+ ], ++ "node_modules/ansi-styles": { ++ "version": "4.3.0", ++ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", ++ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", -+ "optional": true, -+ "os": [ -+ "win32" -+ ], ++ "dependencies": { ++ "color-convert": "^2.0.1" ++ }, + "engines": { -+ "node": ">= 10" ++ "node": ">=8" ++ }, ++ "funding": { ++ "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, -+ "node_modules/arctic/node_modules/@node-rs/argon2-win32-ia32-msvc": { -+ "version": "1.7.0", -+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-ia32-msvc/-/argon2-win32-ia32-msvc-1.7.0.tgz", -+ "integrity": "sha512-JGafwWYQ/HpZ3XSwP4adQ6W41pRvhcdXvpzIWtKvX+17+xEXAe2nmGWM6s27pVkg1iV2ZtoYLRDkOUoGqZkCcg==", -+ "cpu": [ -+ "ia32" -+ ], -+ "license": "MIT", -+ "optional": true, -+ "os": [ -+ "win32" -+ ], ++ "node_modules/any-promise": { ++ "version": "1.3.0", ++ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", ++ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", ++ "license": "MIT" ++ }, ++ "node_modules/anymatch": { ++ "version": "3.1.3", ++ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", ++ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", ++ "license": "ISC", ++ "dependencies": { ++ "normalize-path": "^3.0.0", ++ "picomatch": "^2.0.4" ++ }, + "engines": { -+ "node": ">= 10" ++ "node": ">= 8" + } + }, -+ "node_modules/arctic/node_modules/@node-rs/argon2-win32-x64-msvc": { -+ "version": "1.7.0", -+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-x64-msvc/-/argon2-win32-x64-msvc-1.7.0.tgz", -+ "integrity": "sha512-9oq4ShyFakw8AG3mRls0AoCpxBFcimYx7+jvXeAf2OqKNO+mSA6eZ9z7KQeVCi0+SOEUYxMGf5UiGiDb9R6+9Q==", -+ "cpu": [ -+ "x64" -+ ], ++ "node_modules/anymatch/node_modules/picomatch": { ++ "version": "2.3.1", ++ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", ++ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", -+ "optional": true, -+ "os": [ -+ "win32" -+ ], + "engines": { -+ "node": ">= 10" ++ "node": ">=8.6" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/jonschlinkert" + } + }, -+ "node_modules/arctic/node_modules/@tybys/wasm-util": { -+ "version": "0.8.3", -+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.8.3.tgz", -+ "integrity": "sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q==", ++ "node_modules/apexcharts": { ++ "version": "3.41.0", ++ "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.41.0.tgz", ++ "integrity": "sha512-FJXA7NVjxs1q+ptR3b1I+pN8K/gWuXn+qLZjFz8EHvJOokdgcuwa/HSe5aC465HW/LWnrjWLSTsOQejQbQ42hQ==", + "license": "MIT", -+ "optional": true, + "dependencies": { -+ "tslib": "^2.4.0" ++ "svg.draggable.js": "^2.2.2", ++ "svg.easing.js": "^2.0.0", ++ "svg.filter.js": "^2.0.2", ++ "svg.pathmorphing.js": "^0.1.3", ++ "svg.resize.js": "^1.4.3", ++ "svg.select.js": "^3.0.1" + } + }, -+ "node_modules/arctic/node_modules/oslo": { -+ "version": "1.2.0", -+ "resolved": "https://registry.npmjs.org/oslo/-/oslo-1.2.0.tgz", -+ "integrity": "sha512-OoFX6rDsNcOQVAD2gQD/z03u4vEjWZLzJtwkmgfRF+KpQUXwdgEXErD7zNhyowmHwHefP+PM9Pw13pgpHMRlzw==", -+ "deprecated": "Package is no longer supported. Please see https://oslojs.dev for the successor project.", ++ "node_modules/arctic": { ++ "version": "1.9.2", ++ "resolved": "https://registry.npmjs.org/arctic/-/arctic-1.9.2.tgz", ++ "integrity": "sha512-VTnGpYx+ypboJdNrWnK17WeD7zN/xSCHnpecd5QYsBfVZde/5i+7DJ1wrf/ioSDMiEjagXmyNWAE3V2C9f1hNg==", + "license": "MIT", + "dependencies": { -+ "@node-rs/argon2": "1.7.0", -+ "@node-rs/bcrypt": "1.9.0" ++ "oslo": "1.2.0" + } + }, + "node_modules/arg": { @@ -6193,28 +6177,12 @@ + } + }, + "node_modules/aria-query": { -+ "version": "5.3.2", -+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", -+ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", ++ "version": "5.3.0", ++ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", ++ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "license": "Apache-2.0", -+ "engines": { -+ "node": ">= 0.4" -+ } -+ }, -+ "node_modules/array-buffer-byte-length": { -+ "version": "1.0.2", -+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", -+ "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", -+ "license": "MIT", + "dependencies": { -+ "call-bound": "^1.0.3", -+ "is-array-buffer": "^3.0.5" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" ++ "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { @@ -6233,9 +6201,9 @@ + "license": "MIT" + }, + "node_modules/autoprefixer": { -+ "version": "10.4.21", -+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", -+ "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", ++ "version": "10.4.22", ++ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", ++ "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", + "funding": [ + { + "type": "opencollective", @@ -6252,9 +6220,9 @@ + ], + "license": "MIT", + "dependencies": { -+ "browserslist": "^4.24.4", -+ "caniuse-lite": "^1.0.30001702", -+ "fraction.js": "^4.3.7", ++ "browserslist": "^4.27.0", ++ "caniuse-lite": "^1.0.30001754", ++ "fraction.js": "^5.3.4", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" @@ -6285,9 +6253,9 @@ + } + }, + "node_modules/axios": { -+ "version": "1.12.2", -+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", -+ "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", ++ "version": "1.13.2", ++ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", ++ "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", @@ -6299,6 +6267,7 @@ + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", ++ "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { @@ -6322,14 +6291,32 @@ + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { -+ "version": "2.8.4", -+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.4.tgz", -+ "integrity": "sha512-L+YvJwGAgwJBV1p6ffpSTa2KRc69EeeYGYjRVWKs0GKrK+LON0GC0gV+rKSNtALEDvMDqkvCFq9r1r94/Gjwxw==", ++ "version": "2.8.31", ++ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz", ++ "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, ++ "node_modules/basic-auth": { ++ "version": "2.0.1", ++ "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", ++ "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", ++ "license": "MIT", ++ "dependencies": { ++ "safe-buffer": "5.1.2" ++ }, ++ "engines": { ++ "node": ">= 0.8" ++ } ++ }, ++ "node_modules/basic-auth/node_modules/safe-buffer": { ++ "version": "5.1.2", ++ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", ++ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", ++ "license": "MIT" ++ }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", @@ -6363,38 +6350,44 @@ + } + }, + "node_modules/body-parser": { -+ "version": "2.2.0", -+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", -+ "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", ++ "version": "2.2.1", ++ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", ++ "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", -+ "debug": "^4.4.0", ++ "debug": "^4.4.3", + "http-errors": "^2.0.0", -+ "iconv-lite": "^0.6.3", ++ "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.0", -+ "raw-body": "^3.0.0", -+ "type-is": "^2.0.0" ++ "raw-body": "^3.0.1", ++ "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/express" + } + }, + "node_modules/bowser": { -+ "version": "2.12.1", -+ "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz", -+ "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==", ++ "version": "2.13.0", ++ "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.0.tgz", ++ "integrity": "sha512-yHAbSRuT6LTeKi6k2aS40csueHqgAsFEgmrOsfRyFpJnFv5O2hl9FYmWEUZ97gZ/dG17U4IQQcTx4YAFYPuWRQ==", + "license": "MIT" + }, + "node_modules/brace-expansion": { -+ "version": "2.0.2", -+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", -+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", ++ "version": "1.1.12", ++ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", ++ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", ++ "dev": true, + "license": "MIT", + "dependencies": { -+ "balanced-match": "^1.0.0" ++ "balanced-match": "^1.0.0", ++ "concat-map": "0.0.1" + } + }, + "node_modules/braces": { @@ -6410,9 +6403,9 @@ + } + }, + "node_modules/browserslist": { -+ "version": "4.26.0", -+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.0.tgz", -+ "integrity": "sha512-P9go2WrP9FiPwLv3zqRD/Uoxo0RSHjzFCiQz7d4vbmwNqQFo9T9WCeP/Qn5EbcKQY6DBbkxEXNcpJOmncNrb7A==", ++ "version": "4.28.0", ++ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", ++ "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "funding": [ + { + "type": "opencollective", @@ -6429,11 +6422,11 @@ + ], + "license": "MIT", + "dependencies": { -+ "baseline-browser-mapping": "^2.8.2", -+ "caniuse-lite": "^1.0.30001741", -+ "electron-to-chromium": "^1.5.218", -+ "node-releases": "^2.0.21", -+ "update-browserslist-db": "^1.1.3" ++ "baseline-browser-mapping": "^2.8.25", ++ "caniuse-lite": "^1.0.30001754", ++ "electron-to-chromium": "^1.5.249", ++ "node-releases": "^2.0.27", ++ "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" @@ -6547,9 +6540,9 @@ + } + }, + "node_modules/caniuse-lite": { -+ "version": "1.0.30001741", -+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", -+ "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==", ++ "version": "1.0.30001757", ++ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", ++ "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", + "funding": [ + { + "type": "opencollective", @@ -6600,25 +6593,31 @@ + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, -+ "node_modules/chalk/node_modules/ansi-styles": { -+ "version": "4.3.0", -+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", -+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", ++ "node_modules/chalk/node_modules/has-flag": { ++ "version": "4.0.0", ++ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", ++ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", ++ "license": "MIT", ++ "engines": { ++ "node": ">=8" ++ } ++ }, ++ "node_modules/chalk/node_modules/supports-color": { ++ "version": "7.2.0", ++ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", ++ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { -+ "color-convert": "^2.0.1" ++ "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" -+ }, -+ "funding": { -+ "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chardet": { -+ "version": "2.1.0", -+ "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz", -+ "integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==", ++ "version": "2.1.1", ++ "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", ++ "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "license": "MIT" + }, + "node_modules/check-error": { @@ -6657,18 +6656,6 @@ + "fsevents": "~2.3.2" + } + }, -+ "node_modules/chokidar/node_modules/glob-parent": { -+ "version": "5.1.2", -+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", -+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", -+ "license": "ISC", -+ "dependencies": { -+ "is-glob": "^4.0.1" -+ }, -+ "engines": { -+ "node": ">= 6" -+ } -+ }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -6723,12 +6710,6 @@ + "node": ">= 10" + } + }, -+ "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/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -6743,79 +6724,6 @@ + "node": ">=12" + } + }, -+ "node_modules/cliui/node_modules/ansi-regex": { -+ "version": "5.0.1", -+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", -+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", -+ "license": "MIT", -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/cliui/node_modules/ansi-styles": { -+ "version": "4.3.0", -+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", -+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", -+ "license": "MIT", -+ "dependencies": { -+ "color-convert": "^2.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ }, -+ "funding": { -+ "url": "https://github.com/chalk/ansi-styles?sponsor=1" -+ } -+ }, -+ "node_modules/cliui/node_modules/emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "license": "MIT" -+ }, -+ "node_modules/cliui/node_modules/string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "license": "MIT", -+ "dependencies": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/cliui/node_modules/strip-ansi": { -+ "version": "6.0.1", -+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", -+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", -+ "license": "MIT", -+ "dependencies": { -+ "ansi-regex": "^5.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/cliui/node_modules/wrap-ansi": { -+ "version": "7.0.0", -+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", -+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", -+ "license": "MIT", -+ "dependencies": { -+ "ansi-styles": "^4.0.0", -+ "string-width": "^4.1.0", -+ "strip-ansi": "^6.0.0" -+ }, -+ "engines": { -+ "node": ">=10" -+ }, -+ "funding": { -+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" -+ } -+ }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", @@ -6873,6 +6781,13 @@ + "node": ">= 6" + } + }, ++ "node_modules/concat-map": { ++ "version": "0.0.1", ++ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", ++ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", ++ "dev": true, ++ "license": "MIT" ++ }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", @@ -6880,15 +6795,16 @@ + "license": "MIT" + }, + "node_modules/content-disposition": { -+ "version": "1.0.0", -+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", -+ "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", ++ "version": "1.0.1", ++ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", ++ "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", -+ "dependencies": { -+ "safe-buffer": "5.2.1" -+ }, + "engines": { -+ "node": ">= 0.6" ++ "node": ">=18" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { @@ -6900,6 +6816,13 @@ + "node": ">= 0.6" + } + }, ++ "node_modules/convert-source-map": { ++ "version": "2.0.0", ++ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", ++ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", ++ "dev": true, ++ "license": "MIT" ++ }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -6909,30 +6832,53 @@ + "node": ">= 0.6" + } + }, -+ "node_modules/cookie-signature": { -+ "version": "1.2.2", -+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", -+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", ++ "node_modules/cookie-parser": { ++ "version": "1.4.7", ++ "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", ++ "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", ++ "dependencies": { ++ "cookie": "0.7.2", ++ "cookie-signature": "1.0.6" ++ }, + "engines": { -+ "node": ">=6.6.0" ++ "node": ">= 0.8.0" + } + }, ++ "node_modules/cookie-signature": { ++ "version": "1.0.6", ++ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", ++ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", ++ "license": "MIT" ++ }, + "node_modules/copy-anything": { -+ "version": "3.0.5", -+ "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", -+ "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", ++ "version": "4.0.5", ++ "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", ++ "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { -+ "is-what": "^4.1.8" ++ "is-what": "^5.2.0" + }, + "engines": { -+ "node": ">=12.13" ++ "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, ++ "node_modules/cors": { ++ "version": "2.8.5", ++ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", ++ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", ++ "license": "MIT", ++ "dependencies": { ++ "object-assign": "^4", ++ "vary": "^1" ++ }, ++ "engines": { ++ "node": ">= 0.10" ++ } ++ }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", @@ -6990,20 +6936,12 @@ + } + }, + "node_modules/csstype": { -+ "version": "3.1.3", -+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", -+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", ++ "version": "3.2.3", ++ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", ++ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", ++ "devOptional": true, + "license": "MIT" + }, -+ "node_modules/data-uri-to-buffer": { -+ "version": "4.0.1", -+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", -+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", -+ "license": "MIT", -+ "engines": { -+ "node": ">= 12" -+ } -+ }, + "node_modules/data-urls": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", @@ -7087,38 +7025,6 @@ + "node": ">=6" + } + }, -+ "node_modules/deep-equal": { -+ "version": "2.2.3", -+ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", -+ "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", -+ "license": "MIT", -+ "dependencies": { -+ "array-buffer-byte-length": "^1.0.0", -+ "call-bind": "^1.0.5", -+ "es-get-iterator": "^1.1.3", -+ "get-intrinsic": "^1.2.2", -+ "is-arguments": "^1.1.1", -+ "is-array-buffer": "^3.0.2", -+ "is-date-object": "^1.0.5", -+ "is-regex": "^1.1.4", -+ "is-shared-array-buffer": "^1.0.2", -+ "isarray": "^2.0.5", -+ "object-is": "^1.1.5", -+ "object-keys": "^1.1.1", -+ "object.assign": "^4.1.4", -+ "regexp.prototype.flags": "^1.5.1", -+ "side-channel": "^1.0.4", -+ "which-boxed-primitive": "^1.0.2", -+ "which-collection": "^1.0.1", -+ "which-typed-array": "^1.1.13" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -7157,23 +7063,6 @@ + "url": "https://github.com/sponsors/ljharb" + } + }, -+ "node_modules/define-properties": { -+ "version": "1.2.1", -+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", -+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", -+ "license": "MIT", -+ "dependencies": { -+ "define-data-property": "^1.0.1", -+ "has-property-descriptors": "^1.0.0", -+ "object-keys": "^1.1.1" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, + "node_modules/delay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", @@ -7204,6 +7093,15 @@ + "node": ">= 0.8" + } + }, ++ "node_modules/dequal": { ++ "version": "2.0.3", ++ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", ++ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", ++ "license": "MIT", ++ "engines": { ++ "node": ">=6" ++ } ++ }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -7259,6 +7157,18 @@ + "node": ">=12" + } + }, ++ "node_modules/dotenv": { ++ "version": "16.6.1", ++ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", ++ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", ++ "license": "BSD-2-Clause", ++ "engines": { ++ "node": ">=12" ++ }, ++ "funding": { ++ "url": "https://dotenvx.com" ++ } ++ }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -7285,12 +7195,6 @@ + "stream-shift": "^1.0.2" + } + }, -+ "node_modules/eastasianwidth": { -+ "version": "0.2.0", -+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", -+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", -+ "license": "MIT" -+ }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -7307,15 +7211,15 @@ + "license": "MIT" + }, + "node_modules/electron-to-chromium": { -+ "version": "1.5.218", -+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.218.tgz", -+ "integrity": "sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==", ++ "version": "1.5.262", ++ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz", ++ "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==", + "license": "ISC" + }, + "node_modules/emoji-regex": { -+ "version": "9.2.2", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", -+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", ++ "version": "8.0.0", ++ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", ++ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { @@ -7366,25 +7270,12 @@ + "node": ">= 0.4" + } + }, -+ "node_modules/es-get-iterator": { -+ "version": "1.1.3", -+ "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", -+ "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", -+ "license": "MIT", -+ "dependencies": { -+ "call-bind": "^1.0.2", -+ "get-intrinsic": "^1.1.3", -+ "has-symbols": "^1.0.3", -+ "is-arguments": "^1.1.1", -+ "is-map": "^2.0.2", -+ "is-set": "^2.0.2", -+ "is-string": "^1.0.7", -+ "isarray": "^2.0.5", -+ "stop-iteration-iterator": "^1.0.0" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } ++ "node_modules/es-module-lexer": { ++ "version": "1.7.0", ++ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", ++ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", ++ "dev": true, ++ "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", @@ -7414,9 +7305,9 @@ + } + }, + "node_modules/esbuild": { -+ "version": "0.25.9", -+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", -+ "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", ++ "version": "0.27.2", ++ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", ++ "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", @@ -7427,32 +7318,32 @@ + "node": ">=18" + }, + "optionalDependencies": { -+ "@esbuild/aix-ppc64": "0.25.9", -+ "@esbuild/android-arm": "0.25.9", -+ "@esbuild/android-arm64": "0.25.9", -+ "@esbuild/android-x64": "0.25.9", -+ "@esbuild/darwin-arm64": "0.25.9", -+ "@esbuild/darwin-x64": "0.25.9", -+ "@esbuild/freebsd-arm64": "0.25.9", -+ "@esbuild/freebsd-x64": "0.25.9", -+ "@esbuild/linux-arm": "0.25.9", -+ "@esbuild/linux-arm64": "0.25.9", -+ "@esbuild/linux-ia32": "0.25.9", -+ "@esbuild/linux-loong64": "0.25.9", -+ "@esbuild/linux-mips64el": "0.25.9", -+ "@esbuild/linux-ppc64": "0.25.9", -+ "@esbuild/linux-riscv64": "0.25.9", -+ "@esbuild/linux-s390x": "0.25.9", -+ "@esbuild/linux-x64": "0.25.9", -+ "@esbuild/netbsd-arm64": "0.25.9", -+ "@esbuild/netbsd-x64": "0.25.9", -+ "@esbuild/openbsd-arm64": "0.25.9", -+ "@esbuild/openbsd-x64": "0.25.9", -+ "@esbuild/openharmony-arm64": "0.25.9", -+ "@esbuild/sunos-x64": "0.25.9", -+ "@esbuild/win32-arm64": "0.25.9", -+ "@esbuild/win32-ia32": "0.25.9", -+ "@esbuild/win32-x64": "0.25.9" ++ "@esbuild/aix-ppc64": "0.27.2", ++ "@esbuild/android-arm": "0.27.2", ++ "@esbuild/android-arm64": "0.27.2", ++ "@esbuild/android-x64": "0.27.2", ++ "@esbuild/darwin-arm64": "0.27.2", ++ "@esbuild/darwin-x64": "0.27.2", ++ "@esbuild/freebsd-arm64": "0.27.2", ++ "@esbuild/freebsd-x64": "0.27.2", ++ "@esbuild/linux-arm": "0.27.2", ++ "@esbuild/linux-arm64": "0.27.2", ++ "@esbuild/linux-ia32": "0.27.2", ++ "@esbuild/linux-loong64": "0.27.2", ++ "@esbuild/linux-mips64el": "0.27.2", ++ "@esbuild/linux-ppc64": "0.27.2", ++ "@esbuild/linux-riscv64": "0.27.2", ++ "@esbuild/linux-s390x": "0.27.2", ++ "@esbuild/linux-x64": "0.27.2", ++ "@esbuild/netbsd-arm64": "0.27.2", ++ "@esbuild/netbsd-x64": "0.27.2", ++ "@esbuild/openbsd-arm64": "0.27.2", ++ "@esbuild/openbsd-x64": "0.27.2", ++ "@esbuild/openharmony-arm64": "0.27.2", ++ "@esbuild/sunos-x64": "0.27.2", ++ "@esbuild/win32-arm64": "0.27.2", ++ "@esbuild/win32-ia32": "0.27.2", ++ "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escalade": { @@ -7523,13 +7414,11 @@ + } + }, + "node_modules/estree-walker": { -+ "version": "3.0.3", -+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", -+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", -+ "license": "MIT", -+ "dependencies": { -+ "@types/estree": "^1.0.0" -+ } ++ "version": "2.0.2", ++ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", ++ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", ++ "dev": true, ++ "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", @@ -7629,6 +7518,18 @@ + "url": "https://github.com/sponsors/sindresorhus" + } + }, ++ "node_modules/execa/node_modules/signal-exit": { ++ "version": "4.1.0", ++ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", ++ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", ++ "license": "ISC", ++ "engines": { ++ "node": ">=14" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/isaacs" ++ } ++ }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", @@ -7671,25 +7572,13 @@ + "url": "https://opencollective.com/express" + } + }, -+ "node_modules/express/node_modules/mime-db": { -+ "version": "1.54.0", -+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", -+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", -+ "license": "MIT", -+ "engines": { -+ "node": ">= 0.6" -+ } -+ }, -+ "node_modules/express/node_modules/mime-types": { -+ "version": "3.0.1", -+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", -+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", ++ "node_modules/express/node_modules/cookie-signature": { ++ "version": "1.2.2", ++ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", ++ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", -+ "dependencies": { -+ "mime-db": "^1.54.0" -+ }, + "engines": { -+ "node": ">= 0.6" ++ "node": ">=6.6.0" + } + }, + "node_modules/extend": { @@ -7714,18 +7603,6 @@ + "node": ">=8.6.0" + } + }, -+ "node_modules/fast-glob/node_modules/glob-parent": { -+ "version": "5.1.2", -+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", -+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", -+ "license": "ISC", -+ "dependencies": { -+ "is-glob": "^4.0.1" -+ }, -+ "engines": { -+ "node": ">= 6" -+ } -+ }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", @@ -7753,27 +7630,21 @@ + "reusify": "^1.0.4" + } + }, -+ "node_modules/fetch-blob": { -+ "version": "3.2.0", -+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", -+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", -+ "funding": [ -+ { -+ "type": "github", -+ "url": "https://github.com/sponsors/jimmywarting" -+ }, -+ { -+ "type": "paypal", -+ "url": "https://paypal.me/jimmywarting" -+ } -+ ], -+ "license": "MIT", -+ "dependencies": { -+ "node-domexception": "^1.0.0", -+ "web-streams-polyfill": "^3.0.3" -+ }, ++ "node_modules/fdir": { ++ "version": "6.5.0", ++ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", ++ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", ++ "license": "MIT", + "engines": { -+ "node": "^12.20 || >= 14.13" ++ "node": ">=12.0.0" ++ }, ++ "peerDependencies": { ++ "picomatch": "^3 || ^4" ++ }, ++ "peerDependenciesMeta": { ++ "picomatch": { ++ "optional": true ++ } + } + }, + "node_modules/fflate": { @@ -7867,26 +7738,10 @@ + "url": "https://github.com/sponsors/ljharb" + } + }, -+ "node_modules/foreground-child": { -+ "version": "3.3.1", -+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", -+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", -+ "license": "ISC", -+ "dependencies": { -+ "cross-spawn": "^7.0.6", -+ "signal-exit": "^4.0.1" -+ }, -+ "engines": { -+ "node": ">=14" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/isaacs" -+ } -+ }, + "node_modules/form-data": { -+ "version": "4.0.4", -+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", -+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", ++ "version": "4.0.5", ++ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", ++ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", @@ -7905,6 +7760,27 @@ + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, ++ "node_modules/form-data/node_modules/mime-db": { ++ "version": "1.52.0", ++ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", ++ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", ++ "license": "MIT", ++ "engines": { ++ "node": ">= 0.6" ++ } ++ }, ++ "node_modules/form-data/node_modules/mime-types": { ++ "version": "2.1.35", ++ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", ++ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", ++ "license": "MIT", ++ "dependencies": { ++ "mime-db": "1.52.0" ++ }, ++ "engines": { ++ "node": ">= 0.6" ++ } ++ }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", @@ -7927,18 +7803,6 @@ + "node": ">= 14" + } + }, -+ "node_modules/formdata-polyfill": { -+ "version": "4.0.10", -+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", -+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", -+ "license": "MIT", -+ "dependencies": { -+ "fetch-blob": "^3.1.2" -+ }, -+ "engines": { -+ "node": ">=12.20.0" -+ } -+ }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -7949,15 +7813,15 @@ + } + }, + "node_modules/fraction.js": { -+ "version": "4.3.7", -+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", -+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", ++ "version": "5.3.4", ++ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", ++ "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { -+ "type": "patreon", ++ "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, @@ -8000,15 +7864,6 @@ + "url": "https://github.com/sponsors/ljharb" + } + }, -+ "node_modules/functions-have-names": { -+ "version": "1.2.3", -+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", -+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", -+ "license": "MIT", -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", @@ -8025,26 +7880,6 @@ + "node": ">=14" + } + }, -+ "node_modules/gaxios/node_modules/node-fetch": { -+ "version": "2.7.0", -+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", -+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", -+ "license": "MIT", -+ "dependencies": { -+ "whatwg-url": "^5.0.0" -+ }, -+ "engines": { -+ "node": "4.x || >=6.0.0" -+ }, -+ "peerDependencies": { -+ "encoding": "^0.1.0" -+ }, -+ "peerDependenciesMeta": { -+ "encoding": { -+ "optional": true -+ } -+ } -+ }, + "node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", @@ -8059,6 +7894,25 @@ + "node": ">=14" + } + }, ++ "node_modules/generator-function": { ++ "version": "2.0.1", ++ "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", ++ "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", ++ "license": "MIT", ++ "engines": { ++ "node": ">= 0.4" ++ } ++ }, ++ "node_modules/gensync": { ++ "version": "1.0.0-beta.2", ++ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", ++ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -8135,45 +7989,29 @@ + "url": "https://github.com/sponsors/sindresorhus" + } + }, -+ "node_modules/glob": { -+ "version": "10.4.5", -+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", -+ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", -+ "license": "ISC", ++ "node_modules/get-tsconfig": { ++ "version": "4.13.0", ++ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", ++ "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", ++ "dev": true, ++ "license": "MIT", + "dependencies": { -+ "foreground-child": "^3.1.0", -+ "jackspeak": "^3.1.2", -+ "minimatch": "^9.0.4", -+ "minipass": "^7.1.2", -+ "package-json-from-dist": "^1.0.0", -+ "path-scurry": "^1.11.1" -+ }, -+ "bin": { -+ "glob": "dist/esm/bin.mjs" ++ "resolve-pkg-maps": "^1.0.0" + }, + "funding": { -+ "url": "https://github.com/sponsors/isaacs" ++ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { -+ "version": "6.0.2", -+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", -+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", ++ "version": "5.1.2", ++ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", ++ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { -+ "is-glob": "^4.0.3" ++ "is-glob": "^4.0.1" + }, + "engines": { -+ "node": ">=10.13.0" -+ } -+ }, -+ "node_modules/goober": { -+ "version": "2.1.16", -+ "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", -+ "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", -+ "license": "MIT", -+ "peerDependencies": { -+ "csstype": "^3.0.10" ++ "node": ">= 6" + } + }, + "node_modules/google-auth-library": { @@ -8216,26 +8054,6 @@ + "node": ">=14" + } + }, -+ "node_modules/google-gax/node_modules/node-fetch": { -+ "version": "2.7.0", -+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", -+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", -+ "license": "MIT", -+ "dependencies": { -+ "whatwg-url": "^5.0.0" -+ }, -+ "engines": { -+ "node": "4.x || >=6.0.0" -+ }, -+ "peerDependencies": { -+ "encoding": "^0.1.0" -+ }, -+ "peerDependenciesMeta": { -+ "encoding": { -+ "optional": true -+ } -+ } -+ }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", @@ -8258,9 +8076,9 @@ + } + }, + "node_modules/graphql": { -+ "version": "16.11.0", -+ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", -+ "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", ++ "version": "16.12.0", ++ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", ++ "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" @@ -8279,25 +8097,14 @@ + "node": ">=14.0.0" + } + }, -+ "node_modules/has-bigints": { -+ "version": "1.1.0", -+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", -+ "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", -+ "license": "MIT", -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, + "node_modules/has-flag": { -+ "version": "4.0.0", -+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", -+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", ++ "version": "3.0.0", ++ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", ++ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", ++ "dev": true, + "license": "MIT", + "engines": { -+ "node": ">=8" ++ "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { @@ -8357,11 +8164,14 @@ + "integrity": "sha512-tUCGvt191vNSQgttSyJoibR+VO+I6+iCHIUdhzEMJKE+EAL8BwCN7fUOZlY4ofOelNHsK+gEjxB/B+9N3EWtdA==", + "license": "MIT" + }, -+ "node_modules/headlessui": { -+ "version": "0.0.0", -+ "resolved": "https://registry.npmjs.org/headlessui/-/headlessui-0.0.0.tgz", -+ "integrity": "sha512-CHvacVPbl8AqIg2sBNKySUmumu7o15jSrCaTrIh9GW2Eq4y/krCN/vZFOsKCwlrhWQbO4267a8xvvP8bs+qREQ==", -+ "license": "MIT" ++ "node_modules/helmet": { ++ "version": "6.2.0", ++ "resolved": "https://registry.npmjs.org/helmet/-/helmet-6.2.0.tgz", ++ "integrity": "sha512-DWlwuXLLqbrIOltR6tFQXShj/+7Cyp0gLi6uAb8qMdFh/YBBFbKSgQ6nbXmScYd8emMctuthmgIa7tUfo9Rtyg==", ++ "license": "MIT", ++ "engines": { ++ "node": ">=14.0.0" ++ } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", @@ -8376,28 +8186,23 @@ + } + }, + "node_modules/http-errors": { -+ "version": "2.0.0", -+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", -+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", ++ "version": "2.0.1", ++ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", ++ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { -+ "depd": "2.0.0", -+ "inherits": "2.0.4", -+ "setprototypeof": "1.2.0", -+ "statuses": "2.0.1", -+ "toidentifier": "1.0.1" ++ "depd": "~2.0.0", ++ "inherits": "~2.0.4", ++ "setprototypeof": "~1.2.0", ++ "statuses": "~2.0.2", ++ "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" -+ } -+ }, -+ "node_modules/http-errors/node_modules/statuses": { -+ "version": "2.0.1", -+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", -+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", -+ "license": "MIT", -+ "engines": { -+ "node": ">= 0.8" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { @@ -8458,15 +8263,19 @@ + } + }, + "node_modules/iconv-lite": { -+ "version": "0.6.3", -+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", -+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", ++ "version": "0.7.1", ++ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", ++ "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { @@ -8489,6 +8298,13 @@ + ], + "license": "BSD-3-Clause" + }, ++ "node_modules/ignore-by-default": { ++ "version": "1.0.1", ++ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", ++ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", ++ "dev": true, ++ "license": "ISC" ++ }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -8530,62 +8346,6 @@ + "node": ">=12.0.0" + } + }, -+ "node_modules/inquirer/node_modules/ansi-regex": { -+ "version": "5.0.1", -+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", -+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", -+ "license": "MIT", -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/inquirer/node_modules/ansi-styles": { -+ "version": "4.3.0", -+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", -+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", -+ "license": "MIT", -+ "dependencies": { -+ "color-convert": "^2.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ }, -+ "funding": { -+ "url": "https://github.com/chalk/ansi-styles?sponsor=1" -+ } -+ }, -+ "node_modules/inquirer/node_modules/emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "license": "MIT" -+ }, -+ "node_modules/inquirer/node_modules/string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "license": "MIT", -+ "dependencies": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/inquirer/node_modules/strip-ansi": { -+ "version": "6.0.1", -+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", -+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", -+ "license": "MIT", -+ "dependencies": { -+ "ansi-regex": "^5.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, + "node_modules/inquirer/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -8600,20 +8360,6 @@ + "node": ">=8" + } + }, -+ "node_modules/internal-slot": { -+ "version": "1.1.0", -+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", -+ "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", -+ "license": "MIT", -+ "dependencies": { -+ "es-errors": "^1.3.0", -+ "hasown": "^2.0.2", -+ "side-channel": "^1.1.0" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ } -+ }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -8639,38 +8385,6 @@ + "url": "https://github.com/sponsors/ljharb" + } + }, -+ "node_modules/is-array-buffer": { -+ "version": "3.0.5", -+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", -+ "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", -+ "license": "MIT", -+ "dependencies": { -+ "call-bind": "^1.0.8", -+ "call-bound": "^1.0.3", -+ "get-intrinsic": "^1.2.6" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, -+ "node_modules/is-bigint": { -+ "version": "1.1.0", -+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", -+ "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", -+ "license": "MIT", -+ "dependencies": { -+ "has-bigints": "^1.0.2" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -8683,22 +8397,6 @@ + "node": ">=8" + } + }, -+ "node_modules/is-boolean-object": { -+ "version": "1.2.2", -+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", -+ "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", -+ "license": "MIT", -+ "dependencies": { -+ "call-bound": "^1.0.3", -+ "has-tostringtag": "^1.0.2" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -8726,22 +8424,6 @@ + "url": "https://github.com/sponsors/ljharb" + } + }, -+ "node_modules/is-date-object": { -+ "version": "1.1.0", -+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", -+ "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", -+ "license": "MIT", -+ "dependencies": { -+ "call-bound": "^1.0.2", -+ "has-tostringtag": "^1.0.2" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -8761,13 +8443,14 @@ + } + }, + "node_modules/is-generator-function": { -+ "version": "1.1.0", -+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", -+ "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", ++ "version": "1.1.2", ++ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", ++ "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { -+ "call-bound": "^1.0.3", -+ "get-proto": "^1.0.0", ++ "call-bound": "^1.0.4", ++ "generator-function": "^2.0.0", ++ "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, @@ -8799,17 +8482,12 @@ + "node": ">=8" + } + }, -+ "node_modules/is-map": { -+ "version": "2.0.3", -+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", -+ "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", -+ "license": "MIT", -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } ++ "node_modules/is-module": { ++ "version": "1.0.0", ++ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", ++ "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", ++ "dev": true, ++ "license": "MIT" + }, + "node_modules/is-node-process": { + "version": "1.2.0", @@ -8826,22 +8504,6 @@ + "node": ">=0.12.0" + } + }, -+ "node_modules/is-number-object": { -+ "version": "1.1.1", -+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", -+ "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", -+ "license": "MIT", -+ "dependencies": { -+ "call-bound": "^1.0.3", -+ "has-tostringtag": "^1.0.2" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -8872,33 +8534,6 @@ + "url": "https://github.com/sponsors/ljharb" + } + }, -+ "node_modules/is-set": { -+ "version": "2.0.3", -+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", -+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", -+ "license": "MIT", -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, -+ "node_modules/is-shared-array-buffer": { -+ "version": "1.0.4", -+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", -+ "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", -+ "license": "MIT", -+ "dependencies": { -+ "call-bound": "^1.0.3" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -8911,39 +8546,6 @@ + "url": "https://github.com/sponsors/sindresorhus" + } + }, -+ "node_modules/is-string": { -+ "version": "1.1.1", -+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", -+ "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", -+ "license": "MIT", -+ "dependencies": { -+ "call-bound": "^1.0.3", -+ "has-tostringtag": "^1.0.2" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, -+ "node_modules/is-symbol": { -+ "version": "1.1.1", -+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", -+ "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", -+ "license": "MIT", -+ "dependencies": { -+ "call-bound": "^1.0.2", -+ "has-symbols": "^1.1.0", -+ "safe-regex-test": "^1.1.0" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", @@ -8971,73 +8573,24 @@ + "url": "https://github.com/sponsors/sindresorhus" + } + }, -+ "node_modules/is-weakmap": { -+ "version": "2.0.2", -+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", -+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", -+ "license": "MIT", -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, -+ "node_modules/is-weakset": { -+ "version": "2.0.4", -+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", -+ "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", -+ "license": "MIT", -+ "dependencies": { -+ "call-bound": "^1.0.3", -+ "get-intrinsic": "^1.2.6" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, + "node_modules/is-what": { -+ "version": "4.1.16", -+ "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", -+ "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", ++ "version": "5.5.0", ++ "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", ++ "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { -+ "node": ">=12.13" ++ "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, -+ "node_modules/isarray": { -+ "version": "2.0.5", -+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", -+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", -+ "license": "MIT" -+ }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, -+ "node_modules/jackspeak": { -+ "version": "3.4.3", -+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", -+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", -+ "license": "BlueOak-1.0.0", -+ "dependencies": { -+ "@isaacs/cliui": "^8.0.2" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/isaacs" -+ }, -+ "optionalDependencies": { -+ "@pkgjs/parseargs": "^0.11.0" -+ } -+ }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", @@ -9166,6 +8719,19 @@ + "node": ">=14" + } + }, ++ "node_modules/jsesc": { ++ "version": "3.1.0", ++ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", ++ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", ++ "dev": true, ++ "license": "MIT", ++ "bin": { ++ "jsesc": "bin/jsesc" ++ }, ++ "engines": { ++ "node": ">=6" ++ } ++ }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -9175,6 +8741,19 @@ + "bignumber.js": "^9.0.0" + } + }, ++ "node_modules/json5": { ++ "version": "2.2.3", ++ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", ++ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", ++ "dev": true, ++ "license": "MIT", ++ "bin": { ++ "json5": "lib/cli.js" ++ }, ++ "engines": { ++ "node": ">=6" ++ } ++ }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", @@ -9242,30 +8821,12 @@ + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, -+ "node_modules/lodash.castarray": { -+ "version": "4.4.0", -+ "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", -+ "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", -+ "license": "MIT" -+ }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, -+ "node_modules/lodash.isplainobject": { -+ "version": "4.0.6", -+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", -+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", -+ "license": "MIT" -+ }, -+ "node_modules/lodash.merge": { -+ "version": "4.6.2", -+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", -+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", -+ "license": "MIT" -+ }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -9310,10 +8871,14 @@ + } + }, + "node_modules/lru-cache": { -+ "version": "10.4.3", -+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", -+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", -+ "license": "ISC" ++ "version": "5.1.1", ++ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", ++ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", ++ "dev": true, ++ "license": "ISC", ++ "dependencies": { ++ "yallist": "^3.0.2" ++ } + }, + "node_modules/lucia": { + "version": "3.2.2", @@ -9349,14 +8914,15 @@ + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "license": "MIT", ++ "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { -+ "version": "0.30.19", -+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", -+ "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", ++ "version": "0.30.21", ++ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", ++ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" @@ -9443,25 +9009,41 @@ + "node": ">=8.6" + } + }, ++ "node_modules/micromatch/node_modules/picomatch": { ++ "version": "2.3.1", ++ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", ++ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", ++ "license": "MIT", ++ "engines": { ++ "node": ">=8.6" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/jonschlinkert" ++ } ++ }, + "node_modules/mime-db": { -+ "version": "1.52.0", -+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", -+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", ++ "version": "1.54.0", ++ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", ++ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { -+ "version": "2.1.35", -+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", -+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", ++ "version": "3.0.2", ++ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", ++ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { -+ "mime-db": "1.52.0" ++ "mime-db": "^1.54.0" + }, + "engines": { -+ "node": ">= 0.6" ++ "node": ">=18" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-fn": { @@ -9492,27 +9074,16 @@ + } + }, + "node_modules/minimatch": { -+ "version": "9.0.5", -+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", -+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", ++ "version": "3.1.2", ++ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", ++ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", ++ "dev": true, + "license": "ISC", + "dependencies": { -+ "brace-expansion": "^2.0.1" -+ }, -+ "engines": { -+ "node": ">=16 || 14 >=14.17" ++ "brace-expansion": "^1.1.7" + }, -+ "funding": { -+ "url": "https://github.com/sponsors/isaacs" -+ } -+ }, -+ "node_modules/minipass": { -+ "version": "7.1.2", -+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", -+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", -+ "license": "ISC", + "engines": { -+ "node": ">=16 || 14 >=14.17" ++ "node": "*" + } + }, + "node_modules/mitt": { @@ -9533,12 +9104,49 @@ + "ufo": "^1.6.1" + } + }, -+ "node_modules/mlly/node_modules/pathe": { -+ "version": "2.0.3", -+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", -+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", ++ "node_modules/morgan": { ++ "version": "1.10.1", ++ "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", ++ "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", ++ "license": "MIT", ++ "dependencies": { ++ "basic-auth": "~2.0.1", ++ "debug": "2.6.9", ++ "depd": "~2.0.0", ++ "on-finished": "~2.3.0", ++ "on-headers": "~1.1.0" ++ }, ++ "engines": { ++ "node": ">= 0.8.0" ++ } ++ }, ++ "node_modules/morgan/node_modules/debug": { ++ "version": "2.6.9", ++ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", ++ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", ++ "license": "MIT", ++ "dependencies": { ++ "ms": "2.0.0" ++ } ++ }, ++ "node_modules/morgan/node_modules/ms": { ++ "version": "2.0.0", ++ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", ++ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, ++ "node_modules/morgan/node_modules/on-finished": { ++ "version": "2.3.0", ++ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", ++ "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", ++ "license": "MIT", ++ "dependencies": { ++ "ee-first": "1.1.1" ++ }, ++ "engines": { ++ "node": ">= 0.8" ++ } ++ }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -9592,43 +9200,29 @@ + "url": "https://opencollective.com/mswjs" + }, + "peerDependencies": { -+ "typescript": ">= 4.4.x" -+ }, -+ "peerDependenciesMeta": { -+ "typescript": { -+ "optional": true -+ } -+ } -+ }, -+ "node_modules/msw/node_modules/cookie": { -+ "version": "0.4.2", -+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", -+ "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", -+ "license": "MIT", -+ "engines": { -+ "node": ">= 0.6" -+ } -+ }, -+ "node_modules/msw/node_modules/node-fetch": { -+ "version": "2.7.0", -+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", -+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", -+ "license": "MIT", -+ "dependencies": { -+ "whatwg-url": "^5.0.0" -+ }, -+ "engines": { -+ "node": "4.x || >=6.0.0" -+ }, -+ "peerDependencies": { -+ "encoding": "^0.1.0" ++ "typescript": ">= 4.4.x" + }, + "peerDependenciesMeta": { -+ "encoding": { ++ "typescript": { + "optional": true + } + } + }, ++ "node_modules/msw/node_modules/cookie": { ++ "version": "0.4.2", ++ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", ++ "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", ++ "license": "MIT", ++ "engines": { ++ "node": ">= 0.6" ++ } ++ }, ++ "node_modules/msw/node_modules/path-to-regexp": { ++ "version": "6.3.0", ++ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", ++ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", ++ "license": "MIT" ++ }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -9694,29 +9288,70 @@ + } + }, + "node_modules/node-fetch": { -+ "version": "3.3.0", -+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.0.tgz", -+ "integrity": "sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==", ++ "version": "2.7.0", ++ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", ++ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { -+ "data-uri-to-buffer": "^4.0.0", -+ "fetch-blob": "^3.1.4", -+ "formdata-polyfill": "^4.0.10" ++ "whatwg-url": "^5.0.0" + }, + "engines": { -+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" ++ "node": "4.x || >=6.0.0" + }, -+ "funding": { -+ "type": "opencollective", -+ "url": "https://opencollective.com/node-fetch" ++ "peerDependencies": { ++ "encoding": "^0.1.0" ++ }, ++ "peerDependenciesMeta": { ++ "encoding": { ++ "optional": true ++ } + } + }, + "node_modules/node-releases": { -+ "version": "2.0.21", -+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", -+ "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", ++ "version": "2.0.27", ++ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", ++ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "license": "MIT" + }, ++ "node_modules/nodemon": { ++ "version": "2.0.22", ++ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", ++ "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "chokidar": "^3.5.2", ++ "debug": "^3.2.7", ++ "ignore-by-default": "^1.0.1", ++ "minimatch": "^3.1.2", ++ "pstree.remy": "^1.1.8", ++ "semver": "^5.7.1", ++ "simple-update-notifier": "^1.0.7", ++ "supports-color": "^5.5.0", ++ "touch": "^3.1.0", ++ "undefsafe": "^2.0.5" ++ }, ++ "bin": { ++ "nodemon": "bin/nodemon.js" ++ }, ++ "engines": { ++ "node": ">=8.10.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/nodemon" ++ } ++ }, ++ "node_modules/nodemon/node_modules/debug": { ++ "version": "3.2.7", ++ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", ++ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "ms": "^2.1.1" ++ } ++ }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -9798,51 +9433,6 @@ + "url": "https://github.com/sponsors/ljharb" + } + }, -+ "node_modules/object-is": { -+ "version": "1.1.6", -+ "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", -+ "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", -+ "license": "MIT", -+ "dependencies": { -+ "call-bind": "^1.0.7", -+ "define-properties": "^1.2.1" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, -+ "node_modules/object-keys": { -+ "version": "1.1.1", -+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", -+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", -+ "license": "MIT", -+ "engines": { -+ "node": ">= 0.4" -+ } -+ }, -+ "node_modules/object.assign": { -+ "version": "4.1.7", -+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", -+ "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", -+ "license": "MIT", -+ "dependencies": { -+ "call-bind": "^1.0.8", -+ "call-bound": "^1.0.3", -+ "define-properties": "^1.2.1", -+ "es-object-atoms": "^1.0.0", -+ "has-symbols": "^1.1.0", -+ "object-keys": "^1.1.1" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -9855,6 +9445,15 @@ + "node": ">= 0.8" + } + }, ++ "node_modules/on-headers": { ++ "version": "1.1.0", ++ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", ++ "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", ++ "license": "MIT", ++ "engines": { ++ "node": ">= 0.8" ++ } ++ }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -9910,34 +9509,14 @@ + } + }, + "node_modules/openai/node_modules/@types/node": { -+ "version": "18.19.125", -+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.125.tgz", -+ "integrity": "sha512-4TWNu0IxTQcszliYdW2mxrVvhHeERUeDCUwVuvQFn9JCU02kxrUDs8v52yOazPo7wLHKgqEd2FKxlSN6m8Deqg==", ++ "version": "18.19.130", ++ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", ++ "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, -+ "node_modules/openai/node_modules/node-fetch": { -+ "version": "2.7.0", -+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", -+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", -+ "license": "MIT", -+ "dependencies": { -+ "whatwg-url": "^5.0.0" -+ }, -+ "engines": { -+ "node": "4.x || >=6.0.0" -+ }, -+ "peerDependencies": { -+ "encoding": "^0.1.0" -+ }, -+ "peerDependenciesMeta": { -+ "encoding": { -+ "optional": true -+ } -+ } -+ }, + "node_modules/openai/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -9967,31 +9546,10 @@ + "url": "https://github.com/sponsors/sindresorhus" + } + }, -+ "node_modules/ora/node_modules/ansi-regex": { -+ "version": "5.0.1", -+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", -+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", -+ "license": "MIT", -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/ora/node_modules/strip-ansi": { -+ "version": "6.0.1", -+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", -+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", -+ "license": "MIT", -+ "dependencies": { -+ "ansi-regex": "^5.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, + "node_modules/oslo": { -+ "version": "1.2.1", -+ "resolved": "https://registry.npmjs.org/oslo/-/oslo-1.2.1.tgz", -+ "integrity": "sha512-HfIhB5ruTdQv0XX2XlncWQiJ5SIHZ7NHZhVyHth0CSZ/xzge00etRyYy/3wp/Dsu+PkxMC+6+B2lS/GcKoewkA==", ++ "version": "1.2.0", ++ "resolved": "https://registry.npmjs.org/oslo/-/oslo-1.2.0.tgz", ++ "integrity": "sha512-OoFX6rDsNcOQVAD2gQD/z03u4vEjWZLzJtwkmgfRF+KpQUXwdgEXErD7zNhyowmHwHefP+PM9Pw13pgpHMRlzw==", + "deprecated": "Package is no longer supported. Please see https://oslojs.dev for the successor project.", + "license": "MIT", + "dependencies": { @@ -10317,12 +9875,6 @@ + "url": "https://github.com/sponsors/sindresorhus" + } + }, -+ "node_modules/package-json-from-dist": { -+ "version": "1.0.1", -+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", -+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", -+ "license": "BlueOak-1.0.0" -+ }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -10359,32 +9911,20 @@ + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, -+ "node_modules/path-scurry": { -+ "version": "1.11.1", -+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", -+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", -+ "license": "BlueOak-1.0.0", -+ "dependencies": { -+ "lru-cache": "^10.2.0", -+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" -+ }, -+ "engines": { -+ "node": ">=16 || 14 >=14.18" -+ }, ++ "node_modules/path-to-regexp": { ++ "version": "8.3.0", ++ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", ++ "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", ++ "license": "MIT", + "funding": { -+ "url": "https://github.com/sponsors/isaacs" ++ "type": "opencollective", ++ "url": "https://opencollective.com/express" + } + }, -+ "node_modules/path-to-regexp": { -+ "version": "6.3.0", -+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", -+ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", -+ "license": "MIT" -+ }, + "node_modules/pathe": { -+ "version": "1.1.2", -+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", -+ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", ++ "version": "2.0.3", ++ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", ++ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/pathval": { @@ -10510,12 +10050,12 @@ + "license": "ISC" + }, + "node_modules/picomatch": { -+ "version": "2.3.1", -+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", -+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", ++ "version": "4.0.3", ++ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", ++ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { -+ "node": ">=8.6" ++ "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" @@ -10550,12 +10090,6 @@ + "pathe": "^2.0.1" + } + }, -+ "node_modules/pkg-types/node_modules/pathe": { -+ "version": "2.0.3", -+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", -+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", -+ "license": "MIT" -+ }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -10611,9 +10145,19 @@ + } + }, + "node_modules/postcss-js": { -+ "version": "4.0.1", -+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", -+ "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", ++ "version": "4.1.0", ++ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", ++ "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", ++ "funding": [ ++ { ++ "type": "opencollective", ++ "url": "https://opencollective.com/postcss/" ++ }, ++ { ++ "type": "github", ++ "url": "https://github.com/sponsors/ai" ++ } ++ ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" @@ -10621,18 +10165,14 @@ + "engines": { + "node": "^12 || ^14 || >= 16" + }, -+ "funding": { -+ "type": "opencollective", -+ "url": "https://opencollective.com/postcss/" -+ }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { -+ "version": "4.0.2", -+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", -+ "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", ++ "version": "6.0.1", ++ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", ++ "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "funding": [ + { + "type": "opencollective", @@ -10645,21 +10185,28 @@ + ], + "license": "MIT", + "dependencies": { -+ "lilconfig": "^3.0.0", -+ "yaml": "^2.3.4" ++ "lilconfig": "^3.1.1" + }, + "engines": { -+ "node": ">= 14" ++ "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 + } + } @@ -10849,6 +10396,7 @@ + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "license": "MIT", ++ "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", @@ -10858,20 +10406,12 @@ + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, -+ "node_modules/pretty-format/node_modules/ansi-regex": { -+ "version": "5.0.1", -+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", -+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", -+ "license": "MIT", -+ "engines": { -+ "node": ">=8" -+ } -+ }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", ++ "peer": true, + "engines": { + "node": ">=10" + }, @@ -10883,7 +10423,8 @@ + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", -+ "license": "MIT" ++ "license": "MIT", ++ "peer": true + }, + "node_modules/prisma": { + "version": "5.19.1", @@ -10982,6 +10523,13 @@ + "url": "https://github.com/sponsors/lupomontero" + } + }, ++ "node_modules/pstree.remy": { ++ "version": "1.1.8", ++ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", ++ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", ++ "dev": true, ++ "license": "MIT" ++ }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -11042,44 +10590,25 @@ + } + }, + "node_modules/raw-body": { -+ "version": "3.0.1", -+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", -+ "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", ++ "version": "3.0.2", ++ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", ++ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { -+ "bytes": "3.1.2", -+ "http-errors": "2.0.0", -+ "iconv-lite": "0.7.0", -+ "unpipe": "1.0.0" ++ "bytes": "~3.1.2", ++ "http-errors": "~2.0.1", ++ "iconv-lite": "~0.7.0", ++ "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, -+ "node_modules/raw-body/node_modules/iconv-lite": { -+ "version": "0.7.0", -+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", -+ "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", -+ "license": "MIT", -+ "dependencies": { -+ "safer-buffer": ">= 2.1.2 < 3.0.0" -+ }, -+ "engines": { -+ "node": ">=0.10.0" -+ }, -+ "funding": { -+ "type": "opencollective", -+ "url": "https://opencollective.com/express" -+ } -+ }, + "node_modules/react": { -+ "version": "18.3.1", -+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", -+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", ++ "version": "19.2.3", ++ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", ++ "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", -+ "dependencies": { -+ "loose-envify": "^1.1.0" -+ }, + "engines": { + "node": ">=0.10.0" + } @@ -11098,22 +10627,21 @@ + } + }, + "node_modules/react-dom": { -+ "version": "18.3.1", -+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", -+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", ++ "version": "19.2.3", ++ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", ++ "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "license": "MIT", + "dependencies": { -+ "loose-envify": "^1.1.0", -+ "scheduler": "^0.23.2" ++ "scheduler": "^0.27.0" + }, + "peerDependencies": { -+ "react": "^18.3.1" ++ "react": "^19.2.3" + } + }, + "node_modules/react-hook-form": { -+ "version": "7.62.0", -+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz", -+ "integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==", ++ "version": "7.66.1", ++ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.66.1.tgz", ++ "integrity": "sha512-2KnjpgG2Rhbi+CIiIBQQ9Df6sMGH5ExNyFl4Hw9qO7pIqMBR8Bvu9RQyjl3JM4vehzCh9soiNUM/xYMswb2EiA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" @@ -11126,23 +10654,6 @@ + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, -+ "node_modules/react-hot-toast": { -+ "version": "2.6.0", -+ "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", -+ "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", -+ "license": "MIT", -+ "dependencies": { -+ "csstype": "^3.1.3", -+ "goober": "^2.1.16" -+ }, -+ "engines": { -+ "node": ">=10" -+ }, -+ "peerDependencies": { -+ "react": ">=16", -+ "react-dom": ">=16" -+ } -+ }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", @@ -11158,6 +10669,16 @@ + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, ++ "node_modules/react-refresh": { ++ "version": "0.17.0", ++ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", ++ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=0.10.0" ++ } ++ }, + "node_modules/react-remove-scroll": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", @@ -11206,12 +10727,12 @@ + } + }, + "node_modules/react-router": { -+ "version": "6.30.1", -+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz", -+ "integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==", ++ "version": "6.30.2", ++ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.2.tgz", ++ "integrity": "sha512-H2Bm38Zu1bm8KUE5NVWRMzuIyAV8p/JrOaBJAwVmp37AXG72+CZJlEBw6pdn9i5TBgLMhNDgijS4ZlblpHyWTA==", + "license": "MIT", + "dependencies": { -+ "@remix-run/router": "1.23.0" ++ "@remix-run/router": "1.23.1" + }, + "engines": { + "node": ">=14.0.0" @@ -11221,13 +10742,13 @@ + } + }, + "node_modules/react-router-dom": { -+ "version": "6.30.1", -+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz", -+ "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==", ++ "version": "6.30.2", ++ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.2.tgz", ++ "integrity": "sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q==", + "license": "MIT", + "dependencies": { -+ "@remix-run/router": "1.23.0", -+ "react-router": "6.30.1" ++ "@remix-run/router": "1.23.1", ++ "react-router": "6.30.2" + }, + "engines": { + "node": ">=14.0.0" @@ -11287,11 +10808,23 @@ + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", -+ "dependencies": { -+ "picomatch": "^2.2.1" -+ }, ++ "dependencies": { ++ "picomatch": "^2.2.1" ++ }, ++ "engines": { ++ "node": ">=8.10.0" ++ } ++ }, ++ "node_modules/readdirp/node_modules/picomatch": { ++ "version": "2.3.1", ++ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", ++ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", ++ "license": "MIT", + "engines": { -+ "node": ">=8.10.0" ++ "node": ">=8.6" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/redent": { @@ -11307,26 +10840,6 @@ + "node": ">=8" + } + }, -+ "node_modules/regexp.prototype.flags": { -+ "version": "1.5.4", -+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", -+ "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", -+ "license": "MIT", -+ "dependencies": { -+ "call-bind": "^1.0.8", -+ "define-properties": "^1.2.1", -+ "es-errors": "^1.3.0", -+ "get-proto": "^1.0.1", -+ "gopd": "^1.2.0", -+ "set-function-name": "^2.0.2" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -11343,12 +10856,12 @@ + "license": "MIT" + }, + "node_modules/resolve": { -+ "version": "1.22.10", -+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", -+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", ++ "version": "1.22.11", ++ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", ++ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { -+ "is-core-module": "^2.16.0", ++ "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, @@ -11362,6 +10875,16 @@ + "url": "https://github.com/sponsors/ljharb" + } + }, ++ "node_modules/resolve-pkg-maps": { ++ "version": "1.0.0", ++ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", ++ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", ++ "dev": true, ++ "license": "MIT", ++ "funding": { ++ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" ++ } ++ }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -11375,12 +10898,6 @@ + "node": ">=8" + } + }, -+ "node_modules/restore-cursor/node_modules/signal-exit": { -+ "version": "3.0.7", -+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", -+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", -+ "license": "ISC" -+ }, + "node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", @@ -11406,9 +10923,9 @@ + } + }, + "node_modules/rollup": { -+ "version": "4.50.2", -+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.2.tgz", -+ "integrity": "sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==", ++ "version": "4.53.3", ++ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", ++ "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" @@ -11421,30 +10938,51 @@ + "npm": ">=8.0.0" + }, + "optionalDependencies": { -+ "@rollup/rollup-android-arm-eabi": "4.50.2", -+ "@rollup/rollup-android-arm64": "4.50.2", -+ "@rollup/rollup-darwin-arm64": "4.50.2", -+ "@rollup/rollup-darwin-x64": "4.50.2", -+ "@rollup/rollup-freebsd-arm64": "4.50.2", -+ "@rollup/rollup-freebsd-x64": "4.50.2", -+ "@rollup/rollup-linux-arm-gnueabihf": "4.50.2", -+ "@rollup/rollup-linux-arm-musleabihf": "4.50.2", -+ "@rollup/rollup-linux-arm64-gnu": "4.50.2", -+ "@rollup/rollup-linux-arm64-musl": "4.50.2", -+ "@rollup/rollup-linux-loong64-gnu": "4.50.2", -+ "@rollup/rollup-linux-ppc64-gnu": "4.50.2", -+ "@rollup/rollup-linux-riscv64-gnu": "4.50.2", -+ "@rollup/rollup-linux-riscv64-musl": "4.50.2", -+ "@rollup/rollup-linux-s390x-gnu": "4.50.2", -+ "@rollup/rollup-linux-x64-gnu": "4.50.2", -+ "@rollup/rollup-linux-x64-musl": "4.50.2", -+ "@rollup/rollup-openharmony-arm64": "4.50.2", -+ "@rollup/rollup-win32-arm64-msvc": "4.50.2", -+ "@rollup/rollup-win32-ia32-msvc": "4.50.2", -+ "@rollup/rollup-win32-x64-msvc": "4.50.2", ++ "@rollup/rollup-android-arm-eabi": "4.53.3", ++ "@rollup/rollup-android-arm64": "4.53.3", ++ "@rollup/rollup-darwin-arm64": "4.53.3", ++ "@rollup/rollup-darwin-x64": "4.53.3", ++ "@rollup/rollup-freebsd-arm64": "4.53.3", ++ "@rollup/rollup-freebsd-x64": "4.53.3", ++ "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", ++ "@rollup/rollup-linux-arm-musleabihf": "4.53.3", ++ "@rollup/rollup-linux-arm64-gnu": "4.53.3", ++ "@rollup/rollup-linux-arm64-musl": "4.53.3", ++ "@rollup/rollup-linux-loong64-gnu": "4.53.3", ++ "@rollup/rollup-linux-ppc64-gnu": "4.53.3", ++ "@rollup/rollup-linux-riscv64-gnu": "4.53.3", ++ "@rollup/rollup-linux-riscv64-musl": "4.53.3", ++ "@rollup/rollup-linux-s390x-gnu": "4.53.3", ++ "@rollup/rollup-linux-x64-gnu": "4.53.3", ++ "@rollup/rollup-linux-x64-musl": "4.53.3", ++ "@rollup/rollup-openharmony-arm64": "4.53.3", ++ "@rollup/rollup-win32-arm64-msvc": "4.53.3", ++ "@rollup/rollup-win32-ia32-msvc": "4.53.3", ++ "@rollup/rollup-win32-x64-gnu": "4.53.3", ++ "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, ++ "node_modules/rollup-plugin-esbuild": { ++ "version": "6.2.1", ++ "resolved": "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-6.2.1.tgz", ++ "integrity": "sha512-jTNOMGoMRhs0JuueJrJqbW8tOwxumaWYq+V5i+PD+8ecSCVkuX27tGW7BXqDgoULQ55rO7IdNxPcnsWtshz3AA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "debug": "^4.4.0", ++ "es-module-lexer": "^1.6.0", ++ "get-tsconfig": "^4.10.0", ++ "unplugin-utils": "^0.2.4" ++ }, ++ "engines": { ++ "node": ">=14.18.0" ++ }, ++ "peerDependencies": { ++ "esbuild": ">=0.18.0", ++ "rollup": "^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" ++ } ++ }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -11461,16 +10999,6 @@ + "node": ">= 18" + } + }, -+ "node_modules/router/node_modules/path-to-regexp": { -+ "version": "8.3.0", -+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", -+ "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", -+ "license": "MIT", -+ "funding": { -+ "type": "opencollective", -+ "url": "https://opencollective.com/express" -+ } -+ }, + "node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", @@ -11574,12 +11102,19 @@ + } + }, + "node_modules/scheduler": { -+ "version": "0.23.2", -+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", -+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", -+ "license": "MIT", -+ "dependencies": { -+ "loose-envify": "^1.1.0" ++ "version": "0.27.0", ++ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", ++ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", ++ "license": "MIT" ++ }, ++ "node_modules/semver": { ++ "version": "5.7.2", ++ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", ++ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", ++ "dev": true, ++ "license": "ISC", ++ "bin": { ++ "semver": "bin/semver" + } + }, + "node_modules/send": { @@ -11604,27 +11139,6 @@ + "node": ">= 18" + } + }, -+ "node_modules/send/node_modules/mime-db": { -+ "version": "1.54.0", -+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", -+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", -+ "license": "MIT", -+ "engines": { -+ "node": ">= 0.6" -+ } -+ }, -+ "node_modules/send/node_modules/mime-types": { -+ "version": "3.0.1", -+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", -+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", -+ "license": "MIT", -+ "dependencies": { -+ "mime-db": "^1.54.0" -+ }, -+ "engines": { -+ "node": ">= 0.6" -+ } -+ }, + "node_modules/serialize-error": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", @@ -11668,9 +11182,9 @@ + } + }, + "node_modules/set-cookie-parser": { -+ "version": "2.7.1", -+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", -+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", ++ "version": "2.7.2", ++ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", ++ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/set-function-length": { @@ -11690,21 +11204,6 @@ + "node": ">= 0.4" + } + }, -+ "node_modules/set-function-name": { -+ "version": "2.0.2", -+ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", -+ "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", -+ "license": "MIT", -+ "dependencies": { -+ "define-data-property": "^1.1.4", -+ "es-errors": "^1.3.0", -+ "functions-have-names": "^1.2.3", -+ "has-property-descriptors": "^1.0.2" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ } -+ }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -11811,15 +11310,32 @@ + "license": "ISC" + }, + "node_modules/signal-exit": { -+ "version": "4.1.0", -+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", -+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", -+ "license": "ISC", -+ "engines": { -+ "node": ">=14" ++ "version": "3.0.7", ++ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", ++ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", ++ "license": "ISC" ++ }, ++ "node_modules/simple-update-notifier": { ++ "version": "1.1.0", ++ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", ++ "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "semver": "~7.0.0" + }, -+ "funding": { -+ "url": "https://github.com/sponsors/isaacs" ++ "engines": { ++ "node": ">=8.10.0" ++ } ++ }, ++ "node_modules/simple-update-notifier/node_modules/semver": { ++ "version": "7.0.0", ++ "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", ++ "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", ++ "dev": true, ++ "license": "ISC", ++ "bin": { ++ "semver": "bin/semver.js" + } + }, + "node_modules/sirv": { @@ -11880,24 +11396,11 @@ + } + }, + "node_modules/std-env": { -+ "version": "3.9.0", -+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", -+ "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", ++ "version": "3.10.0", ++ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", ++ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "license": "MIT" + }, -+ "node_modules/stop-iteration-iterator": { -+ "version": "1.1.0", -+ "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", -+ "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", -+ "license": "MIT", -+ "dependencies": { -+ "es-errors": "^1.3.0", -+ "internal-slot": "^1.1.0" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ } -+ }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", @@ -11929,24 +11432,6 @@ + } + }, + "node_modules/string-width": { -+ "version": "5.1.2", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", -+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", -+ "license": "MIT", -+ "dependencies": { -+ "eastasianwidth": "^0.2.0", -+ "emoji-regex": "^9.2.2", -+ "strip-ansi": "^7.0.1" -+ }, -+ "engines": { -+ "node": ">=12" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/sindresorhus" -+ } -+ }, -+ "node_modules/string-width-cjs": { -+ "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", @@ -11960,50 +11445,7 @@ + "node": ">=8" + } + }, -+ "node_modules/string-width-cjs/node_modules/ansi-regex": { -+ "version": "5.0.1", -+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", -+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", -+ "license": "MIT", -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/string-width-cjs/node_modules/emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "license": "MIT" -+ }, -+ "node_modules/string-width-cjs/node_modules/strip-ansi": { -+ "version": "6.0.1", -+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", -+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", -+ "license": "MIT", -+ "dependencies": { -+ "ansi-regex": "^5.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, + "node_modules/strip-ansi": { -+ "version": "7.1.2", -+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", -+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", -+ "license": "MIT", -+ "dependencies": { -+ "ansi-regex": "^6.0.1" -+ }, -+ "engines": { -+ "node": ">=12" -+ }, -+ "funding": { -+ "url": "https://github.com/chalk/strip-ansi?sponsor=1" -+ } -+ }, -+ "node_modules/strip-ansi-cjs": { -+ "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -12015,15 +11457,6 @@ + "node": ">=8" + } + }, -+ "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { -+ "version": "5.0.1", -+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", -+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", -+ "license": "MIT", -+ "engines": { -+ "node": ">=8" -+ } -+ }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -12105,17 +11538,17 @@ + "license": "MIT" + }, + "node_modules/sucrase": { -+ "version": "3.35.0", -+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", -+ "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", ++ "version": "3.35.1", ++ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", ++ "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", -+ "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", ++ "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { @@ -12127,27 +11560,28 @@ + } + }, + "node_modules/superjson": { -+ "version": "2.2.2", -+ "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", -+ "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", ++ "version": "2.2.5", ++ "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.5.tgz", ++ "integrity": "sha512-zWPTX96LVsA/eVYnqOM2+ofcdPqdS1dAF1LN4TS2/MWuUpfitd9ctTa87wt4xrYnZnkLtS69xpBdSxVBP5Rm6w==", + "license": "MIT", + "dependencies": { -+ "copy-anything": "^3.0.2" ++ "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/supports-color": { -+ "version": "7.2.0", -+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", -+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", ++ "version": "5.5.0", ++ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", ++ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", ++ "dev": true, + "license": "MIT", + "dependencies": { -+ "has-flag": "^4.0.0" ++ "has-flag": "^3.0.0" + }, + "engines": { -+ "node": ">=8" ++ "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { @@ -12270,9 +11704,9 @@ + } + }, + "node_modules/tailwindcss": { -+ "version": "3.4.17", -+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", -+ "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", ++ "version": "3.4.18", ++ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", ++ "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", @@ -12283,7 +11717,7 @@ + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", -+ "jiti": "^1.21.6", ++ "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", @@ -12292,7 +11726,7 @@ + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", -+ "postcss-load-config": "^4.0.2", ++ "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", @@ -12315,6 +11749,18 @@ + "tailwindcss": ">=3.0.0 || insiders" + } + }, ++ "node_modules/tailwindcss/node_modules/glob-parent": { ++ "version": "6.0.2", ++ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", ++ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", ++ "license": "ISC", ++ "dependencies": { ++ "is-glob": "^4.0.3" ++ }, ++ "engines": { ++ "node": ">=10.13.0" ++ } ++ }, + "node_modules/tailwindcss/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", @@ -12369,26 +11815,6 @@ + "node": ">= 6" + } + }, -+ "node_modules/teeny-request/node_modules/node-fetch": { -+ "version": "2.7.0", -+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", -+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", -+ "license": "MIT", -+ "dependencies": { -+ "whatwg-url": "^5.0.0" -+ }, -+ "engines": { -+ "node": "4.x || >=6.0.0" -+ }, -+ "peerDependencies": { -+ "encoding": "^0.1.0" -+ }, -+ "peerDependenciesMeta": { -+ "encoding": { -+ "optional": true -+ } -+ } -+ }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -12412,62 +11838,30 @@ + }, + "node_modules/through": { + "version": "2.3.8", -+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", -+ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", -+ "license": "MIT" -+ }, -+ "node_modules/tinybench": { -+ "version": "2.9.0", -+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", -+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", -+ "license": "MIT" -+ }, -+ "node_modules/tinyglobby": { -+ "version": "0.2.15", -+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", -+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", -+ "dev": true, -+ "license": "MIT", -+ "dependencies": { -+ "fdir": "^6.5.0", -+ "picomatch": "^4.0.3" -+ }, -+ "engines": { -+ "node": ">=12.0.0" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/SuperchupuDev" -+ } -+ }, -+ "node_modules/tinyglobby/node_modules/fdir": { -+ "version": "6.5.0", -+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", -+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", -+ "dev": true, -+ "license": "MIT", -+ "engines": { -+ "node": ">=12.0.0" -+ }, -+ "peerDependencies": { -+ "picomatch": "^3 || ^4" -+ }, -+ "peerDependenciesMeta": { -+ "picomatch": { -+ "optional": true -+ } -+ } ++ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", ++ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", ++ "license": "MIT" + }, -+ "node_modules/tinyglobby/node_modules/picomatch": { -+ "version": "4.0.3", -+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", -+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", -+ "dev": true, ++ "node_modules/tinybench": { ++ "version": "2.9.0", ++ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", ++ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", ++ "license": "MIT" ++ }, ++ "node_modules/tinyglobby": { ++ "version": "0.2.15", ++ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", ++ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", ++ "dependencies": { ++ "fdir": "^6.5.0", ++ "picomatch": "^4.0.3" ++ }, + "engines": { -+ "node": ">=12" ++ "node": ">=12.0.0" + }, + "funding": { -+ "url": "https://github.com/sponsors/jonschlinkert" ++ "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { @@ -12518,6 +11912,16 @@ + "node": ">=6" + } + }, ++ "node_modules/touch": { ++ "version": "3.1.1", ++ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", ++ "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", ++ "dev": true, ++ "license": "ISC", ++ "bin": { ++ "nodetouch": "bin/nodetouch.js" ++ } ++ }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", @@ -12586,27 +11990,6 @@ + "node": ">= 0.6" + } + }, -+ "node_modules/type-is/node_modules/mime-db": { -+ "version": "1.54.0", -+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", -+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", -+ "license": "MIT", -+ "engines": { -+ "node": ">= 0.6" -+ } -+ }, -+ "node_modules/type-is/node_modules/mime-types": { -+ "version": "3.0.1", -+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", -+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", -+ "license": "MIT", -+ "dependencies": { -+ "mime-db": "^1.54.0" -+ }, -+ "engines": { -+ "node": ">= 0.6" -+ } -+ }, + "node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", @@ -12627,10 +12010,17 @@ + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "license": "MIT" + }, ++ "node_modules/undefsafe": { ++ "version": "2.0.5", ++ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", ++ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", ++ "dev": true, ++ "license": "MIT" ++ }, + "node_modules/undici-types": { -+ "version": "7.12.0", -+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", -+ "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", ++ "version": "6.21.0", ++ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", ++ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/universalify": { @@ -12651,10 +12041,27 @@ + "node": ">= 0.8" + } + }, ++ "node_modules/unplugin-utils": { ++ "version": "0.2.5", ++ "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.2.5.tgz", ++ "integrity": "sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "pathe": "^2.0.3", ++ "picomatch": "^4.0.3" ++ }, ++ "engines": { ++ "node": ">=18.12.0" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/sxzz" ++ } ++ }, + "node_modules/update-browserslist-db": { -+ "version": "1.1.3", -+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", -+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", ++ "version": "1.1.4", ++ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", ++ "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "funding": [ + { + "type": "opencollective", @@ -12735,9 +12142,9 @@ + } + }, + "node_modules/use-sync-external-store": { -+ "version": "1.5.0", -+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", -+ "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", ++ "version": "1.6.0", ++ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", ++ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -12791,13 +12198,13 @@ + } + }, + "node_modules/vite": { -+ "version": "7.1.5", -+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", -+ "integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==", ++ "version": "7.3.0", ++ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", ++ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "dev": true, + "license": "MIT", + "dependencies": { -+ "esbuild": "^0.25.0", ++ "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", @@ -13293,10 +12700,16 @@ + "@esbuild/win32-x64": "0.21.5" + } + }, ++ "node_modules/vite-node/node_modules/pathe": { ++ "version": "1.1.2", ++ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", ++ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", ++ "license": "MIT" ++ }, + "node_modules/vite-node/node_modules/vite": { -+ "version": "5.4.20", -+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz", -+ "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", ++ "version": "5.4.21", ++ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", ++ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", @@ -13352,37 +12765,6 @@ + } + } + }, -+ "node_modules/vite/node_modules/fdir": { -+ "version": "6.5.0", -+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", -+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", -+ "dev": true, -+ "license": "MIT", -+ "engines": { -+ "node": ">=12.0.0" -+ }, -+ "peerDependencies": { -+ "picomatch": "^3 || ^4" -+ }, -+ "peerDependenciesMeta": { -+ "picomatch": { -+ "optional": true -+ } -+ } -+ }, -+ "node_modules/vite/node_modules/picomatch": { -+ "version": "4.0.3", -+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", -+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", -+ "dev": true, -+ "license": "MIT", -+ "engines": { -+ "node": ">=12" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/jonschlinkert" -+ } -+ }, + "node_modules/vitest": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", @@ -13854,10 +13236,16 @@ + "@esbuild/win32-x64": "0.21.5" + } + }, ++ "node_modules/vitest/node_modules/pathe": { ++ "version": "1.1.2", ++ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", ++ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", ++ "license": "MIT" ++ }, + "node_modules/vitest/node_modules/vite": { -+ "version": "5.4.20", -+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz", -+ "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", ++ "version": "5.4.21", ++ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", ++ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", @@ -13950,15 +13338,6 @@ + "@zxing/text-encoding": "0.9.0" + } + }, -+ "node_modules/web-streams-polyfill": { -+ "version": "3.3.3", -+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", -+ "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", -+ "license": "MIT", -+ "engines": { -+ "node": ">= 8" -+ } -+ }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -13977,6 +13356,18 @@ + "node": ">=12" + } + }, ++ "node_modules/whatwg-encoding/node_modules/iconv-lite": { ++ "version": "0.6.3", ++ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", ++ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", ++ "license": "MIT", ++ "dependencies": { ++ "safer-buffer": ">= 2.1.2 < 3.0.0" ++ }, ++ "engines": { ++ "node": ">=0.10.0" ++ } ++ }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", @@ -14011,43 +13402,6 @@ + "node": ">= 8" + } + }, -+ "node_modules/which-boxed-primitive": { -+ "version": "1.1.1", -+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", -+ "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", -+ "license": "MIT", -+ "dependencies": { -+ "is-bigint": "^1.1.0", -+ "is-boolean-object": "^1.2.1", -+ "is-number-object": "^1.1.1", -+ "is-string": "^1.1.1", -+ "is-symbol": "^1.1.1" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, -+ "node_modules/which-collection": { -+ "version": "1.0.2", -+ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", -+ "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", -+ "license": "MIT", -+ "dependencies": { -+ "is-map": "^2.0.3", -+ "is-set": "^2.0.3", -+ "is-weakmap": "^2.0.2", -+ "is-weakset": "^2.0.3" -+ }, -+ "engines": { -+ "node": ">= 0.4" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", @@ -14086,24 +13440,6 @@ + } + }, + "node_modules/wrap-ansi": { -+ "version": "8.1.0", -+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", -+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", -+ "license": "MIT", -+ "dependencies": { -+ "ansi-styles": "^6.1.0", -+ "string-width": "^5.0.1", -+ "strip-ansi": "^7.0.1" -+ }, -+ "engines": { -+ "node": ">=12" -+ }, -+ "funding": { -+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" -+ } -+ }, -+ "node_modules/wrap-ansi-cjs": { -+ "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", @@ -14120,62 +13456,6 @@ + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, -+ "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { -+ "version": "5.0.1", -+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", -+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", -+ "license": "MIT", -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { -+ "version": "4.3.0", -+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", -+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", -+ "license": "MIT", -+ "dependencies": { -+ "color-convert": "^2.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ }, -+ "funding": { -+ "url": "https://github.com/chalk/ansi-styles?sponsor=1" -+ } -+ }, -+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "license": "MIT" -+ }, -+ "node_modules/wrap-ansi-cjs/node_modules/string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "license": "MIT", -+ "dependencies": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { -+ "version": "6.0.1", -+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", -+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", -+ "license": "MIT", -+ "dependencies": { -+ "ansi-regex": "^5.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -14236,17 +13516,12 @@ + "node": ">=10" + } + }, -+ "node_modules/yaml": { -+ "version": "2.8.1", -+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", -+ "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", -+ "license": "ISC", -+ "bin": { -+ "yaml": "bin.mjs" -+ }, -+ "engines": { -+ "node": ">= 14.6" -+ } ++ "node_modules/yallist": { ++ "version": "3.1.1", ++ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", ++ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", ++ "dev": true, ++ "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", @@ -14275,51 +13550,10 @@ + "node": ">=12" + } + }, -+ "node_modules/yargs/node_modules/ansi-regex": { -+ "version": "5.0.1", -+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", -+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", -+ "license": "MIT", -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/yargs/node_modules/emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "license": "MIT" -+ }, -+ "node_modules/yargs/node_modules/string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "license": "MIT", -+ "dependencies": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/yargs/node_modules/strip-ansi": { -+ "version": "6.0.1", -+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", -+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", -+ "license": "MIT", -+ "dependencies": { -+ "ansi-regex": "^5.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, + "node_modules/yocto-queue": { -+ "version": "1.2.1", -+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", -+ "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", ++ "version": "1.2.2", ++ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", ++ "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" diff --git a/opensaas-sh/app_diff/package.json.diff b/opensaas-sh/app_diff/package.json.diff index 5842a669b..a4f599126 100644 --- a/opensaas-sh/app_diff/package.json.diff +++ b/opensaas-sh/app_diff/package.json.diff @@ -1,9 +1,9 @@ --- template/app/package.json +++ opensaas-sh/app/package.json -@@ -1,6 +1,11 @@ - { - "name": "opensaas", - "type": "module", +@@ -5,14 +5,17 @@ + ".wasp/build/*", + ".wasp/out/*" + ], + "scripts": { + "deploy": "REACT_APP_GOOGLE_ANALYTICS_ID=G-H3LSJCK95H wasp deploy fly deploy", + "env:pull": "npx dotenv-vault@latest pull development .env.server", @@ -12,10 +12,18 @@ "dependencies": { "@aws-sdk/client-s3": "^3.523.0", "@aws-sdk/s3-presigned-post": "^3.750.0", -@@ -36,6 +41,7 @@ - "react-dom": "^18.2.0", + "@aws-sdk/s3-request-presigner": "^3.523.0", + "@google-analytics/data": "4.1.0", + "@hookform/resolvers": "^5.1.1", +- "@lemonsqueezy/lemonsqueezy.js": "^3.2.0", +- "@polar-sh/sdk": "^0.34.3", + "@radix-ui/react-accordion": "^1.2.11", + "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-checkbox": "^1.3.2", +@@ -38,6 +41,7 @@ + "react-apexcharts": "1.4.1", + "react-dom": "^19.2.1", "react-hook-form": "^7.60.0", - "react-hot-toast": "^2.4.1", + "react-icons": "^5.5.0", "react-router-dom": "^6.26.2", "stripe": "18.1.0", diff --git a/template/app/public/fonts/Satoshi-Black.woff2 b/opensaas-sh/app_diff/public/fonts/Satoshi-Black.woff2.copy similarity index 100% rename from template/app/public/fonts/Satoshi-Black.woff2 rename to opensaas-sh/app_diff/public/fonts/Satoshi-Black.woff2.copy diff --git a/template/app/public/fonts/Satoshi-Bold.woff2 b/opensaas-sh/app_diff/public/fonts/Satoshi-Bold.woff2.copy similarity index 100% rename from template/app/public/fonts/Satoshi-Bold.woff2 rename to opensaas-sh/app_diff/public/fonts/Satoshi-Bold.woff2.copy diff --git a/template/app/public/fonts/Satoshi-Light.woff2 b/opensaas-sh/app_diff/public/fonts/Satoshi-Light.woff2.copy similarity index 100% rename from template/app/public/fonts/Satoshi-Light.woff2 rename to opensaas-sh/app_diff/public/fonts/Satoshi-Light.woff2.copy diff --git a/template/app/public/fonts/Satoshi-Medium.woff2 b/opensaas-sh/app_diff/public/fonts/Satoshi-Medium.woff2.copy similarity index 100% rename from template/app/public/fonts/Satoshi-Medium.woff2 rename to opensaas-sh/app_diff/public/fonts/Satoshi-Medium.woff2.copy diff --git a/template/app/public/fonts/Satoshi-Regular.woff2 b/opensaas-sh/app_diff/public/fonts/Satoshi-Regular.woff2.copy similarity index 100% rename from template/app/public/fonts/Satoshi-Regular.woff2 rename to opensaas-sh/app_diff/public/fonts/Satoshi-Regular.woff2.copy diff --git a/opensaas-sh/app_diff/src/admin/dashboards/analytics/RevenueAndProfitChart.tsx.diff b/opensaas-sh/app_diff/src/admin/dashboards/analytics/RevenueAndProfitChart.tsx.diff new file mode 100644 index 000000000..350bb69f4 --- /dev/null +++ b/opensaas-sh/app_diff/src/admin/dashboards/analytics/RevenueAndProfitChart.tsx.diff @@ -0,0 +1,11 @@ +--- template/app/src/admin/dashboards/analytics/RevenueAndProfitChart.tsx ++++ opensaas-sh/app/src/admin/dashboards/analytics/RevenueAndProfitChart.tsx +@@ -11,7 +11,7 @@ + }, + colors: ["#3C50E0", "#80CAEE"], + chart: { +- fontFamily: "system-ui, sans-serif", ++ fontFamily: "Satoshi, system-ui, sans-serif", + height: 335, + type: "area", + dropShadow: { diff --git a/opensaas-sh/app_diff/src/analytics/stats.ts.diff b/opensaas-sh/app_diff/src/analytics/stats.ts.diff index 58361dfbe..60d76725f 100644 --- a/opensaas-sh/app_diff/src/analytics/stats.ts.diff +++ b/opensaas-sh/app_diff/src/analytics/stats.ts.diff @@ -1,23 +1,26 @@ --- template/app/src/analytics/stats.ts +++ opensaas-sh/app/src/analytics/stats.ts -@@ -1,15 +1,13 @@ +@@ -1,18 +1,13 @@ -import { listOrders } from "@lemonsqueezy/lemonsqueezy.js"; import Stripe from "stripe"; import { type DailyStats } from "wasp/entities"; import { type DailyStatsJob } from "wasp/server/jobs"; +import { SubscriptionStatus } from "../payment/plans"; - import { stripe } from "../payment/stripe/stripeClient"; + import { stripeClient } from "../payment/stripe/stripeClient"; import { getDailyPageViews, getSources, } from "./providers/plausibleAnalyticsUtils"; // import { getDailyPageViews, getSources } from './providers/googleAnalyticsUtils'; +-import { OrderStatus } from "@polar-sh/sdk/models/components/orderstatus.js"; -import { paymentProcessor } from "../payment/paymentProcessor"; -import { SubscriptionStatus } from "../payment/plans"; +-import { polarClient } from "../payment/polar/polarClient"; +-import { assertUnreachable } from "../shared/utils"; export type DailyStatsProps = { dailyStats?: DailyStats; -@@ -52,19 +50,7 @@ +@@ -55,20 +50,7 @@ paidUserDelta -= yesterdaysStats.paidUserCount; } @@ -29,16 +32,17 @@ - case "lemonsqueezy": - totalRevenue = await fetchTotalLemonSqueezyRevenue(); - break; +- case "polar": +- totalRevenue = await fetchTotalPolarRevenue(); +- break; - default: -- throw new Error( -- `Unsupported payment processor: ${paymentProcessor.id}`, -- ); +- assertUnreachable(paymentProcessor.id); - } + let totalRevenue = await fetchTotalStripeRevenue(); const { totalViews, prevDayViewsChangePercent } = await getDailyPageViews(); -@@ -176,38 +162,3 @@ +@@ -181,59 +163,3 @@ // Revenue is in cents so we convert to dollars (or your main currency unit) return totalRevenue / 100; } @@ -77,3 +81,24 @@ - throw error; - } -} +- +-async function fetchTotalPolarRevenue(): Promise { +- let totalRevenue = 0; +- +- const result = await polarClient.orders.list({ +- limit: 100, +- }); +- +- for await (const page of result) { +- const orders = page.result.items || []; +- +- for (const order of orders) { +- if (order.status === OrderStatus.Paid && order.totalAmount > 0) { +- totalRevenue += order.totalAmount; +- } +- } +- } +- +- // Revenue is in cents so we convert to dollars +- return totalRevenue / 100; +-} diff --git a/opensaas-sh/app_diff/src/client/App.tsx.diff b/opensaas-sh/app_diff/src/client/App.tsx.diff new file mode 100644 index 000000000..b68647b72 --- /dev/null +++ b/opensaas-sh/app_diff/src/client/App.tsx.diff @@ -0,0 +1,52 @@ +--- template/app/src/client/App.tsx ++++ opensaas-sh/app/src/client/App.tsx +@@ -1,4 +1,4 @@ +-import { useEffect, useMemo } from "react"; ++import { useEffect, useMemo, useRef } from "react"; + import { Outlet, useLocation } from "react-router-dom"; + import { routes } from "wasp/client/router"; + import { Toaster } from "../client/components/ui/toaster"; +@@ -18,7 +18,7 @@ + const location = useLocation(); + const isMarketingPage = useMemo(() => { + return ( +- location.pathname === "/" || location.pathname.startsWith("/pricing") ++ location.pathname === "/" + ); + }, [location]); + +@@ -37,14 +37,33 @@ + return location.pathname.startsWith("/admin"); + }, [location]); + ++ const resizeObserverRef = useRef(null); ++ + useEffect(() => { + if (location.hash) { ++ console.log('location ..', location.hash) + const id = location.hash.replace("#", ""); + const element = document.getElementById(id); ++ + if (element) { +- element.scrollIntoView(); ++ // Scroll immediately but watches for size changes (async content loading) and re-scrolls ++ element.scrollIntoView({ behavior: "smooth" }); ++ console.log('element scrolled ..', element) ++ resizeObserverRef.current = new ResizeObserver(() => { ++ element.scrollIntoView({ behavior: "smooth" }); ++ }); ++ resizeObserverRef.current.observe(element); ++ } ++ } else if (location.pathname === "/") { ++ // Only scroll to top when navigating TO homepage from another page ++ if (window.scrollY > 0) { ++ window.scrollTo({ top: 0, behavior: "smooth" }); + } + } ++ ++ return () => { ++ resizeObserverRef.current?.disconnect(); ++ }; + }, [location]); + + return ( diff --git a/opensaas-sh/app_diff/src/client/Main.css.diff b/opensaas-sh/app_diff/src/client/Main.css.diff index 819a22420..774270a25 100644 --- a/opensaas-sh/app_diff/src/client/Main.css.diff +++ b/opensaas-sh/app_diff/src/client/Main.css.diff @@ -1,6 +1,6 @@ --- template/app/src/client/Main.css +++ opensaas-sh/app/src/client/Main.css -@@ -56,6 +56,23 @@ +@@ -56,6 +56,64 @@ .border-gradient-primary > * { background: hsl(var(--background)); } @@ -21,13 +21,7 @@ + hsl(var(--card)) 100% + ); + } - } - - /* Here is an example of how to add a custom font. -@@ -63,6 +80,16 @@ - * They are defined first here, then need to be referenced in the tailwind.config.js file - * under `theme.extend.fontFamily`, and then can be used as a tailwind class, e.g. className='font-satoshi'. - */ ++} + +/* Satoshi Font Family */ +@font-face { @@ -38,13 +32,14 @@ + font-display: swap; +} + - @font-face { - font-family: "Satoshi"; - src: url("/fonts/Satoshi-Regular.woff2") format("woff2"); -@@ -71,6 +98,30 @@ - font-display: swap; - } - ++@font-face { ++ font-family: "Satoshi"; ++ src: url("/fonts/Satoshi-Regular.woff2") format("woff2"); ++ font-weight: normal; ++ font-style: normal; ++ font-display: swap; ++} ++ +@font-face { + font-family: "Satoshi"; + src: url("/fonts/Satoshi-Medium.woff2") format("woff2"); @@ -67,12 +62,10 @@ + font-weight: 900; + font-style: normal; + font-display: swap; -+} -+ - /* third-party libraries CSS */ + } - .tableCheckbox:checked ~ div span { -@@ -189,4 +240,22 @@ + /* third-party libraries CSS */ +@@ -176,4 +234,22 @@ body { @apply bg-background text-foreground; } diff --git a/opensaas-sh/app_diff/src/client/components/NavBar/NavBar.tsx.diff b/opensaas-sh/app_diff/src/client/components/NavBar/NavBar.tsx.diff index d56212643..af2acebd9 100644 --- a/opensaas-sh/app_diff/src/client/components/NavBar/NavBar.tsx.diff +++ b/opensaas-sh/app_diff/src/client/components/NavBar/NavBar.tsx.diff @@ -4,13 +4,13 @@ import { Link as ReactRouterLink } from "react-router-dom"; import { useAuth } from "wasp/client/auth"; import { Link as WaspRouterLink, routes } from "wasp/client/router"; -+import { Button } from "../../../components/ui/button"; ++import { Button } from "../../../client/components/ui/button"; import { Sheet, SheetContent, @@ -17,6 +18,7 @@ - import { useIsLandingPage } from "../../hooks/useIsLandingPage"; import logo from "../../static/logo.webp"; + import { cn } from "../../utils"; import DarkModeSwitcher from "../DarkModeSwitcher"; +import RepoInfo from "../RepoInfo"; import { Announcement } from "./Announcement"; diff --git a/opensaas-sh/app_diff/src/client/components/NavBar/constants.ts.diff b/opensaas-sh/app_diff/src/client/components/NavBar/constants.ts.diff index 9cf811856..3e4d4979c 100644 --- a/opensaas-sh/app_diff/src/client/components/NavBar/constants.ts.diff +++ b/opensaas-sh/app_diff/src/client/components/NavBar/constants.ts.diff @@ -1,10 +1,19 @@ --- template/app/src/client/components/NavBar/constants.ts +++ opensaas-sh/app/src/client/components/NavBar/constants.ts -@@ -9,7 +9,6 @@ +@@ -8,13 +8,14 @@ + ]; export const marketingNavigationItems: NavigationItem[] = [ - { name: "Features", to: "/#features" }, +- { name: "Features", to: "/#features" }, - { name: "Pricing", to: routes.PricingPageRoute.to }, ++ { name: "Features", to: "/#auth-feature" }, ++ { name: "Roadmap", to: "/#roadmap" }, ...staticNavigationItems, ] as const; + export const demoNavigationitems: NavigationItem[] = [ + { name: "AI Scheduler", to: routes.DemoAppRoute.to }, + { name: "File Upload", to: routes.FileUploadRoute.to }, ++ { name: "Pricing", to: routes.PricingPageRoute.to }, + ...staticNavigationItems, + ] as const; diff --git a/opensaas-sh/app_diff/src/client/components/RepoInfo.tsx.diff b/opensaas-sh/app_diff/src/client/components/RepoInfo.tsx.diff index cd70343db..20716848f 100644 --- a/opensaas-sh/app_diff/src/client/components/RepoInfo.tsx.diff +++ b/opensaas-sh/app_diff/src/client/components/RepoInfo.tsx.diff @@ -3,8 +3,8 @@ @@ -0,0 +1,48 @@ +import { useEffect, useState } from "react"; +import { FaGithub } from "react-icons/fa"; -+import { Button } from "../../components/ui/button"; -+import { formatNumber } from "../../lib/utils"; ++import { Button } from "../../client/components/ui/button"; ++import { formatNumber } from "../utils"; + +const RepoInfo = () => { + const [repoInfo, setRepoInfo] = useState(null); diff --git a/opensaas-sh/app_diff/src/components/ui/button.tsx.diff b/opensaas-sh/app_diff/src/client/components/ui/button.tsx.diff similarity index 60% rename from opensaas-sh/app_diff/src/components/ui/button.tsx.diff rename to opensaas-sh/app_diff/src/client/components/ui/button.tsx.diff index 5b7b20d18..e3afc7673 100644 --- a/opensaas-sh/app_diff/src/components/ui/button.tsx.diff +++ b/opensaas-sh/app_diff/src/client/components/ui/button.tsx.diff @@ -1,12 +1,6 @@ ---- template/app/src/components/ui/button.tsx -+++ opensaas-sh/app/src/components/ui/button.tsx -@@ -5,26 +5,31 @@ - import { cn } from "../../lib/utils"; - - const buttonVariants = cva( -- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", -+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", - { +--- template/app/src/client/components/ui/button.tsx ++++ opensaas-sh/app/src/client/components/ui/button.tsx +@@ -10,21 +10,26 @@ variants: { variant: { default: diff --git a/opensaas-sh/app_diff/src/components/ui/card.tsx.diff b/opensaas-sh/app_diff/src/client/components/ui/card.tsx.diff similarity index 87% rename from opensaas-sh/app_diff/src/components/ui/card.tsx.diff rename to opensaas-sh/app_diff/src/client/components/ui/card.tsx.diff index 6ac414895..9db6ecdcc 100644 --- a/opensaas-sh/app_diff/src/components/ui/card.tsx.diff +++ b/opensaas-sh/app_diff/src/client/components/ui/card.tsx.diff @@ -1,5 +1,5 @@ ---- template/app/src/components/ui/card.tsx -+++ opensaas-sh/app/src/components/ui/card.tsx +--- template/app/src/client/components/ui/card.tsx ++++ opensaas-sh/app/src/client/components/ui/card.tsx @@ -12,7 +12,11 @@ accent: "bg-card-accent text-card-accent-foreground hover:scale-[1.02]", faded: "text-card-faded-foreground scale-95 opacity-50", diff --git a/opensaas-sh/app_diff/src/lib/utils.ts.diff b/opensaas-sh/app_diff/src/client/utils.ts.diff similarity index 80% rename from opensaas-sh/app_diff/src/lib/utils.ts.diff rename to opensaas-sh/app_diff/src/client/utils.ts.diff index 52d923a5e..e8e0cfd77 100644 --- a/opensaas-sh/app_diff/src/lib/utils.ts.diff +++ b/opensaas-sh/app_diff/src/client/utils.ts.diff @@ -1,5 +1,5 @@ ---- template/app/src/lib/utils.ts -+++ opensaas-sh/app/src/lib/utils.ts +--- template/app/src/client/utils.ts ++++ opensaas-sh/app/src/client/utils.ts @@ -4,3 +4,12 @@ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); diff --git a/opensaas-sh/app_diff/src/file-upload/fileUploading.ts.diff b/opensaas-sh/app_diff/src/file-upload/fileUploading.ts.diff new file mode 100644 index 000000000..ec5daf05c --- /dev/null +++ b/opensaas-sh/app_diff/src/file-upload/fileUploading.ts.diff @@ -0,0 +1,30 @@ +--- template/app/src/file-upload/fileUploading.ts ++++ opensaas-sh/app/src/file-upload/fileUploading.ts +@@ -1,5 +1,7 @@ +-import axios from "axios"; +-import { ALLOWED_FILE_TYPES, MAX_FILE_SIZE_BYTES } from "./validation"; ++import type { User } from 'wasp/entities'; ++import axios from 'axios'; ++import { ALLOWED_FILE_TYPES, MAX_FILE_SIZE_BYTES } from './validation'; ++import { PrismaClient } from '@prisma/client'; + + type AllowedFileTypes = (typeof ALLOWED_FILE_TYPES)[number]; + export type FileWithValidType = File & { type: AllowedFileTypes }; +@@ -58,3 +60,17 @@ + function isFileWithAllowedFileType(file: File): file is FileWithValidType { + return ALLOWED_FILE_TYPES.includes(file.type as AllowedFileTypes); + } ++ ++export async function checkIfUserHasReachedFileUploadLimit({ userId, prismaFileDelegate }: { userId: User['id']; prismaFileDelegate: PrismaClient['file'] }) { ++ const numberOfFilesByUser = await prismaFileDelegate.count({ ++ where: { ++ user: { ++ id: userId, ++ }, ++ }, ++ }); ++ if (numberOfFilesByUser >= 2) { ++ return true; ++ } ++ return false; ++} diff --git a/opensaas-sh/app_diff/src/file-upload/operations.ts.diff b/opensaas-sh/app_diff/src/file-upload/operations.ts.diff index 1d87c9447..d34ceb568 100644 --- a/opensaas-sh/app_diff/src/file-upload/operations.ts.diff +++ b/opensaas-sh/app_diff/src/file-upload/operations.ts.diff @@ -1,24 +1,32 @@ --- template/app/src/file-upload/operations.ts +++ opensaas-sh/app/src/file-upload/operations.ts -@@ -44,6 +44,21 @@ - userId: context.user.id, - }); +@@ -6,7 +6,8 @@ + type DeleteFile, + type GetAllFilesByUser, + type GetDownloadFileSignedURL, +-} from "wasp/server/operations"; ++} from 'wasp/server/operations'; ++import { checkIfUserHasReachedFileUploadLimit } from './fileUploading'; -+ const numberOfFilesByUser = await context.entities.File.count({ -+ where: { -+ user: { -+ id: context.user.id, -+ }, -+ }, + import * as z from "zod"; + import { ensureArgsSchemaOrThrowHttpError } from "../server/validation"; +@@ -37,10 +38,15 @@ + throw new HttpError(401); + } + +- const { fileType, fileName } = ensureArgsSchemaOrThrowHttpError( +- createFileInputSchema, +- rawArgs, +- ); ++ const userFileLimitReached = await checkIfUserHasReachedFileUploadLimit({ ++ userId: context.user.id, ++ prismaFileDelegate: context.entities.File, + }); -+ -+ if (numberOfFilesByUser >= 2) { -+ throw new HttpError( -+ 403, -+ "Thanks for trying Open SaaS. This demo only allows 2 file uploads per user.", -+ ); ++ if (userFileLimitReached) { ++ throw new HttpError(403, 'This demo only allows 2 file uploads per user.'); + } + - await context.entities.File.create({ - data: { - name: fileName, ++ const { fileType, fileName } = ensureArgsSchemaOrThrowHttpError(createFileInputSchema, rawArgs); + + return await getUploadFileSignedURLFromS3({ + fileType, diff --git a/opensaas-sh/app_diff/src/file-upload/workers.ts.diff b/opensaas-sh/app_diff/src/file-upload/workers.ts.diff new file mode 100644 index 000000000..4288cc9d8 --- /dev/null +++ b/opensaas-sh/app_diff/src/file-upload/workers.ts.diff @@ -0,0 +1,37 @@ +--- template/app/src/file-upload/workers.ts ++++ opensaas-sh/app/src/file-upload/workers.ts +@@ -0,0 +1,34 @@ ++import type { DeleteFilesJob } from 'wasp/server/jobs'; ++import { deleteFileFromS3 } from './s3Utils'; ++ ++export const deleteFilesJob: DeleteFilesJob = async (_args, context) => { ++ const dayInMiliseconds = 1000 * 60 * 60 * 24; ++ const sevenDaysAgo = Date.now() - 7 * dayInMiliseconds; ++ const filesToDelete = await context.entities.File.findMany({ ++ where: { ++ createdAt: { ++ lt: new Date(sevenDaysAgo), ++ }, ++ }, ++ select: { s3Key: true, id: true }, ++ }); ++ ++ const deletionResults = await Promise.allSettled( ++ filesToDelete.map(async (file) => { ++ await deleteFileFromS3({ s3Key: file.s3Key }); ++ return file.id; ++ }) ++ ); ++ ++ const successfullyDeletedFromS3Ids = deletionResults ++ .filter((result) => result.status === 'fulfilled') ++ .map((result) => result.value); ++ ++ const deletedFiles = await context.entities.File.deleteMany({ ++ where: { ++ id: { in: successfullyDeletedFromS3Ids }, ++ }, ++ }); ++ ++ console.log(`Deleted ${deletedFiles.count} files`); ++}; diff --git a/opensaas-sh/app_diff/src/landing-page/LandingPage.tsx.diff b/opensaas-sh/app_diff/src/landing-page/LandingPage.tsx.diff index 3fd1f59d8..e53021d76 100644 --- a/opensaas-sh/app_diff/src/landing-page/LandingPage.tsx.diff +++ b/opensaas-sh/app_diff/src/landing-page/LandingPage.tsx.diff @@ -5,15 +5,16 @@ import ExamplesCarousel from "./components/ExamplesCarousel"; import FAQ from "./components/FAQ"; import FeaturesGrid from "./components/FeaturesGrid"; -@@ -11,7 +12,6 @@ +@@ -11,7 +12,7 @@ footerNavigation, testimonials, } from "./contentSections"; -import AIReady from "./ExampleHighlightedFeature"; ++import Roadmap from "./components/Roadmap"; export default function LandingPage() { return ( -@@ -19,6 +19,9 @@ +@@ -19,8 +20,12 @@
@@ -22,4 +23,7 @@ + ++ + +
diff --git a/opensaas-sh/app_diff/src/landing-page/components/Examples/Auth.tsx.diff b/opensaas-sh/app_diff/src/landing-page/components/Examples/Auth.tsx.diff index fbb85e4bb..32b3a44b5 100644 --- a/opensaas-sh/app_diff/src/landing-page/components/Examples/Auth.tsx.diff +++ b/opensaas-sh/app_diff/src/landing-page/components/Examples/Auth.tsx.diff @@ -1,11 +1,11 @@ --- template/app/src/landing-page/components/Examples/Auth.tsx +++ opensaas-sh/app/src/landing-page/components/Examples/Auth.tsx -@@ -0,0 +1,163 @@ +@@ -0,0 +1,164 @@ +import { useState } from "react"; +import { FaDiscord, FaGithub, FaGoogle, FaSlack } from "react-icons/fa"; -+import { Button } from "../../../components/ui/button"; -+import { Card, CardContent, CardHeader } from "../../../components/ui/card"; -+import { Input } from "../../../components/ui/input"; ++import { Button } from "../../../client/components/ui/button"; ++import { Card, CardContent, CardHeader } from "../../../client/components/ui/card"; ++import { Input } from "../../../client/components/ui/input"; +import { DocsUrl } from "../../../shared/common"; +import HighlightedFeature from "../HighlightedFeature"; + @@ -55,6 +55,7 @@ + + return ( + ++
+ + -+
++
+
+
+
diff --git a/opensaas-sh/app_diff/src/landing-page/components/Hero/Orbit.tsx.diff b/opensaas-sh/app_diff/src/landing-page/components/Hero/Orbit.tsx.diff index 4f10a71fc..d55e8f3cf 100644 --- a/opensaas-sh/app_diff/src/landing-page/components/Hero/Orbit.tsx.diff +++ b/opensaas-sh/app_diff/src/landing-page/components/Hero/Orbit.tsx.diff @@ -9,7 +9,7 @@ +import stripeLogo from "../../../client/static/logos/stripe-light.webp"; +import tailwindLogoDark from "../../../client/static/logos/tailwind-dark.webp"; +import tailwindLogo from "../../../client/static/logos/tailwind-light.webp"; -+import { cn } from "../../../lib/utils"; ++import { cn } from "../../../client/utils"; +import AstroLogo from "../../logos/AstroLogo"; +import OpenAILogo from "../../logos/OpenAILogo"; +import PrismaLogo from "../../logos/PrismaLogo"; diff --git a/opensaas-sh/app_diff/src/landing-page/components/HighlightedFeature.tsx.diff b/opensaas-sh/app_diff/src/landing-page/components/HighlightedFeature.tsx.diff index 06e500e73..af9616065 100644 --- a/opensaas-sh/app_diff/src/landing-page/components/HighlightedFeature.tsx.diff +++ b/opensaas-sh/app_diff/src/landing-page/components/HighlightedFeature.tsx.diff @@ -1,6 +1,12 @@ --- template/app/src/landing-page/components/HighlightedFeature.tsx +++ opensaas-sh/app/src/landing-page/components/HighlightedFeature.tsx -@@ -6,6 +6,8 @@ +@@ -1,11 +1,14 @@ + import { cn } from "../../client/utils"; + + interface FeatureProps { ++ id?: string; + name: string; + description: string | React.ReactNode; direction?: "row" | "row-reverse"; highlightedComponent: React.ReactNode; tilt?: "left" | "right"; @@ -9,7 +15,13 @@ } /** -@@ -18,6 +20,8 @@ +@@ -13,11 +16,14 @@ + * Shows text description on one side, and whatever component you want to show on the other side to demonstrate the functionality. + */ + const HighlightedFeature = ({ ++ id, + name, + description, direction = "row", highlightedComponent, tilt, @@ -18,12 +30,14 @@ }: FeatureProps) => { const tiltToClass: Record["tilt"], string> = { left: "rotate-1", -@@ -27,12 +31,14 @@ +@@ -26,13 +32,16 @@ + return (
@@ -35,7 +49,7 @@ {typeof description === "string" ? (

{description}

) : ( -@@ -43,6 +49,7 @@ +@@ -43,6 +52,7 @@ className={cn( "my-10 flex w-full flex-1 items-center justify-center transition-transform duration-300 ease-in-out", tilt && tiltToClass[tilt], diff --git a/opensaas-sh/app_diff/src/landing-page/components/Roadmap.tsx.diff b/opensaas-sh/app_diff/src/landing-page/components/Roadmap.tsx.diff new file mode 100644 index 000000000..7dc13f18e --- /dev/null +++ b/opensaas-sh/app_diff/src/landing-page/components/Roadmap.tsx.diff @@ -0,0 +1,94 @@ +--- template/app/src/landing-page/components/Roadmap.tsx ++++ opensaas-sh/app/src/landing-page/components/Roadmap.tsx +@@ -0,0 +1,91 @@ ++import { GithubEpicStatus } from "../operations"; ++ ++import { GitPullRequestArrow, Loader2 } from "lucide-react"; ++import { Link } from "react-router-dom"; ++import { getGithubRoadmap, useQuery } from "wasp/client/operations"; ++import { RoadmapStatusColumn } from "./RoadmapStatusColumn"; ++ ++export default function Roadmap() { ++ const { data: epics, isLoading, error } = useQuery(getGithubRoadmap); ++ ++ if (isLoading) { ++ return ( ++ ++
++ ++
++
++ ); ++ } ++ ++ if (error) { ++ return ( ++ ++
++ Failed to load roadmap ++
++
++ ); ++ } ++ ++ if (!epics || epics.length === 0) { ++ return ( ++ ++
++ ++

No roadmap items yet.

++

++ Add your idea{" "} ++ ++ here ++ ++ . ++

++
++
++ ); ++ } ++ ++ return ( ++ ++
++ {Object.values(GithubEpicStatus).map((status) => ( ++ e.status === status)} ++ /> ++ ))} ++
++
++ ); ++} ++ ++function RoadmapWrapper({ children }: { children?: React.ReactNode }) { ++ return ( ++
++
++ ++ {children} ++
++
++ ); ++} ++ ++function RoadmapHeader() { ++ return ( ++
++

++ The Roadmap ++

++

++ High-level topics we are planning and working on. ++

++
++ ); ++} diff --git a/opensaas-sh/app_diff/src/landing-page/components/RoadmapEpicCard.tsx.diff b/opensaas-sh/app_diff/src/landing-page/components/RoadmapEpicCard.tsx.diff new file mode 100644 index 000000000..2973016eb --- /dev/null +++ b/opensaas-sh/app_diff/src/landing-page/components/RoadmapEpicCard.tsx.diff @@ -0,0 +1,94 @@ +--- template/app/src/landing-page/components/RoadmapEpicCard.tsx ++++ opensaas-sh/app/src/landing-page/components/RoadmapEpicCard.tsx +@@ -0,0 +1,91 @@ ++import { Plus } from "lucide-react"; ++import { cn } from "../../client/utils"; ++import type { GithubEpic } from "../operations"; ++import { GithubEpicStatus } from "../operations"; ++ ++type RoadmapEpicCardProps = { ++ epic: GithubEpic; ++}; ++ ++export function RoadmapEpicCard({ epic }: RoadmapEpicCardProps) { ++ const progress = ++ epic.totalIssues > 0 ++ ? Math.round((epic.doneIssues / epic.totalIssues) * 100) ++ : 0; ++ const progressColor = getProgressBarColor(epic.status); ++ const hoverBorderColor = getHoverBorderColor(epic.status); ++ ++ return ( ++ ++

++ {epic.name} ++

++ ++
++
++ Progress ++ ++ {progress}% ({epic.doneIssues}/{epic.totalIssues}) ++ ++
++
++
++
++
++
++ ); ++} ++ ++export const RoadmapEpicAddIssueCard = () => { ++ return ( ++ ++ ++ ++ ); ++}; ++ ++const getProgressBarColor = (status: GithubEpicStatus) => { ++ switch (status) { ++ case GithubEpicStatus.Ideas: ++ return "bg-blue-500"; ++ case GithubEpicStatus.Planned: ++ return "bg-green-500"; ++ case GithubEpicStatus.InProgress: ++ return "bg-yellow-500"; ++ case GithubEpicStatus.Done: ++ return "bg-purple-500"; ++ default: ++ return "bg-gray-500"; ++ } ++}; ++ ++const getHoverBorderColor = (status: GithubEpicStatus) => { ++ switch (status) { ++ case GithubEpicStatus.Ideas: ++ return "hover:border-blue-400 dark:hover:border-blue-600"; ++ case GithubEpicStatus.Planned: ++ return "hover:border-green-400 dark:hover:border-green-600"; ++ case GithubEpicStatus.InProgress: ++ return "hover:border-yellow-400 dark:hover:border-yellow-600"; ++ case GithubEpicStatus.Done: ++ return "hover:border-purple-400 dark:hover:border-purple-600"; ++ default: ++ return "hover:border-gray-400 dark:hover:border-gray-600"; ++ } ++}; diff --git a/opensaas-sh/app_diff/src/landing-page/components/RoadmapStatusColumn.tsx.diff b/opensaas-sh/app_diff/src/landing-page/components/RoadmapStatusColumn.tsx.diff new file mode 100644 index 000000000..465de435f --- /dev/null +++ b/opensaas-sh/app_diff/src/landing-page/components/RoadmapStatusColumn.tsx.diff @@ -0,0 +1,96 @@ +--- template/app/src/landing-page/components/RoadmapStatusColumn.tsx ++++ opensaas-sh/app/src/landing-page/components/RoadmapStatusColumn.tsx +@@ -0,0 +1,93 @@ ++import type { GithubEpic } from "../operations"; ++import { GithubEpicStatus } from "../operations"; ++import { cn } from "../../client/utils"; ++import { RoadmapEpicCard } from "./RoadmapEpicCard"; ++import { RoadmapEpicAddIssueCard } from "./RoadmapEpicCard"; ++ ++type RoadmapStatusColumnProps = { ++ status: GithubEpicStatus; ++ epics: GithubEpic[]; ++}; ++ ++export function RoadmapStatusColumn({ status, epics }: RoadmapStatusColumnProps) { ++ const borderColor = getBorderColor(status); ++ const statusBgColor = getStatusBgColor(status); ++ const statusTextColor = getStatusTextColor(status); ++ ++ return ( ++
++
++

{status}

++ ({epics.length}) ++
++ ++
++ {epics.map((epic) => ( ++ ++ ))} ++ ++ {epics.length === 0 && ( ++
++ No epics ++
++ )} ++ ++ {status === GithubEpicStatus.Ideas && ( ++ ++ )} ++
++
++ ); ++} ++ ++const getStatusBgColor = (status: GithubEpicStatus) => { ++ switch (status) { ++ case GithubEpicStatus.Ideas: ++ return "bg-blue-100 dark:bg-blue-900/30"; ++ case GithubEpicStatus.Planned: ++ return "bg-green-100 dark:bg-green-900/30"; ++ case GithubEpicStatus.InProgress: ++ return "bg-yellow-100 dark:bg-yellow-900/30"; ++ case GithubEpicStatus.Done: ++ return "bg-purple-100 dark:bg-purple-900/30"; ++ default: ++ return "bg-gray-100 dark:bg-gray-800"; ++ } ++}; ++ ++const getStatusTextColor = (status: GithubEpicStatus) => { ++ switch (status) { ++ case GithubEpicStatus.Ideas: ++ return "text-blue-700 dark:text-blue-300"; ++ case GithubEpicStatus.Planned: ++ return "text-green-700 dark:text-green-300"; ++ case GithubEpicStatus.InProgress: ++ return "text-yellow-700 dark:text-yellow-300"; ++ case GithubEpicStatus.Done: ++ return "text-purple-700 dark:text-purple-300"; ++ default: ++ return "text-gray-700 dark:text-gray-300"; ++ } ++}; ++ ++const getBorderColor = (status: GithubEpicStatus) => { ++ switch (status) { ++ case GithubEpicStatus.Ideas: ++ return "border-blue-200 dark:border-blue-800"; ++ case GithubEpicStatus.Planned: ++ return "border-green-200 dark:border-green-800"; ++ case GithubEpicStatus.InProgress: ++ return "border-yellow-200 dark:border-yellow-800"; ++ case GithubEpicStatus.Done: ++ return "border-purple-200 dark:border-purple-800"; ++ default: ++ return "border-gray-200 dark:border-gray-700"; ++ } ++}; ++ diff --git a/opensaas-sh/app_diff/src/landing-page/contentSections.tsx.diff b/opensaas-sh/app_diff/src/landing-page/contentSections.tsx.diff index 749a36351..5ab6f5502 100644 --- a/opensaas-sh/app_diff/src/landing-page/contentSections.tsx.diff +++ b/opensaas-sh/app_diff/src/landing-page/contentSections.tsx.diff @@ -1,47 +1,57 @@ --- template/app/src/landing-page/contentSections.tsx +++ opensaas-sh/app/src/landing-page/contentSections.tsx -@@ -0,0 +1,263 @@ +@@ -1,4 +1,9 @@ +-import daBoiAvatar from "../client/static/da-boi.webp"; +import { routes } from "wasp/client/router"; +import type { NavigationItem } from "../client/components/NavBar/NavBar"; +import blog from "../client/static/assets/blog.webp"; +import email from "../client/static/assets/email.webp"; +import fileupload from "../client/static/assets/fileupload.webp"; +import ai from "../client/static/assets/openapi.webp"; -+import kivo from "../client/static/examples/kivo.webp"; -+import messync from "../client/static/examples/messync.webp"; -+import microinfluencerClub from "../client/static/examples/microinfluencers.webp"; -+import promptpanda from "../client/static/examples/promptpanda.webp"; -+import reviewradar from "../client/static/examples/reviewradar.webp"; -+import scribeist from "../client/static/examples/scribeist.webp"; -+import searchcraft from "../client/static/examples/searchcraft.webp"; + import kivo from "../client/static/examples/kivo.webp"; + import messync from "../client/static/examples/messync.webp"; + import microinfluencerClub from "../client/static/examples/microinfluencers.webp"; +@@ -6,161 +11,248 @@ + import reviewradar from "../client/static/examples/reviewradar.webp"; + import scribeist from "../client/static/examples/scribeist.webp"; + import searchcraft from "../client/static/examples/searchcraft.webp"; +-import { BlogUrl, DocsUrl } from "../shared/common"; +-import type { GridFeature } from "./components/FeaturesGrid"; +import logo from "../client/static/logo.webp"; +import { BlogUrl, DocsUrl, GithubUrl, WaspUrl } from "../shared/common"; +import { GridFeature } from "./components/FeaturesGrid"; -+ -+export const landingPageNavigationItems: NavigationItem[] = [ -+ { name: "Features", to: "#features" }, -+ { name: "Documentation", to: DocsUrl }, -+ { name: "Blog", to: BlogUrl }, -+]; -+export const features: GridFeature[] = [ -+ { + + export const features: GridFeature[] = [ + { +- name: "Cool Feature 1", +- description: "Your feature", +- emoji: "🤝", + description: + "Have a sweet AI-powered app concept? Get your idea shipped to potential customers in days!", + icon: AI illustration, -+ href: DocsUrl, + href: DocsUrl, +- size: "small", + size: "medium", + fullWidthIcon: true, + align: "left", -+ }, -+ { + }, + { +- name: "Cool Feature 2", +- description: "Feature description", +- emoji: "🔐", + name: "Full-stack Type Safety", + description: + "Full support for TypeScript with auto-generated types that span the whole stack. Nothing to configure!", + emoji: "🥞", -+ href: DocsUrl, + href: DocsUrl, +- size: "small", + size: "medium", -+ }, -+ { + }, + { +- name: "Cool Feature 3", +- description: "Describe your cool feature here", +- emoji: "🥞", +- href: DocsUrl, + description: + "File upload examples with AWS S3 presigned URLs are included and fully documented!", + icon: ( @@ -52,10 +62,15 @@ + /> + ), + href: DocsUrl + "/guides/file-uploading/", -+ size: "medium", + size: "medium", + fullWidthIcon: true, -+ }, -+ { + }, + { +- name: "Cool Feature 4", +- description: "Describe your cool feature here", +- emoji: "💸", +- href: DocsUrl, +- size: "large", + name: "Email Sending", + description: + "Email sending built-in. Combine it with the cron jobs feature to easily send emails to your customers.", @@ -64,16 +79,26 @@ + size: "medium", + fullWidthIcon: true, + direction: "col-reverse", -+ }, -+ { + }, + { +- name: "Cool Feature 5", +- description: "Describe your cool feature here", +- emoji: "💼", +- href: DocsUrl, +- size: "large", + name: "Open SaaS", + description: "Try the demo app", + icon: Wasp Logo, + href: routes.LoginRoute.to, + size: "medium", + highlight: true, -+ }, -+ { + }, + { +- name: "Cool Feature 6", +- description: "It is cool", +- emoji: "📈", +- href: DocsUrl, +- size: "small", + name: "Blog w/ Astro", + description: + "Built-in blog with the Astro framework. Write your posts in Markdown, and watch your SEO performance take off.", @@ -81,8 +106,11 @@ + href: DocsUrl + "/start/guided-tour/", + size: "medium", + fullWidthIcon: true, -+ }, -+ { + }, + { +- name: "Cool Feature 7", +- description: "Cool feature", +- emoji: "📧", + name: "Deploy Anywhere. Easily.", + description: + "No vendor lock-in because you own all your code. Deploy yourself, or let Wasp deploy it for you with a single command.", @@ -93,26 +121,54 @@ + { + name: "Complete Documentation & Support", + description: "And a Discord community to help!", -+ href: DocsUrl, -+ size: "small", -+ }, -+ { + href: DocsUrl, + size: "small", + }, + { +- name: "Cool Feature 8", +- description: "Describe your cool feature here", +- emoji: "🤖", +- href: DocsUrl, +- size: "medium", + name: "E2E Tests w/ Playwright", + description: "Tests and a CI pipeline w/ GitHub Actions", + href: DocsUrl + "/guides/tests/", + size: "small", -+ }, -+ { + }, + { +- name: "Cool Feature 9", +- description: "Describe your cool feature here", +- emoji: "🚀", + name: "Open-Source Philosophy", + description: + "The repo and framework are 100% open-source, and so are the services wherever possible. Still missing something? Contribute!", + emoji: "🤝", -+ href: DocsUrl, -+ size: "medium", -+ }, -+]; -+export const testimonials = [ -+ { + href: DocsUrl, + size: "medium", + }, + ]; +- + export const testimonials = [ + { +- name: "Da Boi", +- role: "Wasp Mascot", +- avatarSrc: daBoiAvatar, +- socialUrl: "https://twitter.com/wasplang", +- quote: "I don't even know how to code. I'm just a plushie.", +- }, +- { +- name: "Mr. Foobar", +- role: "Founder @ Cool Startup", +- avatarSrc: daBoiAvatar, +- socialUrl: "", +- quote: "This product makes me cooler than I already am.", +- }, +- { +- name: "Jamie", +- role: "Happy Customer", +- avatarSrc: daBoiAvatar, +- socialUrl: "#", +- quote: "My cats love it!", + name: "Max Khamrovskyi", + role: "Senior Eng @ Red Hat", + avatarSrc: @@ -137,7 +193,7 @@ + "https://pbs.twimg.com/profile_images/1877734205561430016/jjpG4mS6_400x400.jpg", + socialUrl: "https://twitter.com/billyjhowell", + quote: -+ "Congrats! I am loving Wasp. It's really helped me, a self-taught coder increase my confidence. I feel like I've finally found the perfect, versatile stack for all my projects instead of trying out a new one each time.", ++ "Congrats! I am loving Wasp & Open SaaS. It's really helped me, a self-taught coder increase my confidence. I feel like I've finally found the perfect, versatile stack for all my projects instead of trying out a new one each time.", + }, + { + name: "Tim Skaggs", @@ -192,11 +248,15 @@ + "https://dev.to/wasp/our-web-framework-reached-9000-stars-on-github-9000-jij#comment-2dech", + quote: + "This is exactly the framework I've been dreaming of ever since I've been waiting to fully venture into the JS Backend Dev world. I believe Wasp will go above 50k stars this year. The documentation alone gives me the confidence that this is my permanent Nodejs framework and I'm staying with Wasp. Phenomenal work by the team... Please keep up your amazing spirits. Thank you", -+ }, -+]; -+export const faqs = [ -+ { -+ id: 1, + }, + ]; +- + export const faqs = [ + { + id: 1, +- question: "Whats the meaning of life?", +- answer: "42.", +- href: "https://en.wikipedia.org/wiki/42_(number)", + question: "Why is this SaaS Template free and open-source?", + answer: + "We believe the best product is made when the community puts their heads together. We also believe a quality starting point for a web app should be free and available to everyone. Our hope is that together we can create the best SaaS template out there and bring our ideas to customers quickly.", @@ -207,60 +267,93 @@ + href: "https://wasp-lang.dev", + answer: + "It's the fastest way to develop full-stack React + NodeJS + Prisma apps and it's what gives this template superpowers. Wasp relies on React, NodeJS, and Prisma to define web components and server queries and actions. Wasp's secret sauce is its compiler which takes the client, server code, and config file and outputs the client app, server app and deployment code, supercharging the development experience. Combined with this template, you can build a SaaS app in record time.", -+ }, -+]; -+export const footerNavigation = { -+ app: [ + }, + ]; +- + export const footerNavigation = { + app: [ + { name: "Github", href: GithubUrl }, -+ { name: "Documentation", href: DocsUrl }, -+ { name: "Blog", href: BlogUrl }, -+ ], -+ company: [ + { name: "Documentation", href: DocsUrl }, + { name: "Blog", href: BlogUrl }, + ], + company: [ +- { name: "About", href: "https://wasp.sh" }, +- { name: "Privacy", href: "#" }, +- { name: "Terms of Service", href: "#" }, + { name: "Terms of Service", href: GithubUrl + "/blob/main/LICENSE" }, + { name: "Made by the Wasp team = }", href: WaspUrl }, -+ ], -+}; -+export const examples = [ -+ { + ], + }; +- + export const examples = [ + { +- name: "Example #1", +- description: "Describe your example here.", +- imageSrc: kivo, +- href: "#", + name: "Microinfluencers", + description: "microinfluencer.club", + imageSrc: microinfluencerClub, + href: "https://microinfluencer.club", -+ }, -+ { + }, + { +- name: "Example #2", +- description: "Describe your example here.", +- imageSrc: messync, +- href: "#", + name: "Kivo", + description: "kivo.dev", + imageSrc: kivo, + href: "https://kivo.dev", -+ }, -+ { + }, + { +- name: "Example #3", +- description: "Describe your example here.", +- imageSrc: microinfluencerClub, +- href: "#", + name: "Searchcraft", + description: "searchcraft.io", + imageSrc: searchcraft, + href: "https://www.searchcraft.io", -+ }, -+ { + }, + { +- name: "Example #4", +- description: "Describe your example here.", +- imageSrc: promptpanda, +- href: "#", + name: "Scribeist", + description: "scribeist.com", + imageSrc: scribeist, + href: "https://scribeist.com", -+ }, -+ { + }, + { +- name: "Example #5", +- description: "Describe your example here.", +- imageSrc: reviewradar, +- href: "#", + name: "Messync", + description: "messync.com", + imageSrc: messync, + href: "https://messync.com", -+ }, -+ { + }, + { +- name: "Example #6", +- description: "Describe your example here.", +- imageSrc: scribeist, +- href: "#", + name: "Prompt Panda", + description: "promptpanda.io", + imageSrc: promptpanda, + href: "https://promptpanda.io", -+ }, -+ { + }, + { +- name: "Example #7", +- description: "Describe your example here.", +- imageSrc: searchcraft, +- href: "#", + name: "Review Radar", + description: "reviewradar.ai", + imageSrc: reviewradar, + href: "https://reviewradar.ai", -+ }, -+]; + }, + ]; diff --git a/opensaas-sh/app_diff/src/landing-page/operations.ts.diff b/opensaas-sh/app_diff/src/landing-page/operations.ts.diff new file mode 100644 index 000000000..1f5972e01 --- /dev/null +++ b/opensaas-sh/app_diff/src/landing-page/operations.ts.diff @@ -0,0 +1,247 @@ +--- template/app/src/landing-page/operations.ts ++++ opensaas-sh/app/src/landing-page/operations.ts +@@ -0,0 +1,244 @@ ++import type { GetGithubRoadmap } from "wasp/server/operations"; ++ ++import * as z from "zod"; ++import { assertUnreachable } from "../shared/utils"; ++ ++const GITHUB_ORG_NAME = "wasp-lang"; ++const OPEN_SAAS_GITHUB_PROJECT_NUMBER = 6; ++ ++export enum GithubEpicStatus { ++ Ideas = "Ideas", ++ Planned = "Planned", ++ InProgress = "In progress", ++ Done = "Done", ++} ++ ++export type GithubEpic = { ++ name: string; ++ totalIssues: number; ++ doneIssues: number; ++ status: GithubEpicStatus; ++ url: string; ++}; ++ ++let cachedGithubEpicsData: GithubEpic[] | null = null; ++let lastFetchTime = 0; ++const CACHE_DURATION_4_HOURS = 4 * 60 * 60 * 1000; ++ ++export const getGithubRoadmap: GetGithubRoadmap = async () => { ++ const currentTime = Date.now(); ++ ++ if ( ++ cachedGithubEpicsData && ++ currentTime - lastFetchTime < CACHE_DURATION_4_HOURS ++ ) { ++ return cachedGithubEpicsData; ++ } else { ++ lastFetchTime = currentTime; ++ } ++ ++ const githubRoadmapToken = process.env.GITHUB_ROADMAP_TOKEN; ++ if (!githubRoadmapToken) { ++ console.warn("GITHUB_ROADMAP_TOKEN is not set"); ++ return []; ++ } ++ ++ const githubGraphQlQuery = ` ++ query($org: String!, $number: Int!) { ++ organization(login: $org) { ++ projectV2(number: $number) { ++ items(first: 100) { ++ nodes { ++ id ++ databaseId ++ fieldValues(first: 50) { ++ nodes { ++ ... on ProjectV2ItemFieldSingleSelectValue { ++ name ++ field { ... on ProjectV2FieldCommon { name } } ++ } ++ } ++ } ++ content { ++ ... on Issue { ++ title ++ url ++ number ++ issueType { name } ++ subIssues(first: 100) { ++ totalCount ++ nodes { ++ state ++ number ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ `; ++ ++ try { ++ const response = await fetch("https://api.github.com/graphql", { ++ method: "POST", ++ headers: { ++ "Content-Type": "application/json", ++ Authorization: `Bearer ${githubRoadmapToken}`, ++ }, ++ body: JSON.stringify({ ++ query: githubGraphQlQuery, ++ variables: { ++ org: GITHUB_ORG_NAME, ++ number: OPEN_SAAS_GITHUB_PROJECT_NUMBER, ++ }, ++ }), ++ }); ++ ++ const json = await response.json(); ++ ++ const parseResult = GithubGraphQLResponseSchema.safeParse(json); ++ if (!parseResult.success) { ++ console.error( ++ "GitHub API response validation failed:", ++ parseResult.error, ++ ); ++ return cachedGithubEpicsData || []; ++ } ++ ++ const validatedResponse = parseResult.data; ++ ++ if (validatedResponse.errors) { ++ console.error("GitHub API Errors:", validatedResponse.errors); ++ throw new Error("GitHub API returned errors"); ++ } ++ ++ const nodes = ++ validatedResponse.data?.organization?.projectV2?.items?.nodes || []; ++ const statusFieldName = "Status"; ++ ++ const BASE_PROJECT_URL = `https://github.com/orgs/${GITHUB_ORG_NAME}/projects/${OPEN_SAAS_GITHUB_PROJECT_NUMBER}/views/1?pane=issue&itemId=`; ++ ++ const epics: GithubEpic[] = nodes ++ .filter((node) => node.content?.issueType?.name === "Epic") ++ .map((node) => { ++ const issue = node.content; ++ ++ const statusField = node.fieldValues.nodes.find( ++ (f) => f.field?.name === statusFieldName, ++ ); ++ const statusName = statusField?.name || "Ideas"; ++ ++ let status: GithubEpicStatus = GithubEpicStatus.Ideas; ++ if ( ++ Object.values(GithubEpicStatus).includes( ++ statusName as GithubEpicStatus, ++ ) ++ ) { ++ status = statusName as GithubEpicStatus; ++ } else { ++ console.warn(`Unknown GitHub Epic status: ${statusName}`); ++ } ++ ++ const subIssues = issue.subIssues?.nodes || []; ++ const totalIssues = issue.subIssues?.totalCount ?? subIssues.length; ++ const doneIssues = subIssues.filter( ++ (sub) => sub.state === "CLOSED", ++ ).length; ++ ++ return { ++ name: issue.title, ++ totalIssues, ++ doneIssues, ++ status, ++ url: `${BASE_PROJECT_URL}${node.databaseId}`, ++ }; ++ }); ++ ++ epics.sort((a, b) => getStatusOrder(a.status) - getStatusOrder(b.status)); ++ ++ cachedGithubEpicsData = epics; ++ return epics; ++ } catch (error) { ++ console.error("Failed to fetch GitHub roadmap:", error); ++ return cachedGithubEpicsData || []; ++ } ++}; ++ ++const getStatusOrder = (status: GithubEpicStatus): number => { ++ switch (status) { ++ case GithubEpicStatus.InProgress: ++ return 0; ++ case GithubEpicStatus.Planned: ++ return 1; ++ case GithubEpicStatus.Ideas: ++ return 2; ++ case GithubEpicStatus.Done: ++ return 3; ++ default: ++ return assertUnreachable(status); ++ } ++}; ++ ++const GithubIssueSchema = z.object({ ++ title: z.string(), ++ url: z.string(), ++ number: z.number(), ++ issueType: z ++ .object({ ++ name: z.string(), ++ }) ++ .nullish(), ++ subIssues: z ++ .object({ ++ totalCount: z.number(), ++ nodes: z.array( ++ z.object({ ++ state: z.string(), ++ number: z.number(), ++ }), ++ ), ++ }) ++ .nullish(), ++}); ++ ++const ProjectV2ItemFieldValueSchema = z.object({ ++ name: z.string().nullish(), ++ field: z ++ .object({ ++ name: z.string(), ++ }) ++ .nullish(), ++}); ++ ++const ProjectV2ItemSchema = z.object({ ++ id: z.string(), ++ databaseId: z.number().nullish(), ++ fieldValues: z.object({ ++ nodes: z.array(ProjectV2ItemFieldValueSchema), ++ }), ++ content: GithubIssueSchema, ++}); ++ ++const GithubGraphQLResponseSchema = z.object({ ++ data: z ++ .object({ ++ organization: z ++ .object({ ++ projectV2: z ++ .object({ ++ items: z ++ .object({ ++ nodes: z.array(ProjectV2ItemSchema), ++ }) ++ .nullish(), ++ }) ++ .nullish(), ++ }) ++ .nullish(), ++ }) ++ .nullish(), ++ errors: z.array(z.any()).nullish(), ++}); diff --git a/opensaas-sh/app_diff/src/payment/PricingPage.tsx.diff b/opensaas-sh/app_diff/src/payment/PricingPage.tsx.diff index 208d8d192..82abaf928 100644 --- a/opensaas-sh/app_diff/src/payment/PricingPage.tsx.diff +++ b/opensaas-sh/app_diff/src/payment/PricingPage.tsx.diff @@ -4,27 +4,7 @@ } from "./plans"; const bestDealPaymentPlanId: PaymentPlanId = PaymentPlanId.Pro; -+const PaymentsDocsURL = "https://docs.opensaas.sh/guides/payments-integration/"; ++const PaymentsDocsURL = "https://docs.opensaas.sh/guides/payment-integrations/"; interface PaymentPlanCard { name: string; -@@ -125,9 +126,16 @@ - -
-

-- Choose between Stripe and LemonSqueezy as your payment provider. Just -- add your Product IDs! Try it out below with test credit card number{" "} --
-+ Choose between{" "} -+ -+ Stripe -+ {" "} -+ and{" "} -+ -+ LemonSqueezy -+ {" "} -+ as your payment provider. Just add your Product IDs! Try it out below -+ with test credit card number
- - 4242 4242 4242 4242 4242 - diff --git a/opensaas-sh/app_diff/src/payment/paymentProcessor.ts.diff b/opensaas-sh/app_diff/src/payment/paymentProcessor.ts.diff index 979d0136f..83bf39345 100644 --- a/opensaas-sh/app_diff/src/payment/paymentProcessor.ts.diff +++ b/opensaas-sh/app_diff/src/payment/paymentProcessor.ts.diff @@ -1,6 +1,6 @@ --- template/app/src/payment/paymentProcessor.ts +++ opensaas-sh/app/src/payment/paymentProcessor.ts -@@ -27,9 +27,4 @@ +@@ -29,10 +29,4 @@ webhookMiddlewareConfigFn: MiddlewareConfigFn; } @@ -8,5 +8,6 @@ - * Choose which payment processor you'd like to use, then delete the - * other payment processor code that you're not using from `/src/payment` - */ --// export const paymentProcessor: PaymentProcessor = lemonSqueezyPaymentProcessor; export const paymentProcessor: PaymentProcessor = stripePaymentProcessor; +-// export const paymentProcessor: PaymentProcessor = lemonSqueezyPaymentProcessor; +-// export const paymentProcessor: PaymentProcessor = polarPaymentProcessor; diff --git a/opensaas-sh/app_diff/src/payment/stripe/paymentDetails.ts.diff b/opensaas-sh/app_diff/src/payment/stripe/paymentDetails.ts.diff deleted file mode 100644 index b9dd15868..000000000 --- a/opensaas-sh/app_diff/src/payment/stripe/paymentDetails.ts.diff +++ /dev/null @@ -1,15 +0,0 @@ ---- template/app/src/payment/stripe/paymentDetails.ts -+++ opensaas-sh/app/src/payment/stripe/paymentDetails.ts -@@ -20,10 +20,10 @@ - ) => { - return userDelegate.update({ - where: { -- paymentProcessorUserId: userStripeId, -+ stripeId: userStripeId, - }, - data: { -- paymentProcessorUserId: userStripeId, -+ stripeId: userStripeId, - subscriptionPlan, - subscriptionStatus, - datePaid, diff --git a/opensaas-sh/app_diff/src/payment/stripe/paymentProcessor.ts.diff b/opensaas-sh/app_diff/src/payment/stripe/paymentProcessor.ts.diff index bd3d9ae41..dcf4cceaa 100644 --- a/opensaas-sh/app_diff/src/payment/stripe/paymentProcessor.ts.diff +++ b/opensaas-sh/app_diff/src/payment/stripe/paymentProcessor.ts.diff @@ -1,11 +1,46 @@ --- template/app/src/payment/stripe/paymentProcessor.ts +++ opensaas-sh/app/src/payment/stripe/paymentProcessor.ts -@@ -32,7 +32,7 @@ - id: userId, - }, - data: { -- paymentProcessorUserId: customer.id, -+ stripeId: customer.id, - }, - }); - if (!stripeSession.url) +@@ -8,8 +8,8 @@ + } from "../paymentProcessor"; + import type { PaymentPlanEffect } from "../plans"; + import { +- fetchUserPaymentProcessorUserId, +- updateUserPaymentProcessorUserId, ++ fetchUserStripeId, ++ updateUserStripeId + } from "../user"; + import { + createStripeCheckoutSession, +@@ -28,8 +28,8 @@ + }: CreateCheckoutSessionArgs) => { + const customer = await ensureStripeCustomer(userEmail); + +- await updateUserPaymentProcessorUserId( +- { userId, paymentProcessorUserId: customer.id }, ++ await updateUserStripeId( ++ { userId, stripeId: customer.id }, + prismaUserDelegate, + ); + +@@ -56,18 +56,18 @@ + prismaUserDelegate, + userId, + }: FetchCustomerPortalUrlArgs) => { +- const paymentProcessorUserId = await fetchUserPaymentProcessorUserId( ++ const stripeId = await fetchUserStripeId( + userId, + prismaUserDelegate, + ); + +- if (!paymentProcessorUserId) { ++ if (!stripeId) { + return null; + } + + const billingPortalSession = + await stripeClient.billingPortal.sessions.create({ +- customer: paymentProcessorUserId, ++ customer: stripeId, + return_url: `${config.frontendUrl}/account`, + }); + diff --git a/opensaas-sh/app_diff/src/payment/stripe/webhook.ts.diff b/opensaas-sh/app_diff/src/payment/stripe/webhook.ts.diff new file mode 100644 index 000000000..3ef279550 --- /dev/null +++ b/opensaas-sh/app_diff/src/payment/stripe/webhook.ts.diff @@ -0,0 +1,38 @@ +--- template/app/src/payment/stripe/webhook.ts ++++ opensaas-sh/app/src/payment/stripe/webhook.ts +@@ -111,7 +111,7 @@ + case PaymentPlanId.Credits10: + await updateUserCredits( + { +- paymentProcessorUserId: customerId, ++ stripeId: customerId, + datePaid: invoicePaidAtDate, + numOfCreditsPurchased: paymentPlans[paymentPlanId].effect.amount, + }, +@@ -122,7 +122,7 @@ + case PaymentPlanId.Hobby: + await updateUserSubscription( + { +- paymentProcessorUserId: customerId, ++ stripeId: customerId, + datePaid: invoicePaidAtDate, + paymentPlanId, + subscriptionStatus: SubscriptionStatus.Active, +@@ -169,7 +169,7 @@ + ); + + const user = await updateUserSubscription( +- { paymentProcessorUserId: customerId, paymentPlanId, subscriptionStatus }, ++ { stripeId: customerId, paymentPlanId, subscriptionStatus }, + prismaUserDelegate, + ); + +@@ -237,7 +237,7 @@ + + await updateUserSubscription( + { +- paymentProcessorUserId: customerId, ++ stripeId: customerId, + subscriptionStatus: SubscriptionStatus.Deleted, + }, + prismaUserDelegate, diff --git a/opensaas-sh/app_diff/src/payment/user.ts.diff b/opensaas-sh/app_diff/src/payment/user.ts.diff new file mode 100644 index 000000000..23b16a87c --- /dev/null +++ b/opensaas-sh/app_diff/src/payment/user.ts.diff @@ -0,0 +1,98 @@ +--- template/app/src/payment/user.ts ++++ opensaas-sh/app/src/payment/user.ts +@@ -2,7 +2,7 @@ + import { PrismaClient } from "wasp/server"; + import { PaymentPlanId, SubscriptionStatus } from "./plans"; + +-export async function fetchUserPaymentProcessorUserId( ++export async function fetchUserStripeId( + userId: User["id"], + prismaUserDelegate: PrismaClient["user"], + ): Promise { +@@ -11,20 +11,20 @@ + id: userId, + }, + select: { +- paymentProcessorUserId: true, ++ stripeId: true, + }, + }); + +- return user.paymentProcessorUserId; ++ return user.stripeId; + } + +-interface UpdateUserPaymentProcessorUserIdArgs { ++interface UpdateUserStripeIdArgs { + userId: User["id"]; +- paymentProcessorUserId: NonNullable; ++ stripeId: NonNullable; + } + +-export function updateUserPaymentProcessorUserId( +- { userId, paymentProcessorUserId }: UpdateUserPaymentProcessorUserIdArgs, ++export function updateUserStripeId( ++ { userId, stripeId }: UpdateUserStripeIdArgs, + prismaUserDelegate: PrismaClient["user"], + ): Promise { + return prismaUserDelegate.update({ +@@ -32,13 +32,13 @@ + id: userId, + }, + data: { +- paymentProcessorUserId, ++ stripeId, + }, + }); + } + + interface UpdateUserSubscriptionArgs { +- paymentProcessorUserId: NonNullable; ++ stripeId: NonNullable; + subscriptionStatus: SubscriptionStatus; + paymentPlanId?: PaymentPlanId; + datePaid?: Date; +@@ -46,7 +46,7 @@ + + export function updateUserSubscription( + { +- paymentProcessorUserId, ++ stripeId, + paymentPlanId, + subscriptionStatus, + datePaid, +@@ -55,7 +55,7 @@ + ): Promise { + return userDelegate.update({ + where: { +- paymentProcessorUserId, ++ stripeId, + }, + data: { + subscriptionPlan: paymentPlanId, +@@ -66,14 +66,14 @@ + } + + interface UpdateUserCreditsArgs { +- paymentProcessorUserId: NonNullable; ++ stripeId: NonNullable; + numOfCreditsPurchased: number; + datePaid: Date; + } + + export function updateUserCredits( + { +- paymentProcessorUserId, ++ stripeId, + numOfCreditsPurchased, + datePaid, + }: UpdateUserCreditsArgs, +@@ -81,7 +81,7 @@ + ): Promise { + return userDelegate.update({ + where: { +- paymentProcessorUserId, ++ stripeId, + }, + data: { + credits: { increment: numOfCreditsPurchased }, diff --git a/opensaas-sh/app_diff/tailwind.config.js.diff b/opensaas-sh/app_diff/tailwind.config.js.diff index 050b8fb9e..597d218f7 100644 --- a/opensaas-sh/app_diff/tailwind.config.js.diff +++ b/opensaas-sh/app_diff/tailwind.config.js.diff @@ -1,6 +1,16 @@ --- template/app/tailwind.config.js +++ opensaas-sh/app/tailwind.config.js -@@ -254,6 +254,10 @@ +@@ -12,6 +12,9 @@ + darkMode: "class", + theme: { + extend: { ++ fontFamily: { ++ satoshi: ["Satoshi", "system-ui", "sans-serif"], ++ }, + colors: { + current: "currentColor", + transparent: "transparent", +@@ -251,6 +254,10 @@ 7: "-5px 0 0 #313D4A, 5px 0 0 #313D4A", 8: "1px 0 0 #313D4A, -1px 0 0 #313D4A, 0 1px 0 #313D4A, 0 -1px 0 #313D4A, 0 3px 13px rgb(0 0 0 / 8%)", default: "0px 8px 13px -3px rgba(0, 0, 0, 0.07)", diff --git a/opensaas-sh/blog/.gitignore b/opensaas-sh/blog/.gitignore index eed36a429..222eebe96 100644 --- a/opensaas-sh/blog/.gitignore +++ b/opensaas-sh/blog/.gitignore @@ -21,3 +21,7 @@ pnpm-debug.log* # Local Netlify folder .netlify + +# LLM files +llms.txt +llms-full.txt diff --git a/opensaas-sh/blog/README.md b/opensaas-sh/blog/README.md index 46eda1e33..706787a7f 100644 --- a/opensaas-sh/blog/README.md +++ b/opensaas-sh/blog/README.md @@ -1,4 +1,4 @@ -# OpenSaaS Docs and Blog +# Open SaaS Docs and Blog This is the docs and blog for the [OpenSaaS.sh](https://opensaas.sh/) website, [![Built with Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https://starlight.astro.build) diff --git a/opensaas-sh/blog/astro.config.mjs b/opensaas-sh/blog/astro.config.mjs index b404b1f9b..655025f60 100644 --- a/opensaas-sh/blog/astro.config.mjs +++ b/opensaas-sh/blog/astro.config.mjs @@ -62,7 +62,28 @@ export default defineConfig({ }, { label: "Guides", - autogenerate: { directory: "/guides/" }, + items: [ + { label: 'Analytics', link: "/guides/analytics/" }, + { label: 'Authentication', link: "/guides/authentication/" }, + { label: 'Authorization', link: "/guides/authorization/" }, + { label: 'Cookie Consent Modal', link: "/guides/cookie-consent/" }, + { + label: "Payment Integrations", + items: [ + { label: "Overview", link: "/guides/payment-integrations/" }, + { label: "Stripe", link: "/guides/payment-integrations/stripe/" }, + { label: "Lemon Squeezy", link: "/guides/payment-integrations/lemon-squeezy/" }, + { label: "Polar", link: "/guides/payment-integrations/polar/" }, + ] + }, + { label: 'Deploying', link: "/guides/deploying/" }, + { label: 'SEO', link: "/guides/seo/" }, + { label: 'Email Sending', link: "/guides/email-sending/" }, + { label: 'File Uploading', link: "/guides/file-uploading/" }, + { label: 'Tests', link: "/guides/tests/" }, + { label: 'How (Not) to Update Your Open SaaS App', link: "/guides/updating-opensaas/" }, + { label: 'Vibe Coding with Open SaaS', link: "/guides/vibe-coding/" }, + ] }, { label: "General", diff --git a/opensaas-sh/blog/package.json b/opensaas-sh/blog/package.json index 0b2dc6e12..1c3e95259 100644 --- a/opensaas-sh/blog/package.json +++ b/opensaas-sh/blog/package.json @@ -4,7 +4,7 @@ "type": "module", "scripts": { "astro": "astro", - "build": "astro check && astro build", + "build": "npm run generate-llm-files && astro check && astro build", "dev": "astro dev", "generate-llm-files": "node ./scripts/generate-llm-files.mjs", "preview": "astro preview", diff --git a/opensaas-sh/blog/public/CRAIG_ROCK.png b/opensaas-sh/blog/public/CRAIG_ROCK.png index 526663749..f0385f87b 100644 Binary files a/opensaas-sh/blog/public/CRAIG_ROCK.png and b/opensaas-sh/blog/public/CRAIG_ROCK.png differ diff --git a/opensaas-sh/blog/public/banner-images/2025-11-19-gemini-3-open-saas.webp b/opensaas-sh/blog/public/banner-images/2025-11-19-gemini-3-open-saas.webp new file mode 100644 index 000000000..c4a520775 Binary files /dev/null and b/opensaas-sh/blog/public/banner-images/2025-11-19-gemini-3-open-saas.webp differ diff --git a/opensaas-sh/blog/public/banner-images/2025-11-21-open-saas-public-roadmap.webp b/opensaas-sh/blog/public/banner-images/2025-11-21-open-saas-public-roadmap.webp new file mode 100644 index 000000000..66eaa2027 Binary files /dev/null and b/opensaas-sh/blog/public/banner-images/2025-11-21-open-saas-public-roadmap.webp differ diff --git a/opensaas-sh/blog/public/llms-full.txt b/opensaas-sh/blog/public/llms-full.txt deleted file mode 100644 index a4f1652ba..000000000 --- a/opensaas-sh/blog/public/llms-full.txt +++ /dev/null @@ -1,2264 +0,0 @@ -# Introduction - -## Welcome to your new SaaS App! - -You've decided to build a SaaS app with this template. Great choice! - -This template is: - -1. fully open-source -2. completely free to use and distribute -3. comes with a ton of features out of the box -4. community-driven and constantly improving! - -Check it out in action here: [OpenSaaS.sh](https://opensaas.sh) -Check out the Code: [Open SaaS GitHub Repo](https://github.com/wasp-lang/open-saas) - -:::tip[FREE & OPEN-SOURCE!? ] -That's right. Use this template however you like. No strings attached. - -If you find this template useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp). It helps us to keep bringing you open-source software just like this! -::: - -## What's inside? - -The template itself is built on top of some very powerful tools and frameworks, including: - - [Wasp](https://wasp.sh) - a full-stack React, NodeJS, Prisma framework with superpowers - - [Astro](https://starlight.astro.build/) - Astro's lightweight "Starlight" template for documentation and blog - - [Stripe](https://stripe.com) or [Lemon Squeezy](https://lemonsqueezy.com/) - for products and payments - - [Plausible](https://plausible.io) or [Google](https://analytics.google.com/) Analytics - - [OpenAI](https://openai.com) - OpenAI API integrated into the app or [Replicate](https://replicate.com/) (coming soon ) - - [AWS S3](https://aws.amazon.com/s3/) - for file uploads - - [SendGrid](https://sendgrid.com), [MailGun](https://mailgun.com), or SMTP - for email sending - - [TailwindCSS](https://tailwindcss.com) - for styling - - [TailAdmin](https://tailadmin.com/) - admin dashboard & components for TailwindCSS - -Because we're using Wasp as the full-stack framework, we can leverage a lot of its features to build our SaaS in record time, including: - - [Full-stack Authentication](https://wasp.sh/docs/auth/overview) - Email verified + social Auth in a few lines of code. - - [End-to-end Type Safety](https://wasp.sh/docs/data-model/operations/overview) - Type your backend functions and get inferred types on the front-end automatically, without the need to install or configure any third-party libraries. Oh, and type-safe Links, too! - - [Jobs](https://wasp.sh/docs/advanced/jobs) - Run cron jobs in the background or set up queues simply by defining a function in the config file. - - [One-command Deploy](https://wasp.sh/docs/advanced/deployment/overview) - Easily deploy via the CLI to [Fly.io](https://fly.io), or to other providers like [Railway](https://railway.app) and [Netlify](https://netlify.com). - -You also get access to Wasp's diverse, helpful community if you get stuck or need help. - - [Wasp Discord](https://discord.gg/rzdnErX) - -:::caution["Work In Progress"] -We've tried to get as many of the core features of a SaaS app into this template as possible, but there still might be some missing features or functionality. - -We could always use some help tying up loose ends, so consider [contributing](https://github.com/wasp-lang/open-saas/blob/main/CONTRIBUTING.md)! -::: - -In the next sections, we'll get our SaaS app started and tour its features. Let's get started! - ---- - -# Getting Started - -This guide will help you get your new SaaS app up and running. - -If you prefer video tutorials, you can watch this walkthrough below which will guide you through most of the setup (installation, authentication, payments, etc.). If you get stuck at any point, you can refer back to these docs for more information. - - - -## Install Wasp - -### Pre-requisites - -You must have Node.js (and NPM) installed on your machine and available in `PATH` to use Wasp. -Your version of Node.js must be >= 22.12. - -To switch easily between Node.js versions, we recommend using [nvm](https://github.com/nvm-sh/nvm). - -:::note[Installing and using nvm] -

- - Need help with nvm? - -
- Install nvm via your OS package manager (`apt`, `pacman`, `homebrew`, ...) or via the [nvm](https://github.com/nvm-sh/nvm#install--update-script) install script. - - Then, install a version of Node.js that you need: - - ```shell - nvm install 20 - ``` - - Finally, whenever you need to ensure a specific version of Node.js is used, run: - - ```shell - nvm use 20 - ``` - - to set the Node.js version for the current shell session. - - You can run - - ```shell - node -v - ``` - - to check the version of Node.js currently being used in this shell session. - - Check NVM repo for more details: [https://github.com/nvm-sh/nvm](https://github.com/nvm-sh/nvm). -
-
-::: - -### Linux and macOS - -Open your terminal and run: - -```shell -curl -sSL https://get.wasp.sh/installer.sh | sh -``` - -:::caution[Bad CPU type in executable] -
- - Are you getting this error on a Mac (Apple Silicon)? - -Given that the wasp binary is built for x86 and not for arm64 (Apple Silicon), you'll need to install Rosetta on your Mac if you are using a Mac with Mx (M1, M2, ...). Rosetta is a translation process that enables users to run applications designed for x86 on arm64 (Apple Silicon). To install Rosetta, run the following command in your terminal - -```bash -softwareupdate --install-rosetta -``` -Once Rosetta is installed, you should be able to run Wasp without any issues. - -
-::: - -### Windows - -In order to use Wasp on Windows, you need to install WSL2 (Windows Subsystem for Linux) and a Linux distribution of your choice. We recommend using Ubuntu. - -**You can refer to this [article](https://wasp.sh/blog/2023/11/21/guide-windows-development-wasp-wsl) for a step by step guide to using Wasp in the WSL environment.** If you need further help, reach out to us on [Discord](https://discord.gg/rzdnErX). - -:::caution[WSL2 Docker post installation steps] - -
- - Complete those steps to ensure that PostgreSQL and Docker work correctly with Wasp in WSL2. - -It is recommended to complete those post-install steps in WSL, based on the official Docker guide. These work if you are experiencing an error similar to this one. - -First, run - -```bash -sudo groupadd docker -``` - -command to create the `docker` group in case it doesn't exist. If it exists, don't worry, just continue with next steps. After that, add your current user to docker group by running - -```bash -sudo usermod -aG docker $USER -``` - -where $USER is your username. After that, log out and log back in to apply the changes. Finally, run - -```bash -su -s $USER -``` - -
-::: - -Once in WSL2, run the following command in your **WSL2 environment**: -```sh -curl -sSL https://get.wasp.sh/installer.sh | sh -``` - -:::caution[WSL2 and file system issues] -
- - Are you getting file system issues using WSL2? - -If you are using WSL2, make sure that your Wasp project is not on the Windows file system, but instead on the Linux file system. Otherwise, Wasp won't be able to detect file changes, due to this issue in WSL2. -
-::: - -### Finalize Installation - -Run the following command to verify that Wasp was installed correctly: - -```shell -wasp version -``` - -Also be sure to install the Wasp VSCode extension to get the best DX, e.g. syntax highlighting, code scaffolding, autocomplete, etc. - -:::tip[Installing the Wasp VSCode Extension] -You can install the Wasp VSCode extension by searching for "Wasp" in the Extensions tab in VSCode, or by visiting the [Wasp VSCode Extension](https://marketplace.visualstudio.com/items?itemName=wasp-lang.wasp) homepage -::: - -## Setting up your SaaS app - -### Cloning the Open SaaS template - -From the directory where you'd like to create your new project run: -```sh -wasp new -``` - -Then select option `[3] saas` from the list of templates after entering the name of your project. - -This will clone a **clean copy of the Open SaaS template** into a new directory! - -### Start your DB - -Before you start your app, you need to have a Postgres Database connected and running. With Wasp, that's super easy! - -First, make sure you have **Docker installed and running**. If not, download and install it [here](https://www.docker.com/products/docker-desktop/) - -With Docker running, open a new terminal window/tab and position yourself in the `app` directory: - -```sh -cd app -``` - -Then run: - -```sh -wasp start db -``` - -This will start and connect your app to a Postgres database for you. No need to do anything else! Just make sure to leave this terminal window open in the background while developing. Once you terminate the process, your DB will no longer be available to your app. - -Now let's create our very first database migration, to ensure the database has a correct schema. Open a new terminal tab/window and run the following command: - -```sh -wasp db migrate-dev -``` - -This might take a bit since this is the first time you are running it and it needs to install all the -dependencies for your Wasp project. - -In the future, you will also want to run `wasp db migrate-dev` whenever you make changes to your Prisma schema (Entities), -to apply those schema changes to the database. - -Additionally, if you want to see or manage your DB via Prisma's DB Studio GUI, run: - -```sh -wasp db studio -``` - -### Start your app - -At this point, you should be positioned in the `app/` directory and have the database running in another terminal session. - -Next, copy the `.env.server.example` file to `.env.server`. - -```sh -cp .env.server.example .env.server -``` - -`.env.server` is where API keys for services like payments, email sender, and similar go, and this is where you will want to put them in later. -For now, you can leave it as it is (dummy API keys), this will be enough to run the app. - -Then run: - -```sh -wasp start -``` - -This will install all the dependencies and start the app (client and server) for you :)! - -If the app doesn't open automatically in your browser, you can open it manually by visiting `http://localhost:3000` in your browser. - -At this point, you should have: - - your database running in one terminal session, likely on port `5432`. - - your app running in another terminal session, the client likely on port `3000`, and the server likely on port `3001`. - -#### Run Blog and Docs - -This SaaS app comes with a docs and blog section built with the [Starlight template on top of the Astro](https://starlight.astro.build) framework. You can use this as a starting point for your own blog and documentation, if necessary. - -If you do not need this, you can simply delete the `blog` folder from the root of the project. - -If you want to run the Starlight docs and blog, first navigate to the `blog` folder: - -```sh -cd ../blog -``` - -Then run: - -```sh -npm install -``` - -Then start the development server: - -```sh -npm run dev -``` - -Check the instructions in the terminal for the link to open the blog, it will typically be `https://localhost:4321/`. - -## What's next? - -Awesome! We have our new app ready and we know how to run both it and the blog/docs! Now, in the next section, we'll give you a quick "guided tour" of the different parts of the app we created and understand how it works. - -:::tip[Star our Repo on GitHub! ] -We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free! - -If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp) -::: - ---- - -# Guided Tour - -Awesome, you now have your very own SaaS app up and running! But, first, here are some important things you need to know about your app in its current state: - -1. When signing up with a new user, you will get a message to check your email for a verification link. But, in development, these emails are simply written to your terminal. **So, to continue with the registration process, check your server logs after sign up**! -```sh title="server logs" -[ Server ] -[ Server ] Dummy email sender -[ Server ] -[ Server ] From: Open SaaS App -[ Server ] To: vinny@wasp.sh -[ Server ] Subject: Verify your email -[ Server ] Text -[ Server ] Click the link below to verify your email: http://localhost:3000/email-verification?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InZpbm55QHdhc3Auc2giLCJleHAiOjE3MTg5NjUyNTB9.PkRGrmuDPuYFXkTprf7QpAye0e_O9a70xbER6LfxGJw -[ Server ] HTML -[ Server ]

Click the link below to verify your email

-[ Server ] Verify email -[ Server ] -``` -2. Your app is still missing some key configurations (e.g. API keys for Payment Processors, OpenAI, AWS S3, Auth, Analytics). These services won't work at the moment, but don't fear, because **we've provided detailed guides in these docs to help you set up all the services in this template**. -3. If you want to get a feel for what your SaaS could look like when finished, **check out [OpenSaaS.sh](https://opensaas.sh) in your browser. It was built using this template!** So make sure to log in, play around with the demo app, make a test payment, and check out the admin dashboard. - -In the sections below, we will take a short guide through the codebase and the app's main features. Then at the end of this tour, we also prepared a checklist of likely changes you will want to make to the app to make it your own. - -We're looking forward to seeing what you build! - -## Getting acquainted with the codebase -Now that you've gotten a first look at the app, let's dive into the codebase. - -At the root of our project, you will see three folders: -```sh -. - app - blog - e2e-tests -``` - -`app` contains the Wasp project files, which is your full-stack React + NodeJS + Prisma app along with a Wasp config file, `main.wasp`, which will be explained in more detail below. - -`blog` contains the [Astro Starlight template](https://starlight.astro.build/) for the blog and documentation section. - -`e2e-tests` contains the end-to-end tests using Playwright, which you can run to test your app's functionality. - -### App File Structure - -We've structured this full-stack app template vertically (by feature). That means that most directories within `app/src` contain both the React client code and NodeJS server code necessary for implementing its logic. - -Let's check out what's in the `app` folder in more detail: - -:::caution[v0.13 and below] -If you are using an older version of the Open SaaS template with Wasp `v0.13.x` or below, you may see a slightly different file structure. But don't worry, the vast majority of the code and features are the same! -::: - -```sh -. - main.wasp # Wasp Config file. You define your app structure here. - .wasp/ # Output dir for Wasp. DON'T MODIFY THESE FILES! - public/ # Public assets dir, e.g. www.yourdomain.com/public-banner.webp - src/ # Your code goes here. - admin/ # Admin dashboard related pages and components. - analytics/ # Logic and background jobs for processing analytics. - auth/ # All auth-related pages/components and logic. - client/ # Shared components, hooks, landing page, and other client code (React). - demo-ai-app/ # Logic for the example AI-powered demo app. - file-upload/ # Logic for uploading files to S3. - landing-page # Landing page related code - messages # Logic for app user messages. - payment/ # Logic for handling payments and webhooks. - server/ # Scripts, shared server utils, and other server-specific code (NodeJS). - shared/ # Shared constants and util functions. - user/ # Logic related to users and their accounts. - .env.server # Dev environment variables for your server code. - .env.client # Dev environment variables for your client code. - .prettierrc # Prettier configuration. - tailwind.config.js # TailwindCSS configuration. - package.json - package-lock.json - .wasproot -``` - -### The Wasp Config file - -This template at its core is a Wasp project, where [Wasp](https://wasp.sh) is a full-stack web app framework that let's you write your app in React, NodeJS, and Prisma and will manage the "boilerplatey" work for you, allowing you to just take care of the fun stuff! - -[Wasp's secret sauce](https://wasp.sh/docs) is its use of a config file (`main.wasp`) and compiler which takes your code and outputs the client app, server app and deployment code for you. - -In this template, we've already defined a number of things in the `main.wasp` config file, including: - -- [Auth](https://wasp.sh/docs/auth/overview) -- [Routes and Pages](https://wasp.sh/docs/tutorial/pages) -- [Prisma Database Models](https://wasp.sh/docs/data-model/entities) -- [Operations (data read and write functions)](https://wasp.sh/docs/data-model/operations/overview) -- [Background Jobs](https://wasp.sh/docs/advanced/jobs) -- [Email Sending](https://wasp.sh/docs/advanced/email) - -By defining these things in the config file, Wasp continuously handles the boilerplate necessary with putting all these features together. You just need to focus on the business logic of your app. - -Wasp abstracts away some things that you would normally be used to doing during development, so don't be surprised if you don't see some of the things you're used to seeing. - -:::note -It's possible to learn Wasp's feature set simply through using this template, but if you find yourself unsure how to implement a Wasp-specific feature and/or just want to learn more, a great starting point is the intro tutorial in the [Wasp docs](https://wasp.sh/docs) which takes ~20 minutes. -::: - -### Client - -The `src/client` folder contains any additional client-side code that doesn't belong to a feature: - -```sh -. - client - components # Your shared React components. - fonts # Extra fonts - hooks # Your shared React hooks. - icons # Your shared SVG icons. - static # Assets that you need access to in your code, e.g. import logo from 'static/logo.png' - App.tsx # Main app component to wrap all child components. Useful for global state, navbars, etc. - cn.ts # Helper function for dynamic and conditional Tailwind CSS classes. - Main.css - -``` - -### Server - -The `src/server` folder contains any additional server-side code that does not belong to a specific feature: - -```sh - server - scripts # Scripts to run via Wasp, e.g. database seeding. - utils.ts -``` - -## Main Features - -### Auth - -This template comes with a fully functional auth flow out of the box. It takes advantages of Wasp's built-in [Auth features](https://wasp.sh/docs/auth/overview), which do the dirty work of rolling your own full-stack auth for you! - -```js title="main.wasp" - auth: { - userEntity: User, - methods: { - email: { - //... - }, - google: {}, - github: {}, - discord: {} - }, - onAuthFailedRedirectTo: "/", - }, -``` - -By defining the auth structure in your `main.wasp` file, Wasp manages all the necessary code for you, including: -- Email verified login with reset password -- Social login with Google and/or GitHub -- Auth-related database entities for user credentials, sessions, and social logins -- Custom-generated AuthUI components for login, signup, and reset password -- Auth hooks for fetching user data - -We've set the template up with Wasp's `email`, `google`, and `gitHub` methods, which are all battle-tested and suitable for production. - -You can get started developing your app with the `email` method right away! - -:::caution[Dummy Email Provider] -Note that the `email` method relies on an `emailSender` (configured at `app.emailSender` in the `main.wasp` file), a service which sends emails to verify users and reset passwords. - -For development purposes, Wasp provides a `Dummy` email sender which Open SaaS comes with as the default. This provider *does not* actually send any confirmation emails to the specified email address, but instead logs all email verification links/tokens to the console! You can then follow these links to verify the user and continue with the sign-up process. - -```tsx title="main.wasp" - emailSender: { - provider: Dummy, // logs all email verification links/tokens to the server's console - defaultFrom: { - name: "Open SaaS App", - email: "me@example.com" - }, - }, -``` -::: - -We will explain more about these auth methods, and how to properly integrate them into your app, in the [Authentication Guide](/guides/authentication/). - -### Subscription Payments with Stripe or Lemon Squeezy - -No SaaS is complete without payments, specifically subscription payments. That's why this template comes with a fully functional Stripe or Lemon Squeezy integration. - -Let's take a quick look at how payments are handled in this template. - -1. a user clicks the `BUY` button and a **Checkout session** is created on the server -2. the user is redirected to the Checkout page where they enter their payment info -3. the user is redirected back to the app and the Checkout session is completed -4. Stripe / Lemon Squeezy sends a webhook event to the server with the payment info -5. The app server's **webhook handler** handles the event and updates the user's subscription status - -The payment processor you choose (Stripe or Lemon Squeezy) and its related functions can be found at `src/payment/paymentProcessor.ts`. The `Payment Processor` object holds the logic for creating checkout sessions, webhooks, etc. - -The logic for creating the Checkout session is defined in the `src/payment/operation.ts` file. [Actions](https://wasp.sh/docs/data-model/operations/actions) are a type of Wasp Operation, specifically your server-side functions that are used to **write** or **update** data to the database. Once they're defined in the `main.wasp` file, you can easily call them on the client-side: - -a) define the action in the `main.wasp` file -```js title="main.wasp" -action generateCheckoutSession { - fn: import { generateCheckoutSession } from "@src/payment/operations", - entities: [User] -} -``` - -b) implement the action in the `src/payment/operations` file -```js title="src/server/actions.ts" -export const generateCheckoutSession = async (paymentPlanId, context) => { - //... - } -``` - -c) call the action on the client-side -```js title="src/client/app/SubscriptionPage.tsx" - -const handleBuyClick = async (paymentPlanId) => { - const checkoutSession = await generateCheckoutSession(paymentPlanId); -}; -``` - -The webhook handler is defined in the `src/payment/webhook.ts` file. Unlike Actions and Queries in Wasp which are only to be used internally, we define the webhook handler in the `main.wasp` file as an API endpoint in order to expose it externally to Stripe - -```js title="main.wasp" -api paymentsWebhook { - fn: import { paymentsWebhook } from "@src/payment/webhook", - httpRoute: (POST, "/payments-webhook") - entities: [User], -} -``` - -Within the webhook handler, we look for specific events that the Payment Processor sends us to let us know which payment was completed and for which user. Then we update the user's subscription status in the database. - -To learn more about configuring the app to handle your products and payments, check out the [Payments Integration guide](/guides/payments-integration/). - -:::tip[Star our Repo on GitHub! ] -We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free! - -If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp) -::: - -### Analytics and Admin Dashboard - -Keeping an eye on your metrics is crucial for any SaaS. That's why we've built an administrator's dashboard where you can view your app's stats, user data, and revenue all in one place. - -To do that, we've leveraged Wasp's [Jobs feature](https://wasp.sh/docs/advanced/jobs) to run a cron job that calculates your daily stats. The app stats, such as page views and sources, can be pulled from either Plausible or Google Analytics. All you have to do is create a project with the analytics provider of your choice and import the respective pre-built helper functions! - -```js title="main.wasp" -job dailyStatsJob { - executor: PgBoss, - perform: { - fn: import { calculateDailyStats } from "@src/analytics/stats" - }, - schedule: { - cron: "0 * * * *" // runs every hour - }, - entities: [User, DailyStats, Logs, PageViewSource] -} -``` - -For more info on integrating Plausible or Google Analytics, check out the [Analytics guide](/guides/analytics/). - -## App Customization Walkthrough - -### General Considerations - -When you first start your Open SaaS app straight from the template, it will run, but many of the services won't work because they lack your own API keys. Here are list of services that need your API keys to work properly: - -- Auth Methods (Google, GitHub) -- Stripe or Lemon Squeezy -- OpenAI (Chat GPT API) -- Email Sending (Sendgrid) -- you must set this up if you're using the `email` Auth method -- Analytics (Plausible or Google Analytics) -- File Uploading (AWS S3) - -Now would be a good time to decide which features you do and do not need for your app, and remove the ones from the codebase that you don't need. - -For the features you will use, the next section of the documentation, `Guides`, will walk you through how to set each one up! - -:::note[Open SaaS is built on Wasp] -Remember, this template is built on the Wasp framework. If, at any time, these docs fail to provide enough information about a certain built-in feature, make sure to check out the [Wasp docs](https://wasp.sh/docs)! -::: - -But before you start setting up the main features, let's walk through the customizations you will likely want to make to the template to make it your own. - -### Customizations Checklist -#### `main.wasp` Config File -- [ ] Change the app name and title: - ```ts title="main.wasp" {1, 6} - app YourAppName { - wasp: { - version: "^0.13.2" - }, - - title: "Your App Name", - ``` - :::caution[Restart Your App] - Upon changing the app name, new, empty development database will be assigned to your app. This means you'll need to rerun `wasp db start`, `wasp db migrate-dev` and `wasp start`. - ::: -- [ ] Update meta tags in `app.head` (even if you don't have a custom domain yet, put one you would like to have, as this won't affect development). -- [ ] Update `app.emailSender.defaultFrom.name` with the name of your app/company/whatever you want your users to see in their inbox, if you're using the `emailSender` feature and/or `email` Auth method. -- [ ] Remove any features you might not use or need: - - [ ] Auth methods - `app.auth.methods` - - [ ] If you're not using `email` Auth method, remove the routes/pages `RequestPasswordReset`, `PasswordReset`, and `EmailVerification` - - [ ] Email Sending - `app.emailSender`, `job emailChecker` - - [ ] Plausible analytics - `app.head` - - [ ] File Uploading - `entity File`, `route FileUploadRoute`, `action createFile`, `query getAllFilesByUser`, `getDownloadFileSignedURL` -- [ ] Rename Entites and their properties, Routes/Pages, & Operations, if you wish. - -#### Customizing the Look / Style of the App -- [ ] Update your favicon at `public/favicon.ico`. -- [ ] Update the banner image used when posting links to your site at `public/public-banner.webp`. - - [ ] Update the URL for this banner at `og:image` and `twitter:image` in `app.head` of the `main.wasp` file. -- [ ] Make changes to your landing page, `landingPage.tsx`. - - [ ] Customize the `navBar`, `features`, `testimonials`, and `faqs` in the `contentSections.ts` file. - - [ ] Change/rename the `logo.webp` and main hero banner (`open-saas-banner.webp`) in the `static` folder. -- [ ] If you want to make changes to the global styles of the app, you can do so in `tailwind.config.cjs`. **Be aware that the current custom global styles defined already are mostly used in the app's Admin Dashboard!** - -#### Customizing the Analytics & Admin Dashboard -- [ ] If you're using Plausible, update the `app.head` with your Plausible domain. -- [ ] Update the `calculateDailyStats` function in `src/server/workers/calculateDailyStats.ts` to pull the stats from the analytics provider you've chosen (Plausible or Google Analytics). -- [ ] Change the cron schedule in the `dailyStatsJob` in the `main.wasp` file to match how often you want your stats to be calculated. -- [ ] Update the `AdminDashboard` components to display the stats you do/don't want to see. - -#### `.env.server` and `.env.client` Files -- [ ] After you've followed the `Guides` in the next section, you'll need to update the `.env.server` and `.env.client` files with your API keys and other environment variables for the services you've decided to use. -- [ ] Delete any redundant environment variables that you're not using, from the `.env.*` files as well as the `.env.*.example` files. - -#### Other Customizations -- [ ] Make a new GitHub Repo for your app. -- [ ] Deploy your app to a hosting provider. -- [ ] Buy a domain name for your app and get it set up with your hosting provider. -- [ ] Read the `e2e-tests` README and get your end-to-end tests set up. - - [ ] Change the tests to suit the changes you've made to your app -- [ ] Get the CI pipeline set up for your app (you can get started by using the Open SaaS development CI [example here](https://github.com/wasp-lang/open-saas/tree/main/.github/workflows)) - -## What's next? - -In the following `Guides` sections, we'll walk you through getting those API keys and setting up the finer points of features such as Payments & Webhooks, Auth, Email Sending, Analytics, and more. - ---- - -# Analytics - -This guide will show you how to integrate analytics for your app. You can choose between [Google Analytics](#google-analytics) and [Plausible](#plausible). - -Google Analytics is free, but uses cookies, so you'll probably want/need to implement the [Cookie Consent Modal](/guides/cookie-consent/) when using it. - -Plausible is an open-source, privacy-friendly alternative to Google Analytics. **You DO NOT have to use the cookie consent modal** with Plausible, as it does not use cookies. It's also easier to use than Google if you use their hosted service, but be aware it is a paid feature. It is completely free if you want to self-host it, although this comes with some additional setup steps. - -If you're looking to add analytics to your blog, you can follow the [Adding Analytics to your Blog](#adding-analytics-to-your-blog) section at the end of this guide. - -## Plausible - -### Hosted Plausible -Sign up for a hosted Plausible account [here](https://plausible.io/). - -Once you've signed up, you'll be taken to your dashboard. Create your site by adding your domain. Your domain is also your `PLAUSIBLE_SITE_ID` in your `.env.server` file. Make sure to add it. - -```sh -PLAUSIBLE_SITE_ID= -``` - -After adding your domain, you'll be taken to a page with your Plausible script tag. Copy and paste this script tag into the `main.wasp` file's head section. - -```js {7} -app OpenSaaS { - wasp: { - version: "^0.13.0" - }, - title: "My SaaS App", - head: [ - "", - ], - //... -``` - -Go back to your Plausible dashboard, click on your username in the top right, and click on the `Settings` tab. Scroll down, find your API key and paste it into your `.env.server` file under the `PLAUSIBLE_API_KEY` variable. - -:::note[No Cookies] -Plausible does not use cookies, so you don't need to add it to your [Cookie Consent Modal](/guides/cookie-consent/), hence the script can be added directly to `app.head` in your `main.wasp` file. -::: - -### Self-hosted Plausible - -Plausible, being an open-source project, allows you to self-host your analytics. This is a great option if you want to keep your data private and not pay for the hosted service. - -*coming soon...* -*until then, check out the [official documentation](https://plausible.io/docs)* - -:::tip[Contribute!] -If you'd like to help us write this guide, click the "Edit page" button at the bottom of this page - -As a completely free, open-source project, we appreciate any help -::: - -## Google Analytics - -First off, head over to `src/analytics/stats.ts` and switch out the Plausible Provider for Google Analytics so that your [background (cron) job](https://wasp.sh/docs/advanced/jobs) fetches the data from Google Analytics for your [Admin Dashboard](/general/admin-dashboard/): - -```ts ins={3} del={2} title="stats.ts" -//... - -export const calculateDailyStats: DailyStatsJob = async (_args, context) => { - //... -} -``` - -Next, make sure you sign up for [Google analytics](https://analytics.google.com/), then go to your `Admin` panel in the bottom of the left sidebar and then create a "Property" for your app. - -Once you've created a new Property, some Installation Instructions will pop up. Select `install manually` where you should see a string that looks like this: - -```sh title="" - https://www.googletagmanager.com/gtag/js?id= -``` -and copy and paste the Google Analytics ID into your `.env.client` file to get it working with the [Cookie Consent Modal](/guides/cookie-consent/) provided with this template: - -```sh title=".env.client" -REACT_APP_GOOGLE_ANALYTICS_ID= # e.g. G-1234567890 -``` - -:::tip[noscript] -In the Installation Instructions, Google Tag Manager might also instruct you to paste the `noscript` code snippet immediately after the opening `` tag. -You should skip this step because this snippet is activated only if users try to browse your app without JavaScript enabled, which is very rare and Wasp needs JS anyway. -::: - -Then, set up the Google Analytics API access by following these steps: - -1. **Set up a Google Cloud project:** If you haven't already, start by setting up a project in the [Google Cloud Console](https://console.cloud.google.com/). - -2. **Enable the Google Analytics API for your project:** Navigate to the "Library" in the Google Cloud Console and search for the "Google Analytics Data API" (for Google Analytics 4 properties) and enable it. - -3. **Create credentials:** Now go to the "Credentials" tab within your Google Cloud project, click on `+ credentials`, and create a new service account key. First, give it a name. Then, under "Grant this service account access to project", choose `viewer`. - -4. **Create Credentials:** When you go back to `Credentials` page, you should see a new service account listed under "Service Accounts". It will be a long email address to ends with `@your-project-id.iam.gserviceaccount.com`. Click on the service account name to go to the service account details page. - - - Under "Keys" in the service account details page, click "Add Key" and choose `Create new key`. - - - Select "JSON", then click "Create" to download your new service account's JSON key file. Keep this file secure and don't add it to your git repo as it grants access to your Google Analytics data. -5. **Update your Google Anayltics Settings:** Go back to your Google Analytics dashboard, and click on the `Admin` section in the left sidebar. Under `Property Settings > Property > Property Access Management` Add the service account email address (the one that ends with `@your-project-id.iam.gserviceaccount.com`) and give it `Viewer` permissions. - -6. **Encode and add the Credentials:** Add the `client_email` and the `private_key` from your JSON Key file into your `.env.server` file. But be careful! Because Google uses a special PEM private key, you need to first convert the key to base64, otherwise you will run into errors parsing the key. To do this, in a terminal window, run the command below and paste the output into your `.env.server` file under the `GOOGLE_ANALYTICS_PRIVATE_KEY` variable: - ```sh - echo -n "-----BEGIN PRIVATE KEY-----\nMI...A++eK\n-----END PRIVATE KEY-----\n" | base64 - ``` - -7. **Add your Google Analytics Property ID:** You will find the Property ID in your Google Analytics dashboard in the `Admin > Property > Property Settings > Property Details` section of your Google Analytics property (**not** your Google Cloud console). Add this 9-digit number to your `.env.server` file under the `GOOGLE_ANALYTICS_PROPERTY_ID` variable. - -## Adding Analytics to your Blog - -To add your analytics script to your Astro Starlight blog, all you need to do is modify the `head` property in your `blog/astro.config.mjs` file. - -Below is an example of how to add Google Analytics to your blog: - -```js -export default defineConfig({ - site: 'https://opensaas.sh', - integrations: [ - starlightBlog({ - // ... - }), - starlight({ - //... - head: [ - { - tag: 'script', - attrs: { - src: 'https://www.googletagmanager.com/gtag/js?id=', - }, - }, - { - tag: 'script', - content: ` - window.dataLayer = window.dataLayer || []; - function gtag(){dataLayer.push(arguments);} - gtag('js', new Date()); - - gtag('config', ''); - `, - }, - ], -``` - ---- - -# Authentication - -Setting up your app's authentication is easy with Wasp. In fact, it's already set up for you in the `main.wasp` file: - -```tsx title="main.wasp" - auth: { - userEntity: User, - methods: { - email: {}, - google: {}, - gitHub: {}, - discord: {} - }, - onAuthFailedRedirectTo: "/", - }, -``` - -The great part is, by defining your auth config in the `main.wasp` file, Wasp manages most of the Auth process for you, including the auth-related database entities for user credentials and sessions, as well as auto-generated client components for your app on the fly (aka AuthUI -- you can see them in use in the `src/auth` folder). - -## Email Verified Auth - -`email` method is the default auth method in Open Saas. - -Since it needs to send emails to verify users and reset passwords, it requires an [email sender](https://wasp.sh/docs/advanced/email) provider: a service it can use to send emails. -"email sender" provider is configured via `app.emailSender` field in the `main.wasp` file. - -:::caution[Dummy Email Provider] -To make it easy for you to get started, Open SaaS initially comes with the `Dummy` "email sender" provider, which does not send any emails, but instead logs all email verification links/tokens to the server's console! -You can then follow these links to verify the user and continue with the sign-up process. - -```tsx title="main.wasp" - emailSender: { - provider: Dummy, // logs all email verification links/tokens to the server's console - defaultFrom: { - name: "Open SaaS App", - email: "me@example.com" - }, - }, -``` - -You **can not use the Dummy provider in production** and your app **will not build** until you move to a production-ready provider, such as SendGrid. We outline the process of migrating to SendGrid below. -::: - -In order to use the `email` auth method in production, you'll need to switch from the `Dummy` "email sender" provider to a production-ready provider like SendGrid: - -1. First, set up your app's `emailSender` in the `main.wasp` file by following [this guide](/guides/email-sending/#integrate-your-email-sender). -2. Add your `SENDGRID_API_KEY` to the `.env.server` file. -3. Make sure the email address you use in the `fromField` object is the same email address that you configured your SendGrid account to send out emails with. In the end, your `main.wasp` file should look something like this: -```ts title="main.wasp" {6,7} del={15} ins={16} - auth: { - methods: { - email: { - fromField: { - name: "Open SaaS App", - // When using SendGrid, you must use the same email address that you configured your account to send out emails with! - email: "me@example.com" - }, - //... - }, - } - }, - //... - emailSender: { - provider: Dummy, - provider: SendGrid, - defaultFrom: { - name: "Open SaaS App", - // When using SendGrid, you must use the same email address that you configured your account to send out emails with! - email: "me@example.com" - }, - }, - ``` - -And that's it. Wasp will take care of the rest and update your AuthUI components accordingly. - -Check out the [Wasp Auth docs](https://wasp.sh/docs/auth/overview) for more info. - -## Google, GitHub, & Discord Auth - -We've also customized and pre-built the Google and GitHub auth flow for you. To start using them, you just need to uncomment out the methods you want in your `main.wasp` file and obtain the proper API keys to add to your `.env.server` file. - -To create a Google OAuth app and get your Google API keys, follow the instructions in [Wasp's Google Auth docs](https://wasp.sh/docs/auth/social-auth/google#3-creating-a-google-oauth-app). - -To create a GitHub OAuth app and get your GitHub API keys, follow the instructions in [Wasp's GitHub Auth docs](https://wasp.sh/docs/auth/social-auth/github#3-creating-a-github-oauth-app). - -To create a Discord OAuth app and get your Discord API keys, follow the instructions in [Wasp's Discord Auth docs](https://wasp.sh/docs/auth/social-auth/discord#3-creating-a-discord-app) - -Again, Wasp will take care of the rest and update your AuthUI components accordingly. - ---- - -# Authorization - -This guide will help you get started with authorization in your SaaS app. - -Authorization refers to what users can access in your app. This is useful for differentiating between users who have paid for different subscription tiers (e.g. "hobby" vs "pro"), or between users who have admin privileges and those who do not. - -Authorization differs from [authentication](/guides/authentication/) in that authentication refers to the process of verifying that a user is who they say they are (e.g. logging in with a username and password). - -To learn more about the different types of user permissions built into this SaaS template, including Stripe subscription tiers and statuses, check out the [User Overview Reference](/general/user-overview/). - -Also, check out our [blog post](https://wasp.sh/blog/2022/11/29/permissions-in-web-apps) to learn more about authorization (access control) in web apps. - -### Client-side Authorization - -Open Saas starts with all users having access to the landing page (`/`), but only authenticated users having access to the rest of the app (e.g. to the `/demo-app`, or to the `/account`). - -To control which pages require users to be authenticated to access them, you can set the `authRequired` property of the corresponding `page` definition in your `main.wasp` file: - -```tsx title="main.wasp" {3} -route AccountRoute { path: "/account", to: AccountPage } -page AccountPage { - authRequired: true, - component: import Account from "@src/user/AccountPage" -} -``` - -This will automatically redirect users to the login page if they are not logged in while trying to access that page. - -:::caution[Client-side authorization is just for the looks] -Users can manipulate the client code as they wish, meaning that client-side access control (authorization) serves the purpose of ergonomics/user experience, not the purpose of restricting access to sensitive data. -This means that authorization in the client code is a nice-to-have: it is here to make sure users don't get lost in the part of the app they can't work with because data is missing due to them not having access, not to actually restrict them from doing something. -Actually ensuring they don't have access to the data, that is on the server to ensure, via server-side logic that you will implement for authorization (access control). -::: - -If you want more fine-grained control over what users can access, there are two Wasp-specific options: -1. When you define the `authRequired: true` property on the `page` definition, Wasp automatically passes the User object to the page component. Here you can check for certain user properties before authorizing access: - -```tsx title="ExamplePage.tsx" "{ user }: { user: User }" - -export default function Example({ user }: { user: User }) { - - if (user.subscriptionStatus === 'past_due') { - return (Your subscription is past due. Please update your payment information.) - } - if (user.subscriptionStatus === 'cancel_at_period_end') { - return (Your susbscription will end on 01.01.2024) - } - if (user.subscriptionStatus === 'active') { - return (Thanks so much for your support!) - } - -} -``` - -2. Or you can take advantage of the `useAuth` hook and check for certain user properties before authorizing access to certain pages or components: - -```tsx title="ExamplePage.tsx" {1, 4} - -export default function ExampleHomePage() { - const { data: user } = useAuth(); - - return ( -

Hi {user.email || 'there'}

- ) -} -``` - -### Server-side Authorization - -Authorization on the server-side is the core of your access control logic, and determines what users actually can or can't do (unlike client-side authorization logic which is there merely for UX). - -You can authorize access to server-side operations by adding a check for a logged-in user on the `context.user` object which is passed to all operations in Wasp: - -```tsx title="src/server/actions.ts" -export const someServerAction: SomeServerAction<...> = async (args, context) => { - if (!context.user) { - throw new HttpError(401); // throw an error if user is not logged in - } - - if (context.user.subscriptionStatus === 'past_due') { - throw new HttpError(403, 'Your subscription is past due. Please update your payment information.'); - } - //... -} -``` - ---- - -# Cookie Consent Modal - -Cookie consent banners are annoying, we know. But they are legally required in many countries, so we have to deal with them. - -This guide will help you dynamically add or remove cookies from your app via the Cookie Consent modal that comes with this template. - -This is needed for *non-essential cookies* that are not necessary for the basic functionality of your app, such as analytics cookies or marketing cookies. - -The Modal can be found at `app/src/client/components/cookie-consent/` and contains two main files: -1. `Banner.tsx` - the component that displays the banner at the bottom of the page. -2. `Config.ts` - the configuration file that contains the cookies/scripts that will be dynamically added. - -The `Banner.tsx` component is imported in `app/src/client/App.tsx` and is rendered at the bottom of the page, while all the changes to the banner itself are done within the `Config.ts` file, which we explain below. - -## Configuration - -We decided to use the `vanilla-cookieconsent` library to handle the cookie consent. We've set it up to give you some basic functionality, using mostly the default settings. For a full list of options, you can check the [official documentation](https://www.npmjs.com/package/vanilla-cookieconsent). - -Below, we will guide you through the necessary steps to get the cookie consent modal set up for your app. - -### Google Analytics - -What's impotant to note for this template is that we are simply using the `onAccept` callbacks to dynamically add or remove our [Google Analytics](/guides/analytics/#google-analytics) cookies from the page. In order for it to work correctly with your app, you need to add your [Google Analytics ID](/guides/analytics/#google-analytics) to your `.env.client` file. - -```sh title=".env.client" - REACT_APP_GOOGLE_ANALYTICS_ID=G-1234567890 -``` - -And that's it! The cookie consent modal will now dynamically add or remove the Google Analytics cookies based on the user's choice. - -To check if it's working correctly, you can open the browser's developer tools and check the cookies tab. You should see the following cookies being added or removed based on the user's choice: - -```sh -_ga -_ga... # Google Analytics cookies. -cc_cookie # Cookie Consent cookie. The name of this cookie can be changed in the config file. -``` - -### Plausible Analytics - -If you decide to go with [Plausible Analytics](/guides/analytics/#plausible), you **DO NOT** need to ask users for their consent to use cookies because Plausible, as a privacy-first analytics provider, [does not use cookies](https://plausible.io/privacy-focused-web-analytics). Instead, It collects website usage data anonymously and in aggregate form only, without any personally identifiable information - -**By avoiding cookies, Plausible Analytics avoids the need for cookie consent banners.** - -### Your Terms / Privacy Policy - -You should also add a link to your terms and privacy policy within `consentModal` section of `config.language`: - -```ts title="Config.ts" {10,11} - language: { - default: 'en', - translations: { - en: { - consentModal: { - title: 'We use cookies', - // ... - // TODO: Add your own privacy policy and terms and conditions links below. - footer: ` - Privacy Policy - Terms and Conditions - `, - }, - }, - }, - } -``` - -### Allowing Users to Control Certain Cookies (OPTIONAL) - -If you've added more than just Google Analytics cookies to your app, you can allow users to control which cookies they want to accept or reject. For example, if you've added marketing cookies, you can add a button to the modal that allows users to reject them, while accepting analytics cookies. - -To do that, you can change the `preferencesModal.sections` property in `config.language`. Any section that you add to `preferencesModal.sections` must match a `linkedCategory` in the `config.categories` property. Make sure you also add a `showPreferencesBtn` property to `consentModal` (highlighted below). - -Below is an example of what your config might look like if you want to give users the option to control over multiple cookie preferences: - -```ts title="Config.ts" {7,9-67} - language: { - default: 'en', - translations: { - en: { - consentModal: { - // ... - showPreferencesBtn: 'Manage Individual preferences', // This button will open the preferences modal below. - }, - preferencesModal: { - title: 'Manage cookie preferences', - acceptAllBtn: 'Accept all', - acceptNecessaryBtn: 'Reject all', - savePreferencesBtn: 'Accept current selection', - closeIconLabel: 'Close modal', - serviceCounterLabel: 'Service|Services', - sections: [ - { - title: 'Your Privacy Choices', - description: `In this panel you can express some preferences related to the processing of your personal information. You may review and change expressed choices at any time by resurfacing this panel via the provided link. To deny your consent to the specific processing activities described below, switch the toggles to off or use the "Reject all" button and confirm you want to save your choices.`, - }, - { - title: 'Strictly Necessary', - description: - 'These cookies are essential for the proper functioning of the website and cannot be disabled.', - linkedCategory: 'necessary', - }, - { - title: 'Performance and Analytics', - description: - 'These cookies collect information about how you use our website. All of the data is anonymized and cannot be used to identify you.', - linkedCategory: 'analytics', - cookieTable: { - caption: 'Cookie table', - headers: { - name: 'Cookie', - domain: 'Domain', - desc: 'Description', - }, - body: [ - { - name: '_ga', - domain: location.hostname, - desc: 'Description 1', - }, - { - name: '_gid', - domain: location.hostname, - desc: 'Description 2', - }, - ], - }, - }, - { - title: 'YouTube', - description: 'This service is used to display video content on the website.', - linkedCategory: 'youtube', - cookieTable: { - // ... - } - }, - { - title: 'More information', - description: - 'For any queries in relation to my policy on cookies and your choices, please contact us', - }, - ], - }, - }, - }, - } -``` - -For more information on how to do that, check the [official documentation](https://cookieconsent.orestbida.com/reference/configuration-reference.html#translation-preferencesmodal-sections). - ---- - -# Deploying - -Because this SaaS app is a React/NodeJS/Postgres app built on top of [Wasp](https://wasp.sh), Open SaaS can take advantage of Wasp's easy, one-command deploy to Fly.io or manual deploy to any provider of your choice. - -The simplest and quickest option is to take advantage of Wasp's one-command deploy to Fly.io. - -Or if you prefer to deploy to a different provider, or your frontend and backend separately, you can follow the Deploying Manually section below. - -## Deploying your App -### Steps for Deploying - -These are the steps necessary for you to deploy your app. We recommend you follow these steps in order. - -- [ ] Get your [production API keys and environment variables](#prerequisites) -- [ ] Deploy your app easily to [Fly.io](#deploying-to-flyio) or [manually](#deploying-manually--to-other-providers) to any provider. -- [ ] Add the correct [redirect URL's to your social auth credentials](#adding-server-redirect-urls-to-social-auth) -- [ ] Set up your [production webhooks for either [Stripe](#setting-up-your-production-stripe-webhook) or [Lemon Squeezy](#setting-up-your-production-lemon-squeezy-webhook) -- [ ] Set your [production environment variables](#other-vars) on your deployed apps -- [ ] (Optional) [Deploy your blog](#deploying-your-blog) - -Each of these steps is covered in more detail below. - -### Prerequisites -#### AWS S3 CORS configuration -If you're storing files in AWS S3, ensure you've listed your production domain -in the bucket's CORS configuration under `AllowedOrigins`. Check the [File -uploading guide](/guides/file-uploading/#change-the-cors-settings) for details. - -#### Env Vars -Make sure you've got all your API keys and environment variables set up before you deploy. -##### Payment Processor Vars -In the [Payments Processor integration guide](/guides/payments-integration/), you set up your API keys using test keys and test product ids. You'll need to get the live/production versions of those keys. To get these, repeat the instructions in the [Integration Guide](/guides/payments-integration/) without being in test mode. Add the new keys to your deployed environment secrets. - -##### Other Vars -Many of your other environment variables will probably be the same as in development, but you should double-check that they are set correctly for production. - -Here are a list of all of them (some of which you may not be using, e.g. Analytics, Social Auth) in case you need to check: -###### General Vars -- [ ] `DATABASE_URL` -- [ ] `JWT_SECRET` -- [ ] `WASP_WEB_CLIENT_URL` -- [ ] `WASP_SERVER_URL` - -###### Open AI API Key -- [ ] `OPENAI_API_KEY` - -###### Sendgrid API Key -- [ ] `SENDGRID_API_KEY` - -###### Social Auth Vars -- [ ] `GOOGLE_CLIENT_ID` -- [ ] `GOOGLE_CLIENT_SECRET` -- [ ] `GITHUB_CLIENT_ID` -- [ ] `GITHUB_CLIENT_SECRET` - -###### Analytics Vars -- [ ] `REACT_APP_PLAUSIBLE_ANALYTICS_ID` (for client-side) -- [ ] `PLAUSIBLE_API_KEY` -- [ ] `PLAUSIBLE_SITE_ID` -- [ ] `PLAUSIBLE_BASE_URL` -- [ ] `REACT_APP_GOOGLE_ANALYTICS_ID` (for client-side) -- [ ] `GOOGLE_ANALYTICS_CLIENT_EMAIL` -- [ ] `GOOGLE_ANALYTICS_PROPERTY_ID` -- [ ] `GOOGLE_ANALYTICS_PRIVATE_KEY` -(Make sure you convert the private key within the JSON file to base64 first with `echo -n "PRIVATE_KEY" | base64`. See the [Analytics docs](/guides/analytics/#google-analytics) for more info) - -###### AWS S3 Vars -- [ ] `AWS_S3_IAM_ACCESS_KEY` -- [ ] `AWS_S3_IAM_SECRET_KEY` -- [ ] `AWS_S3_FILES_BUCKET` -- [ ] `AWS_S3_REGION` - -:::tip[Deployed? Get some swag! ] - -Do you have an Open SaaS app running in production? If yes, we'd love to send some swag your way! All you need to do is fill out [this form](https://e44cy1h4s0q.typeform.com/to/EPJCwsMi) and we'll make it happen. - -::: - -### Deploying to Fly.io - -[Fly.io](https://fly.io) is a platform for running your apps globally. It's a great choice for deploying your SaaS app because it's free to get started, can host your entire full-stack app in one place, scales well, and has one-command deploy integration with Wasp. - -**Wasp provides the handy `wasp deploy` command to deploy your entire full-stack app (DB, server, and client) in one command.** - -To learn how, please follow the detailed guide for [deploying to Fly via the Wasp CLI](https://wasp.sh/docs/deployment/deployment-methods/cli) from the Wasp documentation. We suggest you follow this guide carefully to get your app deployed. - -:::caution[Setting Environment Variables] -Remember, because we've set certain client-side env variables, make sure to pass them to the `wasp deploy` commands so that they can be included in the build: -```sh -REACT_APP_CLIENT_ENV_VAR_1=<...> REACT_APP_CLIENT_ENV_VAR_2=<...> wasp deploy -``` - -The `wasp deploy` command will also take care of setting the following server-side environment variables for you so you don't have to: -- `DATABASE_URL` -- `PORT` -- `JWT_SECRET` -- `WASP_WEB_CLIENT_URL` -- `WASP_SERVER_URL` - -For setting the remaining server-side environment variables, please refer to the [Deploying with the Wasp CLI Guide](https://wasp.sh/docs/deployment/deployment-methods/cli#launch). -::: - -### Deploying Manually / to Other Providers - -If you prefer to deploy manually, your frontend and backend separately, or just prefer using your favorite provider you can follow [Wasp's Manual Deployment Guide](https://wasp.sh/docs/deployment/deployment-methods/paas). - -:::caution[Client-side Environment Variables] -Remember to always set additional client-side environment variables, such as `REACT_APP_STRIPE_CUSTOMER_PORTAL` by appending them to the build command, e.g. -```sh -REACT_APP_CLIENT_ENV_VAR_1=<...> npm run build -``` -::: - -### Adding Server Redirect URL's to Social Auth - -After deploying your server, you need to add the correct redirect URIs to the credential settings. For this, refer to the following guides from the Wasp Docs: - -- [Google Auth](https://wasp.sh/docs/auth/social-auth/google#3-creating-a-google-oauth-app:~:text=Under%20Authorized%20redirect%20URIs) -- [Github Auth](https://wasp.sh/docs/auth/social-auth/github#3-creating-a-github-oauth-app:~:text=Authorization%20callback%20URL) - -### Setting up your Production Stripe Webhook - -Now you need to set up your stripe webhook for production use. Below are some important steps and considerations you should take as you prepare to deploy your app to production. - -#### Stripe API Versions - -When you create your Stripe account, Stripe will automatically assign you to their latest API version at that time. This API version is important because it determines the structure of the responses Stripe sends to your webhook, as well as the structure it expects of the requests you make toward the Stripe API. - -Because this template was built with a specific version of the Stripe API in mind, it could be that your Stripe account is set to a different API version. - -:::note -```ts title="stripeClient.ts" -export const stripe = new Stripe(process.env.STRIPE_API_KEY!, { - apiVersion: 'YYYY-MM-DD', // e.g. 2023-08-16 -}); -``` -When you specify a specific API version in your Stripe client, the requests you send to Stripe from your server, along with their responses, will match that API version. On the other hand, Stripe will send all other events to your webhook that didn't originate as a request sent from your server, like those made after a user completes a payment on checkout, using the default API version of the API. - -This is why it's important to make sure your Stripe client version also matches the API version in your Stripe account, and to thoroughly test any changes you make to your Stripe client before deploying to production. -::: - -To make sure your app is consistent with your Stripe account, here are some steps you can follow: - -1. You can find your `default` API version in the Stripe dashboard under the [Developers](https://dashboard.stripe.com/developers) section. -2. Check that the API version in your `/src/payment/stripe/stripeClient.ts` file matches the default API version in your dashboard: -```ts title="stripeClient.ts" {2} -export const stripe = new Stripe(process.env.STRIPE_KEY!, { - apiVersion: 'YYYY-MM-DD', // e.g. 2023-08-16 -}); -``` -3. If they don't match, you can upgrade/downgrade your Stripe NPM package in `package.json` to match the API version in your dashboard: - - If your default version on the Stripe dashboard is also the latest version of the API, you can simply upgrade your Stripe NPM package to the latest version. - - If your default version on the Stripe dashboard is not the latest version, and you don't want to [upgrade to the latest version](https://docs.stripe.com/upgrades#how-can-i-upgrade-my-api), because e.g. you have other projects that depend on the current version, you can find and install the Stripe NPM package version that matches your default API version by following these steps: - - Find and note the date of your default API version in the [developer dashboard](https://dashboard.stripe.com/developers). - - Go to the [Stripe NPM package](https://www.npmjs.com/package/stripe) page and hover over `Published` date column until you find the package release that matches your version. For example, here we find the NPM version that matches the default API version of `2023-08-16` in our dashboard, which is `13.x.x`. - - - Install the correct version of the Stripe NPM package by running, : - ```sh - npm install stripe@x.x.x # e.g. npm install stripe@13.11.0 - ``` -4. **Test your app thoroughly** to make sure that the changes you made to your Stripe client are working as expected before deploying to production. - -#### Creating Your Production Webhook -1. go to [https://dashboard.stripe.com/webhooks](https://dashboard.stripe.com/webhooks) -2. click on `+ add endpoint` -3. enter your endpoint url, which will be the url of your deployed server + `/payments-webhook`, e.g. `https://open-saas-wasp-sh-server.fly.dev/payments-webhook` - -4. select the events you want to listen to. These should be the same events you're consuming in your webhook which you can find listed in [`src/payment/stripe/webhookPayload.ts`](https://github.com/wasp-lang/open-saas/blob/main/template/app/src/payment/stripe/webhookPayload.ts): - -5. after that, go to the webhook you just created and `reveal` the new signing secret. -6. add this secret to your deployed server's `STRIPE_WEBHOOK_SECRET=` environment variable.
If you've deployed to Fly.io, you can do that easily with the following command: -```sh -wasp deploy fly cmd --context server secrets set STRIPE_WEBHOOK_SECRET=whsec_... -``` - -### Setting up your Production Lemon Squeezy Webhook - -To set up your Lemon Squeezy webhook, you'll need the URL of you newly deployed server + `/payments-webhook`, e.g. `https://open-saas-wasp-sh-server.fly.dev/payments-webhook`. - -With the webhook url ready, go to your [Lemon Squeezy Webhooks Dashboard](https://app.lemonsqueezy.com/settings/webhooks): -- click the `+` button. -- add the webhook forwarding url to the `Callback URL` section. -- give your webhook a signing secret (a long, random string). -- add this signing secret to your server's production environment variables under `LEMONSQUEEZY_WEBHOOK_SECRET=` -- make sure to select at least the following updates to be sent: - - order_created - - subscription_created - - subscription_updated - - subscription_cancelled -- click `save` - -## Deploying your Blog - -Deploying your Astro Starlight blog is a bit different than deploying your SaaS app. As an example, we will show you how to deploy your blog for free to Netlify. You will need a Netlify account and [Netlify CLI](https://docs.netlify.com/cli/get-started/) installed to follow these instructions. - -Make sure you are logged in with Netlify CLI. -- You can check if you are logged in with `netlify status`, -- you can log in with `netlify login`. - -Position yourself in the `blog` directory and run the following command: - -```sh -npm run build -``` - -This will build your blog into the `blog/dist` directory. Now you can deploy your blog to Netlify with the following command: - -```sh -netlify deploy -``` - -Select the `dist` directory as the deploy path. - -Finally, if the deployment looks good, you can deploy your blog to production with the following command: - -```sh -netlify deploy --prod -``` - ---- - -# Email Sending - -This guide explains how to use the integrated email sender and how you can integrate your own account in this template. - -## Sending Emails - -### The `Dummy` Email Provider (for Local Dev Only) -By default we've set up the email sender to use the `Dummy` provider. This is **for local development only** and no emails will actually be sent out! -To obtain an email verification token/link, you must check the server logs on initial sign up. You can click this link to verify your email and continue with the sign up process. -```tsx title="main.wasp" -app SaaSTemplate { - // ... - emailSender: { - provider: Dummy, - defaultFrom: { - name: "Open SaaS App", - email: "me@example.com" - }, - }, -``` - -Note that your app will not build if using the `Dummy` provider and you must switch to a production-ready provider in order to do so. - -### Using a Production-Ready Email Provider (e.g. SendGrid) -To change your email provider to a production-ready one, such as SendGrid, you'll want to configure your `emailSender` like so: - -```tsx title="main.wasp" -app SaaSTemplate { - // ... - emailSender: { - provider: SendGrid, - defaultFrom: { - name: "Open SaaS App", - // When using SendGrid, you must use the same email address that you configured your account to send out emails with! - email: "me@example.com" - }, - }, -``` - -This means that you can send emails from your app using the `send` function from the `email` modul provided by Wasp: - -```tsx title="src/server/webhooks.ts" - -//... - - if (subscription.cancel_at_period_end) { - await emailSender.send({ - to: customer.email, - subject: 'We hate to see you go :(', - text: 'We hate to see you go. Here is a sweet offer...', - html: 'We hate to see you go. Here is a sweet offer...', - }); - } -``` - -In the example above, you can see that we're sending an email to the customer when we receive a cancel subscription event within the Stripe webhook. - -This is a powerful feature and super simple to use. - -## Integrate your email sender - -To set up your email sender, you first need an account with one of the supported email providers. - - - - - Register at SendGrid.com and then get your [API KEYS](https://app.sendgrid.com/settings/api_keys). - - Copy yours to the `.env.server` file under the `SENDGRID_API_KEY` variable. - - Make sure to change the `defaultFrom` email address in the `main.wasp` file to use the same email address that you configured your account to send out emails with! - - ```tsx title="main.wasp" {5} - emailSender: { - provider: SendGrid, - defaultFrom: { - name: "Open SaaS App", - email: "me@example.com" // <--- same email address you configured your SendGrid account to send emails with! - }, - ``` - - - - Go to [Mailgun](https://mailgun.com) and create an account. - - Go to [API Keys](https://app.mailgun.com/settings/api_security/api_keys?onboardingTask=api-key) and create a new API key. - - Copy the API key and add it to your .env.server file under the `MAILGUN_API_KEY=` variable. - - Go to [Domains](https://app.mailgun.com/mg/sending/new-domain?onboardingTask=add-verify-domain) and create a new domain. - - Copy the domain and add it to your .env.server file as `MAILGUN_DOMAIN=`. - - Make sure to change the `defaultFrom` email address in the `main.wasp` file to use the same email address that you configured your account to send out emails with! - - ```tsx title="main.wasp" {5} - emailSender: { - provider: Mailgun, - defaultFrom: { - name: "Open SaaS App", - email: "me@example.com" // <--- same email address you configured your Mailgun account to send emails with! - }, - ``` - - - -If you want more detailed info, or would like to use SMTP, check out the [Wasp docs](https://wasp.sh/docs/advanced/email). - ---- - -# File Uploading - -This guide will show you how to set up file uploading in your SaaS app. - -There are two options we recommend: -1. Using [AWS S3](https://aws.amazon.com/s3/) with presigned URLS for secure file storage -2. Using Multer middleware to upload files to your own server - -**We recommend using AWS S3 as it's a scalable, secure option, that can handle a large amount of storage.** - -If you're just looking to upload small files and don't expect your app to grow to a large scale, you can use Multer to upload files to your app's server. - -:::tip[Star our Repo on GitHub! ] -We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free! - -If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp) -::: - -## Using AWS S3 - -### How presigned URLs work - -Presigned URLs are URLs that have been signed with your AWS credentials and can be used to upload files to your S3 bucket. They are time-limited and can be generated on the server and sent to the client to upload files directly to S3. - -The process of generating a presigned URL is as follows: -1. The client sends a request to the server to upload a file -2. The server generates a presigned URL using its AWS credentials -3. The server sends the presigned URL to the client -4. The client uses the presigned URL to upload the file directly to S3 before the URL expires - -We use this method to upload files to S3 because it is more secure than uploading files directly from the client to S3. It also allows us to keep our AWS credentials private and not expose them to the client. - -To use presigned URLs, we'll need to set up an S3 bucket and get our AWS credentials. - -### Create an AWS Account - -Before you begin, you'll need to create an AWS account. AWS accounts are free to create and are split up into: -1. Root account -2. IAM users - -You'll need to first create a root account, and then an IAM user for your SaaS app before you can start uploading files to S3. - -To do so, follow the steps in this external guide: [Creating IAM users and S3 buckets in AWS](https://medium.com/@emmanuelnwright/create-iam-users-and-s3-buckets-in-aws-264e78281f7f) - -### Create an AWS S3 Bucket - -Once you are logged in with your IAM user, you'll need to create an S3 bucket to store your files. - -1. Navigate to the S3 service in the AWS console - -2. Click on the `Create bucket` button - -3. Fill in the bucket name and region -4. **Leave all the settings as default** and click `Create bucket` - - -### Change the CORS settings - -Now we need to change some permissions on the bucket to allow for file uploads from your app. - -1. Click on the bucket you just created - -2. Click on the `Permissions` tab - -3. Scroll down to the `Cross-origin resource sharing (CORS)` section and click `Edit` - -5. Insert the correct CORS configuration and click `Save changes`. You can - copy-paste most of the config below, but **you must edit the - `AllowedOrigins` field** to fit your app. Include `http://localhost:3000` for - local development, and `https://` for production. - - If you don't yet have a domain name, just list `http://localhost:3000` for - now. We'll remind you to add your domain before deploying to production in - the [Deployment docs](/guides/deploying/#aws-s3-cors-configuration). - ```json {11,12} - [ - { - "AllowedHeaders": [ - "*" - ], - "AllowedMethods": [ - "POST", - "GET" - ], - "AllowedOrigins": [ - "http://localhost:3000", - "https://" - ], - "ExposeHeaders": [] - } - ] - ``` - -### Get your AWS S3 credentials - -Now that you have your S3 bucket set up, you'll need to get your S3 credentials to use in your app. - -1. Click on your username in the top right corner of the AWS console and select `Security Credentials` - -2. Scroll down to the `Access keys` section -3. Click on `Create Access Key` -4. Select the `Application running on an AWS service` option and create the access key - -5. Copy the `Access key ID` and `Secret access key` and paste them in your `src/app/.env.server` file: -```sh - AWS_S3_IAM_ACCESS_KEY=ACK... - AWS_S3_IAM_SECRET_KEY=t+33a... - AWS_S3_FILES_BUCKET=your-bucket-name - AWS_S3_REGION=your-region // (e.g. us-west-2) -``` - -:::tip[Star our Repo on GitHub! ] -We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free! - -If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp) -::: - -### Using and Customizing File Uploads with S3 in your App - -With your S3 bucket set up and your AWS credentials in place, you can now start uploading files in your app using presigned URLs by navigating to `localhost:3000/file-upload` and uploading a file. - -To begin customizing file uploads, is important to know where everything lives in your app. Here's a quick overview: -- `main.wasp`: - - The `File entity` can be found here. Here you can modify the fields to suit your needs. -- `src/file-upload/FileUploadPage.tsx`: - - The `FileUploadPage` component is where the file upload form lives. It also allows you to download the file from S3 by calling the `getDownloadFileSignedURL` based on that files `key` in the app DB. -- `src/file-upload/operations.ts`: - - The `createFile` action lives here and calls the `getUploadFileSignedURLFromS3` within it using your AWS credentials before passing it to the client. This function stores the files in the S3 bucket within folders named after the user's ID, so that each user's files are stored separately. - - The `getAllFilesByUser` fetches all File information uploaded by the user. Note that the files do not exist in the app database, but rather the file data, its name and its `key`, which is used to fetch the file from S3. - - The `getDownloadFileSignedURL` query fetches the presigned URL for a file to be downloaded from S3 using the file's `key` stored in the app's database. - -## Using Multer to upload files to your server - -If you're looking to upload files to the app server, you can use the Multer middleware to handle file uploads. This will allow you to store files on your server and is a good option if you need a quick and dirty, free solution for simple file uploads. - -Below are GitHub Gists that show you how to set up file uploads using Multer in your app: - -### Wasp version 0.12 & higher - - - ---- - -# Payments Integration - -This guide will show you how to set up Payments for testing and local development with the following payment processors: -- Stripe -- Lemon Squeezy - -:::note[Which should I choose?] -Stripe is the industry standard, is more configurable, and has cheaper fees. -Lemon Squeezy acts a [Merchant of Record](https://www.lemonsqueezy.com/reporting/merchant-of-record). This means they take care of paying taxes in multiple countries for you, but charge higher fees per transaction. -::: - -## Important First Steps - -First, go to `/src/payment/paymentProcessor.ts` and choose which payment processor you'd like to use, e.g. Stripe or Lemon Squeezy: - -```ts title="src/payment/paymentProcessor.ts" ins={5, 7} - -//... - -export const paymentProcessor: PaymentProcessor = stripePaymentProcessor; -// or... -export const paymentProcessor: PaymentProcessor = lemonSqueezyPaymentProcessor; -``` - -At this point, you can delete: -- the unused payment processor code within the `/src/payment/` directory, -- any unused environment variables from `.env.server` (they will be prefixed with the name of the provider your are not using): - - e.g. `STRIPE_API_KEY`, `STRIPE_CUSTOMER_PORTAL_URL`, `LEMONSQUEEZY_API_KEY`, `LEMONSQUEEZY_WEBHOOK_SECRET` -- Make sure to also uninstall the unused dependencies: - - `npm uninstall @lemonsqueezy/lemonsqueezy.js` - - or - - `npm uninstall stripe` -- Remove any unused fields from the `User` model in the `schema.prisma` file if they exist: - - e.g. `lemonSqueezyCustomerPortalUrl` - -Now your code is ready to go with your preferred payment processor and it's time to configure your payment processor's API keys, products, and other settings. - -## Stripe - -First, you'll need to create a Stripe account. You can do that [here](https://dashboard.stripe.com/register). - -:::tip[Star our Repo on GitHub! ] -We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free! - -If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp) -::: - -### Get your test Stripe API Keys - -Once you've created your account, you'll need to get your test API keys. You can do that by navigating to [https://dashboard.stripe.com/test/apikeys](https://dashboard.stripe.com/test/apikeys) or by going to the [Stripe Dashboard](https://dashboard.stripe.com/test/dashboard) and clicking on the `Developers`. - -- Click on the `Reveal test key token` button and copy the `Secret key`. -- Paste it in your `.env.server` file under `STRIPE_API_KEY=` - -### Create Test Products - -To create a test product, go to the test products url [https://dashboard.stripe.com/test/products](https://dashboard.stripe.com/test/products), or after navigating to your dashboard, click the `test mode` toggle. - -- Click on the `Add a product` button and fill in the relevant information for your product. -- Make sure you select `Software as a service (SaaS)` as the product type. -- For Subscription products, make sure you select `Recurring` as the billing type. -- For One-time payment products, make sure you select `One-time` as the billing type. -- If you intend to let your users switch between two subscription plans, e.g. upgrade from hobby to pro, you'll need to create two separate products and with their own price IDs. The ability for users to swich plans can then be configured later in the [Customer Portal](#set-up-the-customer-portal). -- If you want to add different price tiers for the same product (e.g. monthly and yearly), click the `Add another price` button at the buttom. - -- After you save the product, you'll be directed to the product page. -- Copy the price IDs and paste them in the `.env.server` file - - We've set you up with two example subscription product environment variables, `PAYMENTS_HOBBY_SUBSCRIPTION_PLAN_ID=` and `PAYMENTS_PRO_SUBSCRIPTION_PLAN_ID=`. - - As well as a one-time payment product/credits-based environment variable, `PAYMENTS_CREDITS_10_PLAN_ID=`. -- Note that if you change the names of the price IDs, you'll need to update your server code to match these names as well - -### Create a Test Customer - -To create a test customer, go to the test customers url [https://dashboard.stripe.com/test/customers](https://dashboard.stripe.com/test/customers). - -- Click on the `Add a customer` button and fill in the relevant information for your test customer. -:::note - When filling in the test customer email address, use an address you have access to and will use when logging into your SaaS app. This is important because the email address is used to identify the customer when creating a subscription and allows you to manage your test user's payments/subscriptions via the test customer portal -::: - -### Set up the Customer Portal - -Go to https://dashboard.stripe.com/test/settings/billing/portal in the Stripe Dashboard and activate and copy the `Customer portal link`. Paste it in your `.env.server` file: - -```ts title=".env.server" -STRIPE_CUSTOMER_PORTAL_URL= -``` - -If you'd like to give users the ability to switch between different plans, e.g. upgrade from a hobby to a pro subscription, go down to the `Subscriptions` dropdown and select `customers can switch plans`. - -Then select the products you'd like them to be able to switch between. - -Now, after your users have paid, they can click on `Manage Subscription` in the client and will be taken to the customer portal where they can update their current plan. - -### Install the Stripe CLI - -To install the Stripe CLI with homebrew, run the following command in your terminal: - -```sh -brew install stripe/stripe-cli/stripe -``` - -or for other install scripts or OSes, follow the instructions [here](https://stripe.com/docs/stripe-cli#install). - -Now, let's start the webhook server and get our webhook signing secret. - -First, login: -```sh -stripe login -``` - -:::caution[Errors running the Stripe CLI] -If you're seeing errors, consider appending `sudo` to the stripe commands. -See this [GitHuh issue](https://github.com/stripe/stripe-cli/issues/933) for more details. -::: - -```sh -stripe listen --forward-to localhost:3001/payments-webhook -``` - -You should see a message like this: - -```sh -> Ready! You are using Stripe API Version [2023-08-16]. Your webhook signing secret is whsec_8a... (^C to quit) -``` - -copy this secret to your `.env.server` file under `STRIPE_WEBHOOK_SECRET=`. - -### Testing Webhooks via the Stripe CLI - -- In a new terminal window, run the following command: - -```sh -stripe login -``` - -- start the Stripe CLI webhook forwarding on port 3001 where your Node server is running. - -```sh -stripe listen --forward-to localhost:3001/payments-webhook -``` - -:::caution[Webhook URL] -In older versions of this template, the webhook URL was `http://localhost:3001/stripe-webhook`. -If you're using an older version, **make sure to use the url that matches the webhook url in your `main.wasp` file payemnts API definition.** -::: - -remember to copy and paste the outputted webhook signing secret (`whsec_...`) into your `.env.server` file under `STRIPE_WEBHOOK_SECRET=` if you haven't already. - -- In another terminal window, trigger a test event: - -```sh -stripe trigger payment_intent.succeeded -``` - -The results of the event firing will be visible in the initial terminal window. You should see messages like this: - -```sh -... -2023-11-21 09:31:09 --> invoice.paid [evt_1OEpMPILOQf67J5TjrUgRpk4] -2023-11-21 09:31:09 <-- [200] POST http://localhost:3001/payments-webhook [evt_1OEpMPILOQf67J5TjrUgRpk4] -2023-11-21 09:31:10 --> invoice.payment_succeeded [evt_1OEpMPILOQf67J5T3MFBr1bq] -2023-11-21 09:31:10 <-- [200] POST http://localhost:3001/payments-webhook [evt_1OEpMPILOQf67J5T3MFBr1bq] -2023-11-21 09:31:10 --> checkout.session.completed [evt_1OEpMQILOQf67J5ThTZ0999r] -2023-11-21 09:31:11 <-- [200] POST http://localhost:3001/payments-webhook [evt_1OEpMQILOQf67J5ThTZ0999r] -``` - -For more info on testing webhooks, check out https://stripe.com/docs/webhooks#test-webhook - -:::tip[Star our Repo on GitHub! ] -We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free! - -If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp) -::: - -### Testing Checkout and Payments via the Client - -Make sure the **Stripe CLI is running** by following the steps above. -You can then test the payment flow via the client by doing the following: - -- Click on a Buy button on the for any of the products on the homepage. You should be redirected to the checkout page. -- Fill in the form with the following test credit card number `4242 4242 4242 4242` and any future date for the expiration date and any 3 digits for the CVC. - -- Click on the "Pay" button. You should be redirected to the success page. - -- Check your terminal window for status messages and logs - -- You can also check your Database via the DB Studio to see if the user entity has been updated by running: - -```sh -wasp db studio -``` - -- Navigate to `localhost:5555` and click on the `users` table. You should see the `subscriptionStatus` is `active` for the user that just made the purchase. - -:::note -If you want to learn more about how a user's payment status, subscription status, and subscription tier affect a user's priveledges within the app, check out the [User Overview](/general/user-overview) reference. -::: - -## Lemon Squeezy - -First, make sure you've defined your payment processor in `src/payment/paymentProcessor.ts`, as described in the [important first steps](#important-first-steps). - -Next, you'll need to create a Lemon Squeezy account in test mode. You can do that [here](https://lemonsqueezy.com). - -:::tip[Star our Repo on GitHub! ] -We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free! - -If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp) -::: - -### Get your test Lemon Squeezy API Keys - -Once you've created your account, you'll need to get your test API keys. You can do that by navigating to [https://app.lemonsqueezy.com/settings/api](https://app.lemonsqueezy.com/settings/api) and creating a new API key. - -- Click on the `+` button -- Give your API key a name -- Copy and paste it in your `.env.server` file under `LEMONSQUEEZY_API_KEY=` - -### Get your Lemon Squeezy Store ID - -To get your store ID, go to the [Lemon Squeezy Dashboard](https://app.lemonsqueezy.com/settings/stores) and copy the `Store ID` from the top right corner. - -Copy and paste this number in your `.env.server` file under `LEMONSQUEEZY_STORE_ID=` - -### Create Test Products - -To create a test product, go to the test products url [https://app.lemonsqueezy.com/products](https://app.lemonsqueezy.com/products). - -- Click on the `+ New Product` button and fill in the relevant information for your product. -- Fill in the general information. -- For pricing, select the type of product you'd like to create, e.g. `Subscription` for a recurring monthly payment product or `Single Payment` for credits-based product. - -- Make sure you select `Software as a service (SaaS)` as the Tax category type. -- If you want to add different price tiers for `Subscription` products, click on `add variant` under the `variants` tab. Here you can input the name of the variant (e.g. "Hobby", "Pro"), and that variant's price. - -- For a product with no variants, on the product page, click the `...` menu button and select `Copy variant ID` - -- For a product with variants, on the product page, click on the product, go to the variants tab and select `Copy ID` for each variant. - -- Paste these IDs in the `.env.server` file: - - We've set you up with two example subscription product environment variables, `PAYMENTS_HOBBY_SUBSCRIPTION_PLAN_ID=` and `PAYMENTS_PRO_SUBSCRIPTION_PLAN_ID=`. - - As well as a one-time payment product/credits-based environment variable, `PAYMENTS_CREDITS__10_PLAN_ID=`. -- Note that if you change the names of the these environment variables, you'll need to update your app code to match these names as well. - -### Create and Use the Lemon Squeezy Webhook in Local Development - -Lemon Squeezy sends messages/updates to your Wasp app via its webhook, e.g. when a payment is successful. For that to work during development, we need to expose our locally running (via `wasp start`) Wasp app and make it available online, specifically the server part of it. Since the Wasp server runs on port 3001, you should run ngrok on port 3001, which will provide you with a public URL that you can use to configure Lemon Squeezy with. - -To do this, first make sure you have installed [ngrok](https://ngrok.com/docs/getting-started/). - -Once installed, and with your wasp app running, run: -```sh -ngrok http 3001 -``` - -Ngrok will output a forwarding address for you. Copy and paste this address and add `/payments-webhook` to the end (this URL path has been configured for you already in `main.wasp` under the `api paymentsWebhook` definition). It should look something like this: - -```sh title="Callback URL" -https://89e5-2003-c7-153c-72a5-f837.ngrok-free.app/payments-webhook -``` - -Now go to your [Lemon Squeezy Webhooks Dashboard](https://app.lemonsqueezy.com/settings/webhooks): -- click the `+` button. -- add the newly created webhook forwarding url to the `Callback URL` section. -- give your webhook a signing secret (a long, random string). -- copy and paste this same signing secret into your `.env.server` file under `LEMONSQUEEZY_WEBHOOK_SECRET=` -- make sure to select at least the following updates to be sent: - - order_created - - subscription_created - - subscription_updated - - subscription_cancelled -- click `save` - -You're now ready to start consuming Lemon Squeezy webhook events in local development. - -## Deploying - -Once you deploy your app, you can follow the same steps, just make sure that you are no longer in test mode within the Stripe or Lemon Squeezy Dashboards. After you've repeated the steps in live mode, add the new API keys and price/variant IDs to your environment variables in your deployed environment. - ---- - -# SEO - -This guides explains how to improve SEO for of your app - -## Landing Page Meta Tags - -Wasp gives you the ability to add meta tags to your landing page HTML via the `main.wasp` file's `head` property: - -```js {8-11} -app SaaSTemplate { - wasp: { - version: "^0.13.0" - }, - title: "Open SaaS", - head: [ - "", - "", - "", - "", - "", - //... - ], - //... -``` - -Change the above highlighted meta tags to match your app. Wasp will inject these tags into the HTML of your `index.html` file, which is the Landing Page (`app/src/client/landing-page/LandingPage.tsx`), in this case. - -This means you **do not** need to rely on a separate app or framework to serve your landing page for SEO purposes. - -:::tip[Star our Repo on GitHub! ] -We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free! - -If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp) -::: - -## Docs & Blog Meta Tags - -Astro, being a static-site generator, will automatically inject relevant information provided in the `blog/astro.config.mjs` file, as well as in the frontmatter of `.md` files into the pages HTML: - -```yaml ---- -title: 'My First Blog Post' -pubDate: 2022-07-01 -description: 'This is the first post of my new Astro blog.' -author: 'Astro Learner' -image: - url: 'https://docs.astro.build/assets/full-logo-light.png' - alt: 'The full Astro logo.' -tags: ["astro", "blogging", "learning in public"] ---- -``` - -Improving your SEO is as simple as adding these properties to your docs and blog content! - -## A Word on SSR & SEO - -Open SaaS and Wasp do not currently have a SSR option (although it is coming soon!), but that does not mean that Open SaaS apps are at a disadvantage with regards to SEO. - -That's because the meta tags for the landing page (described above), plus the Astro docs/blog provided with Open SaaS are more than enough! Not to mention, Google is also able to crawl websites with JavaScript activated, making SSR unnecessary. - -For example, try searching "Open SaaS" on Google and you'll see this App, which was built with this template, as the first result! - ---- - -# Tests - -This guide will show you how to use the included end-to-end (e2e) tests for your Open SaaS application. - -## The Tests Directory - -In the root of your project, you'll find an `e2e-tests` directory which contains the [Playwright](https://playwright.dev) tests for your Open SaaS application.: - -``` -. - e2e-tests/ - tests/ # Directory containing the test files - README.md # Instructions on how to run the tests - ci-start-app-and-db.js # Script to start the app and db for CI - playwright.config.ts # Playwright configuration - package.json - ... -``` - -To run the tests locally, or in a CI pipeline, follow the instructions in the `README.md` file in the `e2e-tests` directory. - -## Using Tests in CI with GitHub Actions -Although the Open SaaS template does not come with an example workflow, you can find one at `.github/workflows/e2e-tests.yml` of the [remote repo](https://github.com/wasp-lang/open-saas). - -You can copy and paste the `.github/` directory containing the `e2e-tests.yml` workflow into the root of your own repository to run the tests as part of your CI pipeline. - -:::caution[WASP_VERSION] -Please make sure to update the `WASP_VERSION` environment variable in the `e2e-tests.yml` file to match the version of Wasp you are using in your project. -::: - -In order for these tests to run correctly on GitHub, you need to provide the environment variables mentioned in the `e2e-tests.yml` file within your GitHub repository's "Actions" secrets so that they can be accessed by the tests. - ---- - -# How (Not) to Update Your Open SaaS App - -:::danger[We advise against merging the latest template changes into your app] -If you've already started building your app, we generally advise against merging the latest template changes into your app. - -Below we outline our reasoning why, and provide a basic guide to help you update your app if you decide to do so anyway. -::: - -## Why you probably shouldn't include the latest template changes in your app - -We generally **advise against updating your Open SaaS-based applications** after initial setup. - -Why? - -Because your codebase will naturally diverge from the template as you build your unique application, and any updates we may make to the template may not be compatible with your modified codebase, or your version of Wasp. - -Even if you *really* want to include a new feature from the template in your app, proceed with caution and thoroughly consider the following: - -- Changes to the template may be tightly coupled. Implementing one change without related ones could cause unexpected issues. -- Updates might not be compatible with your version of Wasp. -- The more your codebase has diverged, the more challenging the update will be. - -## If you still decide to update your app - -If you read above, considered the risks, and still need specific improvements, we recommend that you manually merge the changes. - -To do this, you should can either 1) merge new Open SaaS template changes into your current project, or 2) merge project changes into a fresh Open SaaS template. - -1) Merge new Open SaaS template changes into your current project by: -- reviewing the latest commits, -- understanding what happened, -- being mindful of the Wasp version you're using, -- and then fitting those changes into your own codebase. - -2) Merge your project changes into a fresh Open SaaS template by: -- starting a new, fresh project with the latest Open SaaS template, -- and then copying over the logic from your existing project that you want to keep. - -The method you choose is up to you and will largely depend on the complexity of the changes you need to make. - ---- - -# Vibe Coding with Open SaaS - -If you're looking to use AI to help build (or "vibe code") your SaaS app, this guide is for you. - -## Coding with AI, Open SaaS, & Wasp - -Wasp is particularly well suited to coding with AI due to its central config file which gives LLMs context about the entire full-stack app, and its ability to manage boilerplate code so AI doesn't have to. - -Regardless, there are still some shortcomings to using AI to code with Wasp, as well as a learning curve to using it effectively. - -Luckily, we did the work for you and put together a bunch of resources to help you use Wasp & Open SaaS with AI as effectively as possible. - -### AI Resources in the Template - -The template comes with: -- A full set of rules files, `app/.cursor/rules`, to be used with Cursor or adapted to your coding tool of choice (Windsurf, Claude Code, etc.). -- A set of example prompts, `app/.cursor/example-prompts.md`, to help you get started. - -### LLM-Friendly Documentation - -We've also created a bunch of LLM-friendly documentation: -- [Open SaaS Docs - LLMs.txt](https://docs.opensaas.sh/llms.txt) - Links to the raw text docs. -- **[Open SaaS Docs - LLMs-full.txt](https://docs.opensaas.sh/llms-full.txt) - Complete docs as one text file.** -- Coming Soon! ~~[Wasp Docs - LLMs.txt](https://wasp.sh/llms.txt)~~ - Links to the raw text docs. -- Coming Soon! ~~[Wasp Docs - LLMs-full.txt](https://wasp.sh/llms-full.txt)~~ - Complete docs as one text file. - -Add these to your AI-assisted IDE settings so you can easily reference them in your chat sessions with the LLM. -**In most cases, you'll want to pass the `llms-full.txt` to the LLM and ask it to help you with a specific task.** - -### More AI-assisted Coding Learning Resources - -Here's a list of articles and tutorials we've made: -- [3hr YouTube tutorial: Vibe Coding a Personal Finance App w/ Wasp & Cursor](https://www.youtube.com/watch?v=WYzEROo7reY) -- [Article: A Structured Workflow for "Vibe Coding" Full-Stack Apps](https://dev.to/wasp/a-structured-workflow-for-vibe-coding-full-stack-apps-352l) - ---- - -# Admin Dashboard - -This is a reference on how the Admin dashboard, available at `/admin`, is set up. - -## Permissions - -The Admin dashboard is only accessible to users with the `isAdmin` field set to true. - -```tsx title="schema.prisma" {5} -model User { - id Int @id @default(autoincrement()) - email String? @unique - username String? - isAdmin Boolean @default(false) - //... -``` - -To give yourself administrator priveledges, make sure you add your email addresses to the `ADMIN_EMAILS` environment variable in `.env.server` file before registering/logging in with that email address: -```sh title=".env.server" -ADMIN_EMAILS=me@example.com - -// or add many admins with a comma-separated list - -ADMIN_EMAILS=me@example.com,you@example.com,them@example.com -``` - -Or if you've already logged in with an email address that you want to give admin priveledges to, you can run the following command in a separate terminal window to update the user's `isAdmin` field manually: - -```sh -wasp db studio -``` - ---- - -:::tip[Star our Repo on GitHub! ] -We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free! - -If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp) -::: - -## Admin Dashboard Pages - -### Analytics Dashboard -The Admin analytics dashboard is a single place for you to view your most important metrics and perform some admin tasks. At the moment, it pulls data from: - -- [Payments Processor](/guides/payments-integration/): - - total revenue - - revenue for each day of the past week -- [Google or Plausible](/guides/analytics/): - - total number of page views (non-unique) - - percentage change in page views from the previous day - - top sources/referrers with unique visitor count (i.e. how many people came from that source to your app) -- Database: - - total number of registered users - - daily change in number of registered users - - total number of paying users - - daily change in number of paying users - -These metrics are aggregated within the background job `dailyStatsJob`, which by default is run every hour. You can change the frequency of this job by modifying its `cron` field: - -```ts title="main.wasp" {8,7} -job dailyStatsJob { - executor: PgBoss, - perform: { - fn: import { calculateDailyStats } from "@src/analytics/stats" - }, - schedule: { - cron: "0 * * * *" // every hour. useful in production - // cron: "* * * * *" // every minute. useful for debugging - }, - entities: [User, DailyStats, Logs, PageViewSource] -} -``` -For more info on Wasp's recurring background jobs, check out the [Wasp Jobs docs](https://wasp.sh/docs/advanced/jobs). - -For a guide on how to integrate these services so that you can view your analytics via the dashboard, check out the [Payments Integration](/guides/payments-integration/) and [Analytics guide](/guides/analytics/) of the docs. - -:::note[Help us improve] -We're always looking to improve the Admin dashboard. If you feel something is missing or could be improved, consider [opening an issue](https://github.com/wasp-lang/open-saas/issues) or [submitting a pull request](https://github.com/wasp-lang/open-saas/pulls) -::: - -### Users -The Users page is where you can view all your users and their most important details. You can also search and filter users by: -- email address -- subscription/payment status -- admin status - ---- - -# User Overview - -This reference will help you understand how the User entity works in this template. -This includes the user roles, subscription plans and statuses, and how to authorize access to certain pages and components. - -## User Entity - -The `User` entity within your app is defined in the `schema.prisma` file: - -```tsx title="schema.prisma" ins="User: {}" -model User { - id Int @id @default(autoincrement()) - email String? @unique - username String? - createdAt DateTime @default(now()) - isAdmin Boolean @default(false) - paymentProcessorUserId String? @unique - lemonSqueezyCustomerPortalUrl String? // You can delete this if you're not using Lemon Squeezy as your payments processor. - subscriptionPlan String? - subscriptionStatus String? - sendEmail Boolean @default(false) - datePaid DateTime? - credits Int @default(3) - gptResponses GptResponse[] - contactFormMessages ContactFormMessage[] - tasks Task[] - files File[] -} -``` - -We store all pertinent information to the user, including identification, subscription, and payment processor information. Meanwhile, Wasp abstracts away all the Auth related entities dealing with `passwords`, `sessions`, and `socialLogins`, so you don't have to worry about these at all in your Prisma schema (if you want to learn more about this process, check out the [Wasp Auth Docs](https://wasp.sh/docs/auth/overview)). - -## Stripe and Subscriptions - -We use Stripe to handle all of our subscription payments. The `User` entity has a number of fields that are related to Stripe and their ability to access features behind the paywall: - -```tsx title="schema.prisma" {4-10} -model User { - id Int @id @default(autoincrement()) - //... - paymentProcessorUserId String? @unique - subscriptionPlan String? - subscriptionStatus String? - datePaid DateTime? - credits Int @default(3) - //... -} -``` - -- `paymentProcessorUserId`: The payment processor customer ID. This is created on checkout and used to identify the customer. -- `subscriptionPlan`: The subscription plan the user is on. This is set by the app and is used to determine what features the user has access to. By default, we have three plans: `hobby` and `pro` subscription plans, as well as a `credits10` one-time purchase plan. -- `subscriptionStatus`: The subscription status of the user. This is set by the payment processor and is used to determine whether the user has access to the app or not. By default, we have four statuses: `active`, `past_due`, `cancel_at_period_end`, and `deleted`. -- `credits` (optional): By default, a user is given 3 credits to trial your product before they have to pay. You can create a one-time purchase product in Stripe to allow users to purchase more credits if they run out, e.g. the `credits10` plan in the template. - -### Subscription Statuses - -In general, we determine if a user has paid for an initial subscription by checking if the `subscriptionStatus` field is set. This field is set by Stripe within your webhook handler and is used to signify more detailed information on the user's current status. By default, the template handles four statuses: `active`, `past_due`, `cancel_at_period_end`, and `deleted`. - -- When `active` the user has paid for a subscription and has full access to the app. - -- When `cancel_at_period_end`, the user has canceled their subscription and has access to the app until the end of their billing period. - -- When `deleted`, the user has reached the end of their subscription period after canceling and no longer has access to the app. - -- When `past_due`, the user's automatic subscription renewal payment was declined (e.g. their credit card expired). You can choose how to handle this status within your app. For example, you can send the user an email to update their payment information: -```tsx title="src/payment/stripe/webhook.ts" - -//... - -if (subscription.status === 'past_due') { - const updatedCustomer = await context.entities.User.update({ - where: { - id: customer.id, - }, - data: { - subscriptionStatus: 'past_due', - }, - }); - - if (updatedCustomer.email) { - await emailSender.send({ - to: updatedCustomer.email, - subject: 'Your Payment is Past Due', - text: 'Please update your payment information to continue using our service.', - html: '...', - }); - } -} -``` - -See the client-side [authorization section](/guides/authorization/) below for more info on how to handle these statuses within your app. - -### Subscription Plans - -The `subscriptionPlan` field is used to determine what features the user has access to. - -By default, we have three plans: `hobby` and `pro` subscription plans, as well as a `credits10` one-time purchase plan. - -You can add more plans by adding more products and price IDs to your Stripe product and updating environment variables in your `.env.server` file as well as the relevant code in your app. - -See the [Payments Integration Guide](/guides/payments-integration/) for more info on how to do this. - -## User Roles - -At the moment, we have two user roles: `admin` and `user`. This is defined within the `isAdmin` field in the `User` entity: - -```tsx title="schema.prisma" {7} -model User { - id Int @id @default(autoincrement()) - email String? @unique - username String? - createdAt DateTime @default(now()) - isAdmin Boolean @default(false) -//... -} -``` - -As an Admin, a user has access to the Admin dashboard, along with the user table where they can view and search for users, and edit and update information manually if necessary. - -:::tip[Admin Privileges] -If you'd like to give yourself and/or certain users admin privileges, follow the instructions in the [Admin Dashboard](/general/admin-dashboard/#permissions) section. -::: - -As a general User, a user has access to the user-facing app that sits behind the login, but not the Admin dashboard. You can further restrict access to certain features within the app by following the [authorization guide](/guides/authorization/). - ---- \ No newline at end of file diff --git a/opensaas-sh/blog/public/llms.txt b/opensaas-sh/blog/public/llms.txt deleted file mode 100644 index eb3fffea3..000000000 --- a/opensaas-sh/blog/public/llms.txt +++ /dev/null @@ -1,25 +0,0 @@ -# Open SaaS Documentation for LLMs - -> Open SaaS is a free, open-source, full-stack starter kit for building SaaS applications quickly. It leverages the [Wasp](https://wasp.sh/docs) framework (React, Node.js, Prisma) and integrates with various services like Stripe/Lemon Squeezy, OpenAI API, AWS S3, and different email providers. - -## Full Documentation - - [View Complete LLM-formatted Open SaaS Documentation](https://docs.opensaas.sh/llms-full.txt) - -## Individual documentation sections and guides: -- [Introduction](https://raw.githubusercontent.com/wasp-lang/open-saas/main/opensaas-sh/blog/src/content/docs/index.mdx) -- [Getting Started](https://raw.githubusercontent.com/wasp-lang/open-saas/main/opensaas-sh/blog/src/content/docs/start/getting-started.mdx) -- [Guided Tour](https://raw.githubusercontent.com/wasp-lang/open-saas/main/opensaas-sh/blog/src/content/docs/start/guided-tour.md) -- [Analytics](https://raw.githubusercontent.com/wasp-lang/open-saas/main/opensaas-sh/blog/src/content/docs/guides/analytics.md) -- [Authentication](https://raw.githubusercontent.com/wasp-lang/open-saas/main/opensaas-sh/blog/src/content/docs/guides/authentication.md) -- [Authorization](https://raw.githubusercontent.com/wasp-lang/open-saas/main/opensaas-sh/blog/src/content/docs/guides/authorization.md) -- [Cookie Consent Modal](https://raw.githubusercontent.com/wasp-lang/open-saas/main/opensaas-sh/blog/src/content/docs/guides/cookie-consent.mdx) -- [Deploying](https://raw.githubusercontent.com/wasp-lang/open-saas/main/opensaas-sh/blog/src/content/docs/guides/deploying.mdx) -- [Email Sending](https://raw.githubusercontent.com/wasp-lang/open-saas/main/opensaas-sh/blog/src/content/docs/guides/email-sending.mdx) -- [File Uploading](https://raw.githubusercontent.com/wasp-lang/open-saas/main/opensaas-sh/blog/src/content/docs/guides/file-uploading.mdx) -- [Payments Integration](https://raw.githubusercontent.com/wasp-lang/open-saas/main/opensaas-sh/blog/src/content/docs/guides/payments-integration.mdx) -- [SEO](https://raw.githubusercontent.com/wasp-lang/open-saas/main/opensaas-sh/blog/src/content/docs/guides/seo.mdx) -- [Tests](https://raw.githubusercontent.com/wasp-lang/open-saas/main/opensaas-sh/blog/src/content/docs/guides/tests.md) -- [How (Not) to Update Your Open SaaS App](https://raw.githubusercontent.com/wasp-lang/open-saas/main/opensaas-sh/blog/src/content/docs/guides/updating-opensaas.md) -- [Vibe Coding with Open SaaS](https://raw.githubusercontent.com/wasp-lang/open-saas/main/opensaas-sh/blog/src/content/docs/guides/vibe-coding.mdx) -- [Admin Dashboard](https://raw.githubusercontent.com/wasp-lang/open-saas/main/opensaas-sh/blog/src/content/docs/general/admin-dashboard.mdx) -- [User Overview](https://raw.githubusercontent.com/wasp-lang/open-saas/main/opensaas-sh/blog/src/content/docs/general/user-overview.md) \ No newline at end of file diff --git a/opensaas-sh/blog/public/martin.jpg b/opensaas-sh/blog/public/martin.jpg index 6fc6607ce..165c66d4f 100644 Binary files a/opensaas-sh/blog/public/martin.jpg and b/opensaas-sh/blog/public/martin.jpg differ diff --git a/opensaas-sh/blog/public/matija.jpeg b/opensaas-sh/blog/public/matija.jpeg index ee7387634..890e02d5a 100644 Binary files a/opensaas-sh/blog/public/matija.jpeg and b/opensaas-sh/blog/public/matija.jpeg differ diff --git a/opensaas-sh/blog/public/milica.jpg b/opensaas-sh/blog/public/milica.jpg index 50c5f8b19..ec8c0ee59 100644 Binary files a/opensaas-sh/blog/public/milica.jpg and b/opensaas-sh/blog/public/milica.jpg differ diff --git a/opensaas-sh/blog/src/assets/admin/admin-dashboard.png b/opensaas-sh/blog/src/assets/admin/admin-dashboard.png index 4630af2a1..a1071d53d 100644 Binary files a/opensaas-sh/blog/src/assets/admin/admin-dashboard.png and b/opensaas-sh/blog/src/assets/admin/admin-dashboard.png differ diff --git a/opensaas-sh/blog/src/assets/ai/vibe-boi.png b/opensaas-sh/blog/src/assets/ai/vibe-boi.png index 421e6b4f4..672b0242c 100644 Binary files a/opensaas-sh/blog/src/assets/ai/vibe-boi.png and b/opensaas-sh/blog/src/assets/ai/vibe-boi.png differ diff --git a/opensaas-sh/blog/src/assets/boilerplate-starters/boilerplate-licenses.png b/opensaas-sh/blog/src/assets/boilerplate-starters/boilerplate-licenses.png index b0fe34c16..7f12ac5d2 100644 Binary files a/opensaas-sh/blog/src/assets/boilerplate-starters/boilerplate-licenses.png and b/opensaas-sh/blog/src/assets/boilerplate-starters/boilerplate-licenses.png differ diff --git a/opensaas-sh/blog/src/assets/boilerplate-starters/community-contributions.png b/opensaas-sh/blog/src/assets/boilerplate-starters/community-contributions.png index fd8f07ca6..762b77bf9 100644 Binary files a/opensaas-sh/blog/src/assets/boilerplate-starters/community-contributions.png and b/opensaas-sh/blog/src/assets/boilerplate-starters/community-contributions.png differ diff --git a/opensaas-sh/blog/src/assets/boilerplate-starters/dave-shipfast-tweet.png b/opensaas-sh/blog/src/assets/boilerplate-starters/dave-shipfast-tweet.png index 7d7d477c4..aab40fb59 100644 Binary files a/opensaas-sh/blog/src/assets/boilerplate-starters/dave-shipfast-tweet.png and b/opensaas-sh/blog/src/assets/boilerplate-starters/dave-shipfast-tweet.png differ diff --git a/opensaas-sh/blog/src/assets/boilerplate-starters/free-updates-vs-not.png b/opensaas-sh/blog/src/assets/boilerplate-starters/free-updates-vs-not.png index e0ef5f7b8..0c5c805a6 100644 Binary files a/opensaas-sh/blog/src/assets/boilerplate-starters/free-updates-vs-not.png and b/opensaas-sh/blog/src/assets/boilerplate-starters/free-updates-vs-not.png differ diff --git a/opensaas-sh/blog/src/assets/boilerplate-starters/marc1.png b/opensaas-sh/blog/src/assets/boilerplate-starters/marc1.png index 91875677d..d0aec03a9 100644 Binary files a/opensaas-sh/blog/src/assets/boilerplate-starters/marc1.png and b/opensaas-sh/blog/src/assets/boilerplate-starters/marc1.png differ diff --git a/opensaas-sh/blog/src/assets/boilerplate-starters/marc2.png b/opensaas-sh/blog/src/assets/boilerplate-starters/marc2.png index 9ec45e185..5deaec255 100644 Binary files a/opensaas-sh/blog/src/assets/boilerplate-starters/marc2.png and b/opensaas-sh/blog/src/assets/boilerplate-starters/marc2.png differ diff --git a/opensaas-sh/blog/src/assets/boilerplate-starters/opensaas.png b/opensaas-sh/blog/src/assets/boilerplate-starters/opensaas.png index 869011c96..db3b0c417 100644 Binary files a/opensaas-sh/blog/src/assets/boilerplate-starters/opensaas.png and b/opensaas-sh/blog/src/assets/boilerplate-starters/opensaas.png differ diff --git a/opensaas-sh/blog/src/assets/boilerplate-starters/os-commits.png b/opensaas-sh/blog/src/assets/boilerplate-starters/os-commits.png index b3f73685f..94f9a76a4 100644 Binary files a/opensaas-sh/blog/src/assets/boilerplate-starters/os-commits.png and b/opensaas-sh/blog/src/assets/boilerplate-starters/os-commits.png differ diff --git a/opensaas-sh/blog/src/assets/boilerplate-starters/os-gh-stats.png b/opensaas-sh/blog/src/assets/boilerplate-starters/os-gh-stats.png index c74204f6d..ed7be43b2 100644 Binary files a/opensaas-sh/blog/src/assets/boilerplate-starters/os-gh-stats.png and b/opensaas-sh/blog/src/assets/boilerplate-starters/os-gh-stats.png differ diff --git a/opensaas-sh/blog/src/assets/cookie-consent/annoying-cookie-banners.jpg b/opensaas-sh/blog/src/assets/cookie-consent/annoying-cookie-banners.jpg index 1108980ed..39975d1c6 100644 Binary files a/opensaas-sh/blog/src/assets/cookie-consent/annoying-cookie-banners.jpg and b/opensaas-sh/blog/src/assets/cookie-consent/annoying-cookie-banners.jpg differ diff --git a/opensaas-sh/blog/src/assets/cookie-consent/cookiebanner.png b/opensaas-sh/blog/src/assets/cookie-consent/cookiebanner.png index 8e36eb680..d66ed7451 100644 Binary files a/opensaas-sh/blog/src/assets/cookie-consent/cookiebanner.png and b/opensaas-sh/blog/src/assets/cookie-consent/cookiebanner.png differ diff --git a/opensaas-sh/blog/src/assets/cookie-consent/image.png b/opensaas-sh/blog/src/assets/cookie-consent/image.png index 4274a55b1..749f416f4 100644 Binary files a/opensaas-sh/blog/src/assets/cookie-consent/image.png and b/opensaas-sh/blog/src/assets/cookie-consent/image.png differ diff --git a/opensaas-sh/blog/src/assets/cookie-consent/keyboard.jpg b/opensaas-sh/blog/src/assets/cookie-consent/keyboard.jpg index 3578a0758..975f7bf77 100644 Binary files a/opensaas-sh/blog/src/assets/cookie-consent/keyboard.jpg and b/opensaas-sh/blog/src/assets/cookie-consent/keyboard.jpg differ diff --git a/opensaas-sh/blog/src/assets/cookie-consent/preferences.png b/opensaas-sh/blog/src/assets/cookie-consent/preferences.png index 00923c1c2..d1ee0c638 100644 Binary files a/opensaas-sh/blog/src/assets/cookie-consent/preferences.png and b/opensaas-sh/blog/src/assets/cookie-consent/preferences.png differ diff --git a/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-indiehackers.png b/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-indiehackers.png index e5ba99c38..60ac47483 100644 Binary files a/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-indiehackers.png and b/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-indiehackers.png differ diff --git a/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-may2025.png b/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-may2025.png index ce80f2d97..6d6702aeb 100644 Binary files a/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-may2025.png and b/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-may2025.png differ diff --git a/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-ph-launch.png b/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-ph-launch.png index 8b3a9edbf..8437e959e 100644 Binary files a/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-ph-launch.png and b/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-ph-launch.png differ diff --git a/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-plans.png b/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-plans.png index e71500f95..f35da6f31 100644 Binary files a/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-plans.png and b/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-plans.png differ diff --git a/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-reddit.png b/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-reddit.png index 89e3ac931..07c9342ba 100644 Binary files a/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-reddit.png and b/opensaas-sh/blog/src/assets/cover-letter-gpt/coverlettergpt-reddit.png differ diff --git a/opensaas-sh/blog/src/assets/cover-letter-gpt/jeep.png b/opensaas-sh/blog/src/assets/cover-letter-gpt/jeep.png index ccc2a7a29..5cee4e54e 100644 Binary files a/opensaas-sh/blog/src/assets/cover-letter-gpt/jeep.png and b/opensaas-sh/blog/src/assets/cover-letter-gpt/jeep.png differ diff --git a/opensaas-sh/blog/src/assets/cover-letter-gpt/mrr-growth.png b/opensaas-sh/blog/src/assets/cover-letter-gpt/mrr-growth.png index 0c5a5e903..04c94c904 100644 Binary files a/opensaas-sh/blog/src/assets/cover-letter-gpt/mrr-growth.png and b/opensaas-sh/blog/src/assets/cover-letter-gpt/mrr-growth.png differ diff --git a/opensaas-sh/blog/src/assets/cover-letter-gpt/mrr-revenue.png b/opensaas-sh/blog/src/assets/cover-letter-gpt/mrr-revenue.png index c7f512f35..6d2f2a6e8 100644 Binary files a/opensaas-sh/blog/src/assets/cover-letter-gpt/mrr-revenue.png and b/opensaas-sh/blog/src/assets/cover-letter-gpt/mrr-revenue.png differ diff --git a/opensaas-sh/blog/src/assets/cover-letter-gpt/openai-cost.png b/opensaas-sh/blog/src/assets/cover-letter-gpt/openai-cost.png index 2f1ab6ad6..c03018d7a 100644 Binary files a/opensaas-sh/blog/src/assets/cover-letter-gpt/openai-cost.png and b/opensaas-sh/blog/src/assets/cover-letter-gpt/openai-cost.png differ diff --git a/opensaas-sh/blog/src/assets/cover-letter-gpt/openai-requests.png b/opensaas-sh/blog/src/assets/cover-letter-gpt/openai-requests.png index c56218fe5..9a97e6c9a 100644 Binary files a/opensaas-sh/blog/src/assets/cover-letter-gpt/openai-requests.png and b/opensaas-sh/blog/src/assets/cover-letter-gpt/openai-requests.png differ diff --git a/opensaas-sh/blog/src/assets/file-uploads/cors.png b/opensaas-sh/blog/src/assets/file-uploads/cors.png index a16ddabef..93e78f2be 100644 Binary files a/opensaas-sh/blog/src/assets/file-uploads/cors.png and b/opensaas-sh/blog/src/assets/file-uploads/cors.png differ diff --git a/opensaas-sh/blog/src/assets/file-uploads/create-bucket.png b/opensaas-sh/blog/src/assets/file-uploads/create-bucket.png index 4f384c9cb..fe43af6bf 100644 Binary files a/opensaas-sh/blog/src/assets/file-uploads/create-bucket.png and b/opensaas-sh/blog/src/assets/file-uploads/create-bucket.png differ diff --git a/opensaas-sh/blog/src/assets/file-uploads/default-settings.png b/opensaas-sh/blog/src/assets/file-uploads/default-settings.png index 8f074b49b..5071ddb63 100644 Binary files a/opensaas-sh/blog/src/assets/file-uploads/default-settings.png and b/opensaas-sh/blog/src/assets/file-uploads/default-settings.png differ diff --git a/opensaas-sh/blog/src/assets/file-uploads/find-s3.png b/opensaas-sh/blog/src/assets/file-uploads/find-s3.png index ef3e43b4d..693871807 100644 Binary files a/opensaas-sh/blog/src/assets/file-uploads/find-s3.png and b/opensaas-sh/blog/src/assets/file-uploads/find-s3.png differ diff --git a/opensaas-sh/blog/src/assets/file-uploads/keys.png b/opensaas-sh/blog/src/assets/file-uploads/keys.png index 07888a844..695bae4ca 100644 Binary files a/opensaas-sh/blog/src/assets/file-uploads/keys.png and b/opensaas-sh/blog/src/assets/file-uploads/keys.png differ diff --git a/opensaas-sh/blog/src/assets/file-uploads/new-bucket.png b/opensaas-sh/blog/src/assets/file-uploads/new-bucket.png index c399a1a27..ed02b9c54 100644 Binary files a/opensaas-sh/blog/src/assets/file-uploads/new-bucket.png and b/opensaas-sh/blog/src/assets/file-uploads/new-bucket.png differ diff --git a/opensaas-sh/blog/src/assets/file-uploads/permissions.png b/opensaas-sh/blog/src/assets/file-uploads/permissions.png index 0f1703d90..81292ec2a 100644 Binary files a/opensaas-sh/blog/src/assets/file-uploads/permissions.png and b/opensaas-sh/blog/src/assets/file-uploads/permissions.png differ diff --git a/opensaas-sh/blog/src/assets/file-uploads/username.png b/opensaas-sh/blog/src/assets/file-uploads/username.png index a832ff493..fccd1bed5 100644 Binary files a/opensaas-sh/blog/src/assets/file-uploads/username.png and b/opensaas-sh/blog/src/assets/file-uploads/username.png differ diff --git a/opensaas-sh/blog/src/assets/gemini-3/chat.webp b/opensaas-sh/blog/src/assets/gemini-3/chat.webp new file mode 100644 index 000000000..8644bdd1e Binary files /dev/null and b/opensaas-sh/blog/src/assets/gemini-3/chat.webp differ diff --git a/opensaas-sh/blog/src/assets/gemini-3/landing-page.webp b/opensaas-sh/blog/src/assets/gemini-3/landing-page.webp new file mode 100644 index 000000000..5e30d7ac2 Binary files /dev/null and b/opensaas-sh/blog/src/assets/gemini-3/landing-page.webp differ diff --git a/opensaas-sh/blog/src/assets/lemon-squeezy/add-product.png b/opensaas-sh/blog/src/assets/lemon-squeezy/add-product.png index 5e65c37df..a83f1b4ee 100644 Binary files a/opensaas-sh/blog/src/assets/lemon-squeezy/add-product.png and b/opensaas-sh/blog/src/assets/lemon-squeezy/add-product.png differ diff --git a/opensaas-sh/blog/src/assets/lemon-squeezy/add-variant.png b/opensaas-sh/blog/src/assets/lemon-squeezy/add-variant.png index d78868e6a..c2d8a87af 100644 Binary files a/opensaas-sh/blog/src/assets/lemon-squeezy/add-variant.png and b/opensaas-sh/blog/src/assets/lemon-squeezy/add-variant.png differ diff --git a/opensaas-sh/blog/src/assets/lemon-squeezy/ngrok.png b/opensaas-sh/blog/src/assets/lemon-squeezy/ngrok.png deleted file mode 100644 index b5f085ec4..000000000 Binary files a/opensaas-sh/blog/src/assets/lemon-squeezy/ngrok.png and /dev/null differ diff --git a/opensaas-sh/blog/src/assets/lemon-squeezy/store-id.png b/opensaas-sh/blog/src/assets/lemon-squeezy/store-id.png index 94943d387..34d277f7a 100644 Binary files a/opensaas-sh/blog/src/assets/lemon-squeezy/store-id.png and b/opensaas-sh/blog/src/assets/lemon-squeezy/store-id.png differ diff --git a/opensaas-sh/blog/src/assets/lemon-squeezy/subscription-variant-ids.png b/opensaas-sh/blog/src/assets/lemon-squeezy/subscription-variant-ids.png index 4379e2889..81600ebdf 100644 Binary files a/opensaas-sh/blog/src/assets/lemon-squeezy/subscription-variant-ids.png and b/opensaas-sh/blog/src/assets/lemon-squeezy/subscription-variant-ids.png differ diff --git a/opensaas-sh/blog/src/assets/lemon-squeezy/variant-id.png b/opensaas-sh/blog/src/assets/lemon-squeezy/variant-id.png index 22c2b4620..3fcf11c92 100644 Binary files a/opensaas-sh/blog/src/assets/lemon-squeezy/variant-id.png and b/opensaas-sh/blog/src/assets/lemon-squeezy/variant-id.png differ diff --git a/opensaas-sh/blog/src/assets/ngrok.png b/opensaas-sh/blog/src/assets/ngrok.png new file mode 100644 index 000000000..ec3ec18f7 Binary files /dev/null and b/opensaas-sh/blog/src/assets/ngrok.png differ diff --git a/opensaas-sh/blog/src/assets/open-saas-v2/open-saas-banner-light.png b/opensaas-sh/blog/src/assets/open-saas-v2/open-saas-banner-light.png index c577fdd2f..f481df413 100644 Binary files a/opensaas-sh/blog/src/assets/open-saas-v2/open-saas-banner-light.png and b/opensaas-sh/blog/src/assets/open-saas-v2/open-saas-banner-light.png differ diff --git a/opensaas-sh/blog/src/assets/open-saas-v2/playwright-ui.png b/opensaas-sh/blog/src/assets/open-saas-v2/playwright-ui.png index 90900cf96..dcf12eb69 100644 Binary files a/opensaas-sh/blog/src/assets/open-saas-v2/playwright-ui.png and b/opensaas-sh/blog/src/assets/open-saas-v2/playwright-ui.png differ diff --git a/opensaas-sh/blog/src/assets/open-saas-v2/wait-theres-more.jpg b/opensaas-sh/blog/src/assets/open-saas-v2/wait-theres-more.jpg index b00888c25..84b37ba83 100644 Binary files a/opensaas-sh/blog/src/assets/open-saas-v2/wait-theres-more.jpg and b/opensaas-sh/blog/src/assets/open-saas-v2/wait-theres-more.jpg differ diff --git a/opensaas-sh/blog/src/assets/ph/10.png b/opensaas-sh/blog/src/assets/ph/10.png index 00b50bf19..7253cf27a 100644 Binary files a/opensaas-sh/blog/src/assets/ph/10.png and b/opensaas-sh/blog/src/assets/ph/10.png differ diff --git a/opensaas-sh/blog/src/assets/ph/4.png b/opensaas-sh/blog/src/assets/ph/4.png index f62c2b51c..8655fe2c3 100644 Binary files a/opensaas-sh/blog/src/assets/ph/4.png and b/opensaas-sh/blog/src/assets/ph/4.png differ diff --git a/opensaas-sh/blog/src/assets/ph/5.png b/opensaas-sh/blog/src/assets/ph/5.png index 35645c610..a1cceff41 100644 Binary files a/opensaas-sh/blog/src/assets/ph/5.png and b/opensaas-sh/blog/src/assets/ph/5.png differ diff --git a/opensaas-sh/blog/src/assets/ph/9.png b/opensaas-sh/blog/src/assets/ph/9.png index 55df57627..b42bbc8dc 100644 Binary files a/opensaas-sh/blog/src/assets/ph/9.png and b/opensaas-sh/blog/src/assets/ph/9.png differ diff --git a/opensaas-sh/blog/src/assets/ph/compass.png b/opensaas-sh/blog/src/assets/ph/compass.png index 8378ec42b..e1e58adc0 100644 Binary files a/opensaas-sh/blog/src/assets/ph/compass.png and b/opensaas-sh/blog/src/assets/ph/compass.png differ diff --git a/opensaas-sh/blog/src/assets/polar/user-table.png b/opensaas-sh/blog/src/assets/polar/user-table.png new file mode 100644 index 000000000..5e2c9277c Binary files /dev/null and b/opensaas-sh/blog/src/assets/polar/user-table.png differ diff --git a/opensaas-sh/blog/src/assets/polar/webhook-log.png b/opensaas-sh/blog/src/assets/polar/webhook-log.png new file mode 100644 index 000000000..39a560370 Binary files /dev/null and b/opensaas-sh/blog/src/assets/polar/webhook-log.png differ diff --git a/opensaas-sh/blog/src/assets/promptpanda/prompt-panda.png b/opensaas-sh/blog/src/assets/promptpanda/prompt-panda.png index 233919d10..bc3b45bf9 100644 Binary files a/opensaas-sh/blog/src/assets/promptpanda/prompt-panda.png and b/opensaas-sh/blog/src/assets/promptpanda/prompt-panda.png differ diff --git a/opensaas-sh/blog/src/assets/ricardo-growth-hacks/article-generation.png b/opensaas-sh/blog/src/assets/ricardo-growth-hacks/article-generation.png index f3f27dee4..9396f044a 100644 Binary files a/opensaas-sh/blog/src/assets/ricardo-growth-hacks/article-generation.png and b/opensaas-sh/blog/src/assets/ricardo-growth-hacks/article-generation.png differ diff --git a/opensaas-sh/blog/src/assets/ricardo-growth-hacks/meeting-reminders.png b/opensaas-sh/blog/src/assets/ricardo-growth-hacks/meeting-reminders.png index ae721db1e..a587dd7ea 100644 Binary files a/opensaas-sh/blog/src/assets/ricardo-growth-hacks/meeting-reminders.png and b/opensaas-sh/blog/src/assets/ricardo-growth-hacks/meeting-reminders.png differ diff --git a/opensaas-sh/blog/src/assets/roadmap/open-saas-ai-ready.webp b/opensaas-sh/blog/src/assets/roadmap/open-saas-ai-ready.webp new file mode 100644 index 000000000..26ab1dc1c Binary files /dev/null and b/opensaas-sh/blog/src/assets/roadmap/open-saas-ai-ready.webp differ diff --git a/opensaas-sh/blog/src/assets/roadmap/open-saas-epic-detail.webp b/opensaas-sh/blog/src/assets/roadmap/open-saas-epic-detail.webp new file mode 100644 index 000000000..2674291dd Binary files /dev/null and b/opensaas-sh/blog/src/assets/roadmap/open-saas-epic-detail.webp differ diff --git a/opensaas-sh/blog/src/assets/roadmap/open-saas-epic.webp b/opensaas-sh/blog/src/assets/roadmap/open-saas-epic.webp new file mode 100644 index 000000000..5beaf43b0 Binary files /dev/null and b/opensaas-sh/blog/src/assets/roadmap/open-saas-epic.webp differ diff --git a/opensaas-sh/blog/src/assets/roadmap/open-saas-roadmap.webp b/opensaas-sh/blog/src/assets/roadmap/open-saas-roadmap.webp new file mode 100644 index 000000000..a3ad26df9 Binary files /dev/null and b/opensaas-sh/blog/src/assets/roadmap/open-saas-roadmap.webp differ diff --git a/opensaas-sh/blog/src/assets/roadmap/open-saas-used-by.webp b/opensaas-sh/blog/src/assets/roadmap/open-saas-used-by.webp new file mode 100644 index 000000000..bd51df914 Binary files /dev/null and b/opensaas-sh/blog/src/assets/roadmap/open-saas-used-by.webp differ diff --git a/opensaas-sh/blog/src/assets/roadmap/subscribe-github.webp b/opensaas-sh/blog/src/assets/roadmap/subscribe-github.webp new file mode 100644 index 000000000..1b8690487 Binary files /dev/null and b/opensaas-sh/blog/src/assets/roadmap/subscribe-github.webp differ diff --git a/opensaas-sh/blog/src/assets/seo/open-saas-google.png b/opensaas-sh/blog/src/assets/seo/open-saas-google.png index 20e518972..a2ca4d1f6 100644 Binary files a/opensaas-sh/blog/src/assets/seo/open-saas-google.png and b/opensaas-sh/blog/src/assets/seo/open-saas-google.png differ diff --git a/opensaas-sh/blog/src/assets/stripe/api-keys.png b/opensaas-sh/blog/src/assets/stripe/api-keys.png index 5a08167e1..7403c6fdc 100644 Binary files a/opensaas-sh/blog/src/assets/stripe/api-keys.png and b/opensaas-sh/blog/src/assets/stripe/api-keys.png differ diff --git a/opensaas-sh/blog/src/assets/stripe/db-studio.png b/opensaas-sh/blog/src/assets/stripe/db-studio.png index 0fb692377..94e0648cf 100644 Binary files a/opensaas-sh/blog/src/assets/stripe/db-studio.png and b/opensaas-sh/blog/src/assets/stripe/db-studio.png differ diff --git a/opensaas-sh/blog/src/assets/stripe/listen-to-stripe-events.png b/opensaas-sh/blog/src/assets/stripe/listen-to-stripe-events.png index f53a67499..853fa5c77 100644 Binary files a/opensaas-sh/blog/src/assets/stripe/listen-to-stripe-events.png and b/opensaas-sh/blog/src/assets/stripe/listen-to-stripe-events.png differ diff --git a/opensaas-sh/blog/src/assets/stripe/npm-version.png b/opensaas-sh/blog/src/assets/stripe/npm-version.png index 95e4bf196..5371c4312 100644 Binary files a/opensaas-sh/blog/src/assets/stripe/npm-version.png and b/opensaas-sh/blog/src/assets/stripe/npm-version.png differ diff --git a/opensaas-sh/blog/src/assets/stripe/price-ids.png b/opensaas-sh/blog/src/assets/stripe/price-ids.png index 340457fd5..e118f4f18 100644 Binary files a/opensaas-sh/blog/src/assets/stripe/price-ids.png and b/opensaas-sh/blog/src/assets/stripe/price-ids.png differ diff --git a/opensaas-sh/blog/src/assets/stripe/stripe-webhook-signing-secret.png b/opensaas-sh/blog/src/assets/stripe/stripe-webhook-signing-secret.png index fc2bd26dd..45f681713 100644 Binary files a/opensaas-sh/blog/src/assets/stripe/stripe-webhook-signing-secret.png and b/opensaas-sh/blog/src/assets/stripe/stripe-webhook-signing-secret.png differ diff --git a/opensaas-sh/blog/src/assets/stripe/switch-plans.png b/opensaas-sh/blog/src/assets/stripe/switch-plans.png index f73eb03bd..2e564442f 100644 Binary files a/opensaas-sh/blog/src/assets/stripe/switch-plans.png and b/opensaas-sh/blog/src/assets/stripe/switch-plans.png differ diff --git a/opensaas-sh/blog/src/assets/stripe/test-product.png b/opensaas-sh/blog/src/assets/stripe/test-product.png index 9f0126090..459c7a701 100644 Binary files a/opensaas-sh/blog/src/assets/stripe/test-product.png and b/opensaas-sh/blog/src/assets/stripe/test-product.png differ diff --git a/opensaas-sh/blog/src/content/docs/blog/2024-12-10-turboreel-os-ai-video-generator-built-with-open-saas.mdx b/opensaas-sh/blog/src/content/docs/blog/2024-12-10-turboreel-os-ai-video-generator-built-with-open-saas.mdx index 6c0660664..cd85bbb55 100644 --- a/opensaas-sh/blog/src/content/docs/blog/2024-12-10-turboreel-os-ai-video-generator-built-with-open-saas.mdx +++ b/opensaas-sh/blog/src/content/docs/blog/2024-12-10-turboreel-os-ai-video-generator-built-with-open-saas.mdx @@ -8,13 +8,13 @@ tags: - indiehackers authors: milica --- -import VideoPlayer from '../../../components/VideoPlayer.astro'; import { Image } from 'astro:assets'; import landing from '../../../assets/turboreel/landing.webp'; -import studioInterface from '../../../assets/turboreel/studio-interface.mp4'; import opensaas from '../../../assets/turboreel/opensaas.mp4'; -import reddit100Users from '../../../assets/turboreel/reddit-100-users.webp' -import reddit200Upvotes from '../../../assets/turboreel/reddit-200-upvotes.webp' +import reddit100Users from '../../../assets/turboreel/reddit-100-users.webp'; +import reddit200Upvotes from '../../../assets/turboreel/reddit-200-upvotes.webp'; +import studioInterface from '../../../assets/turboreel/studio-interface.mp4'; +import VideoPlayer from '../../../components/VideoPlayer.astro'; Peter is the creator of [**TurboReel**](https://turboreelgpt.tech/), an open-source platform with a paid SaaS layer, that transforms how creators generate short-form video content. With just a prompt, users can produce polished TikToks and YouTube Shorts in moments. @@ -116,7 +116,7 @@ app myApp { ### Out-of-the-box Stripe integration -Another significant advantage for Peter was how Open SaaS handled third-party integrations. Setting up services like [**Stripe for payments**](https://docs.opensaas.sh/guides/payments-integration/) often requires a lot of effort, but Wasp's Open SaaS streamlined the process - you just need to add your API key and you're good to go. +Another significant advantage for Peter was how Open SaaS handled third-party integrations. Setting up services like [**Stripe for payments**](https://docs.opensaas.sh/guides/payment-integrations/) often requires a lot of effort, but Wasp's Open SaaS streamlined the process - you just need to add your API key and you're good to go. > *"Payments are usually a huge headache, but Open SaaS made it so smooth. I didn't have to spend weeks integrating Stripe—it just worked. That gave me more time to focus on TurboReel's core functionality.*" diff --git a/opensaas-sh/blog/src/content/docs/blog/2025-11-19-gemini-3-open-saas.mdx b/opensaas-sh/blog/src/content/docs/blog/2025-11-19-gemini-3-open-saas.mdx new file mode 100644 index 000000000..f501df014 --- /dev/null +++ b/opensaas-sh/blog/src/content/docs/blog/2025-11-19-gemini-3-open-saas.mdx @@ -0,0 +1,72 @@ +--- +title: "Building a SaaS with Gemini 3 and Open SaaS" +date: 2025-11-19 +tags: + - gemini + - cursor + - ai + - tutorial +authors: vince +--- +import VideoPlayer from '../../../components/VideoPlayer.astro'; +import { Image } from 'astro:assets'; +import chatInterface from '../../../assets/gemini-3/chat.webp'; +import landingPage from '../../../assets/gemini-3/landing-page.webp'; + +I'm always skeptical of demos whenever a new model gets released. So I had to test Google's new Gemini 3 model myself. + +I wanted to give a more unfiltered look at some real-life SaaS development tasks with **Gemini 3**, **Cursor** and our very own **Open SaaS boilerplate**, so I recorded a quick video of the process. + +The results were impressive and it was able to handle 3 tasks of increasing complexity. I'll definitely be using this as my new go-to model for feature development in future projects! + +Check out the full video below to see it in action: + + + +## Refactoring the Landing Page + +I started with a fresh copy of the [Open SaaS template](https://opensaas.sh/) and gave Gemini 3 a simple prompt: **look at the demo AI app and update the landing page to reflect it.** + +Using Cursor's "Plan Mode," Gemini 3 proposed three different design styles. I chose an "interactive product-led demo" approach. The model generated a plan and executed it on the first try, building a custom interactive component and integrating it seamlessly into the existing landing page hero section. + +This was a cool surprise, as I hand't thought about building an interactive component to showcase the app's features. A nice win! + +Landing page + +## Refactoring with Structured Outputs + +Next, I ramped up the difficulty. The demo app originally used OpenAI's function calling to generate a daily schedule. I asked Gemini 3 to **refactor this to use OpenAI's "Structured Outputs" feature instead.** + +The model: +1. Identified the need for a Zod schema to define the response format. +2. Switched the model to `gpt-4o-mini`. +3. Replaced the legacy tool definitions with the new `response_format` parameter. +4. Updated the parsing logic to handle the structured response. + +This refactor modernized the codebase without breaking existing functionality. Nice. This saved me a ton of time reading the docs and figuring out how to use the new feature myself. + +## Adding a Collaborative Chat Interface + +For the final test, we asked for a major feature addition: **a chat interface to collaborate with the AI on the creating a daily plan (with subtasks) before committing to it.** + +Gemini 3 broke this down into a multi-step plan: +* **Backend:** Define new schemas and actions to handle the chat history and plan proposals. +* **Frontend:** Implement a chat component using `shadcn/ui` components (specifically the ScrollArea). +* **Integration:** Connect the chat interface to the planning logic, allowing users to refine tasks (e.g., "prioritize filming the video") before accepting the final schedule. + +The result was a functional chat interface where the AI intelligently adjusted the schedule based on user feedback, embedded directly into the app. I loved how it injected a nice styled component into the chat window, something that I wanted (it read my mind!) and would have taken me hours to build in the past. + +I was really happy with the results of this challenge! + +Chat interface + +## Conclusion + +The combination of **Gemini 3**, **Cursor**, and **Open SaaS** proved to be pretty powerful. The model didn't just generate code; it understood the project structure (Open SaaS organizes code vertically by feature), followed architectural patterns, and successfully implemented complex refactors and new features with minimal human intervention. + +Overall, I was very impressed and I think other SaaS builders would be smart to give this stack a go. + +
+ ⭐️ Star Open SaaS repo and start building your next idea today! +
+ diff --git a/opensaas-sh/blog/src/content/docs/blog/2025-11-21-open-saas-public-roadmap.mdx b/opensaas-sh/blog/src/content/docs/blog/2025-11-21-open-saas-public-roadmap.mdx new file mode 100644 index 000000000..efa655a14 --- /dev/null +++ b/opensaas-sh/blog/src/content/docs/blog/2025-11-21-open-saas-public-roadmap.mdx @@ -0,0 +1,206 @@ +--- +title: "The Public Development Roadmap for Open SaaS" +date: 2025-11-21 +tags: + - roadmap + - wasp + - open-source + - development +authors: vince +--- +import { Image } from 'astro:assets'; +import openSaasUsedBy from '../../../assets/roadmap/open-saas-used-by.webp'; +import openSaasRoadmap from '../../../assets/roadmap/open-saas-roadmap.webp'; +import epicExample from '../../../assets/roadmap/open-saas-epic.webp'; +import epicDetailExample from '../../../assets/roadmap/open-saas-epic-detail.webp'; +import aiReady from '../../../assets/roadmap/open-saas-ai-ready.webp'; +import subscribeGithubButton from '../../../assets/roadmap/subscribe-github.webp'; +import Tweet from '../../../components/Tweet.astro'; +import { Aside } from '@astrojs/starlight/components'; + +We created Open SaaS to help developers launch full-featured SaaS products faster. + +But launching fast only works when you're working with clear direction and a good foundation. As an open-source project powered by [Wasp](https://wasp.sh), and with our users in mind, we thought it was important to add two things to this project: +1. A solid roadmap so that development is focused. +2. Making that roadmap public, to give the community a clear view of what's coming next and how they can get involved. + +So we created just that. + +Behold, the [Open SaaS public roadmap](https://github.com/orgs/wasp-lang/projects/6)! +
+ +Open SaaS public roadmap + + +--- + +## Why a roadmap? + +Open SaaS is becoming a beast of its own. At [13k GitHub stars](https://github.com/wasp-lang/open-saas) and growing, and with constant feature additions and improvements, we thought it was time to make future development more organized, transparent, and community-driven. + +
+ +Open SaaS is used by thousands of developers + +
+ +With tons of SaaS apps being deployed with Open SaaS, the needs of its users are also becoming more diverse. It would be impossible to address all of them, but we needed a way to track and prioritize the most important features and improvements. + +Up until now, it was just me basically deciding which issues to work on and when. This was pretty inefficent to begin with, and then with more contributors joining the project, it became even more so. + +The roadmap is thus a great way to make sense of all the different possible directions we can go in, help focus our efforts, and make development and contributions more meaningful. + +## How it works + +Open SaaS currently has about [100 open GitHub issues](https://github.com/wasp-lang/open-saas/issues), so it would be crazy to organize and track them all in the roadmap. + +
+ +An example of an Epic in the Open SaaS roadmap + +
+ +So to make things overseeable, the roadmap is a GitHub project with a collection of "Epics". An Epic is just a GitHub issue with a bunch of sub-issues. Each Epic has a status and a progress bar to show you how many of its sub-issues are completed. + +
+ +An example of an Epic in the Open SaaS roadmap + +
+ +No regular issues are allowed in it, only epics, of which we don't want too many. This way, its easy to understand the current themes and priorities of the project, as well as what's currently underway. + +--- + +## Guiding themes + +When creating epics within the roadmap, there are a few categories we're focusing on at the moment. These guiding themes inform the epics we create, and help us make sure we're addressing the most important needs of our users. + +### 1. Real-world launch readiness + +Open SaaS should not just be a polished demo, or a flimsy template. It should be something you can deploy and scale. We want to make sure Open SaaS is a production-ready starter that can be used to build real-world SaaS products. +This means having a solid architecture, a clean codebase, and a set of features that are robust and ready to be used in production. + + + +### 2. Modularity & flexibility + +Different [auth methods](https://docs.opensaas.sh/guides/authentication/), [billing providers](https://docs.opensaas.sh/guides/payment-integrations/), file uploads, AI integrations, analytics, and more. We want to make sure Open SaaS is a flexible and modular platform that can be tailored to the needs of (almost) any SaaS product. +This means having the most essential features you need to launch your SaaS quickly, and being able to easily remove the things you don't. + +### 3. AI-first developer experience + +
+ +Open SaaS is AI-ready + +
+ +Whether you use Cursor, VSCode + Copilot, Claude, or others, Open SaaS should have top-notch code quality and be able to “explain itself” well to AI-driven coding tools, making it enjoyable to develop with in 2026 and beyond. + +We also want to provide demos and templates for AI-powered SaaS products, so you can get started building SaaS apps that leverage the newest technologies to create powerful and innovative products. + + + +### 4. Community-powered evolution + +We want Open SaaS to be shaped by real feedback, real PRs, and real use cases. Roadmap ideas are only valuable when they reflect what people actually need. +So your [feedback and contributions](https://github.com/wasp-lang/open-saas/issues) are important to us! + +--- + +## What's in the roadmap + +We just got finished with a major UI redesign, which we dubbed "Open SaaS v2.0". Take a look at the announcement below. + + +With the new UI, based on [Shadcn UI](https://ui.shadcn.com/), finished and tucked away, we've turned back towards adding highly-requested features, and improving the codebase with smaller fixes. + +### 1. Payment Provider Expansion + +Today, Open SaaS supports Stripe, Polar, and Lemon Squeezy as payment providers. It's super [easy to get started](https://docs.opensaas.sh/guides/payment-integrations/) with one -- just plug and play. + +**Upcoming features:** + +* Polar.sh integration (merged in [#461](https://github.com/wasp-lang/open-saas/pull/461)) +* Paddle, and additional gateways (especially regional-friendly options) +* Supporting up- or down-grading between different billing plans and models (proration) + +--- + +### 2. AI-First Workflows & Templates + +Open SaaS already includes a simple OpenAI integration example, but we’re looking to expand and improve it. + +**Coming soon:** + +* Exploring using Vercel's AI SDK within our demo app (issue [#567](https://github.com/wasp-lang/open-saas/issues/567)). +* Cursor / Claude prompt presets for smoother onboarding (issue [#579](https://github.com/wasp-lang/open-saas/issues/579)) +* Creating better docs and guides for AI-powered SaaS development + +--- + +### 3. Core Development Improvements + +We want to keep Open SaaS relevant and up-to-date with the latest trends, as well as with the latest Wasp (the framework it's built on) features and improvements . + +**Efforts underway:** + +* Refactors, galore! E.g. removing `any` and `as` from the codebase, extracting `SVGs`, etc. (issue [#568](https://github.com/wasp-lang/open-saas/issues/568)) +* Improving OpenSaaS.sh development workflows (issue [#552](https://github.com/wasp-lang/open-saas/issues/552)) +* Improving e2e tests (issue [#558](https://github.com/wasp-lang/open-saas/issues/558)) + +--- + +
+ + + +# How to Participate + +So that's just a quick overview of the current state of the roadmap. But we're not done yet! + +Open SaaS is community-driven — and we’d love your help. + +### ⭐ Star the repo + +This is super easy and helps a ton! + +[https://github.com/wasp-lang/open-saas](https://github.com/wasp-lang/open-saas) + +### 🧩 File issues or feature requests + +Especially around real-world SaaS requirements you’re running into. + +[https://github.com/wasp-lang/open-saas/issues](https://github.com/wasp-lang/open-saas/issues) + +### 🔧 Contribute code + +If you’re interested in payments, dashboarding, AI workflows, or dev-experience improvements — we’ll happily support and guide. + +[https://github.com/orgs/wasp-lang/projects/6](https://github.com/orgs/wasp-lang/projects/6) + +### 🚀 Build with it + +The best feedback always comes from people deploying real products. +Drop us a line on [Discord](https://discord.com/invite/aCamt5wCpS) or [Twitter](https://x.com/wasplang) and let us know what you're building! + +--- + +# Roadmap Updates + +We’ll keep updating this roadmap alongside major releases and aim for a quarterly revision cycle. + +You can follow progress in the GitHub Project board: + +👉 **[https://github.com/orgs/wasp-lang/projects/6](https://github.com/orgs/wasp-lang/projects/6)** + +See you there! diff --git a/opensaas-sh/blog/src/content/docs/general/admin-dashboard.mdx b/opensaas-sh/blog/src/content/docs/general/admin-dashboard.mdx index f1195d55d..bdf062238 100644 --- a/opensaas-sh/blog/src/content/docs/general/admin-dashboard.mdx +++ b/opensaas-sh/blog/src/content/docs/general/admin-dashboard.mdx @@ -4,9 +4,9 @@ banner: content: | Have an Open SaaS app in production? We'll send you some swag! 👕 --- -import { Image } from 'astro:assets'; -import dbStudio from '@assets/stripe/db-studio.png'; import adminDashboard from '@assets/admin/admin-dashboard.png'; +import dbStudio from '@assets/stripe/db-studio.png'; +import { Image } from 'astro:assets'; This is a reference on how the Admin dashboard, available at `/admin`, is set up. @@ -55,7 +55,7 @@ If you're finding this template and its guides useful, consider giving us [a sta ### Analytics Dashboard The Admin analytics dashboard is a single place for you to view your most important metrics and perform some admin tasks. At the moment, it pulls data from: -- [Payments Processor](/guides/payments-integration/): +- [Payment Processor](/guides/payment-integrations/): - total revenue - revenue for each day of the past week - [Google or Plausible](/guides/analytics/): @@ -85,7 +85,7 @@ job dailyStatsJob { ``` For more info on Wasp's recurring background jobs, check out the [Wasp Jobs docs](https://wasp.sh/docs/advanced/jobs). -For a guide on how to integrate these services so that you can view your analytics via the dashboard, check out the [Payments Integration](/guides/payments-integration/) and [Analytics guide](/guides/analytics/) of the docs. +For a guide on how to integrate these services so that you can view your analytics via the dashboard, check out the [Payment Integrations](/guides/payment-integrations/) and [Analytics guide](/guides/analytics/) of the docs. :::note[Help us improve] We're always looking to improve the Admin dashboard. If you feel something is missing or could be improved, consider [opening an issue](https://github.com/wasp-lang/open-saas/issues) or [submitting a pull request](https://github.com/wasp-lang/open-saas/pulls) diff --git a/opensaas-sh/blog/src/content/docs/general/user-overview.md b/opensaas-sh/blog/src/content/docs/general/user-overview.md index faea88e58..90ffeb163 100644 --- a/opensaas-sh/blog/src/content/docs/general/user-overview.md +++ b/opensaas-sh/blog/src/content/docs/general/user-overview.md @@ -103,7 +103,7 @@ By default, we have three plans: `hobby` and `pro` subscription plans, as well a You can add more plans by adding more products and price IDs to your Stripe product and updating environment variables in your `.env.server` file as well as the relevant code in your app. -See the [Payments Integration Guide](/guides/payments-integration/) for more info on how to do this. +See the [Payment Integrations Guide](/guides/payment-integrations/) for more info on how to do this. ## User Roles diff --git a/opensaas-sh/blog/src/content/docs/guides/deploying.mdx b/opensaas-sh/blog/src/content/docs/guides/deploying.mdx index 257db56e1..7d22360d3 100644 --- a/opensaas-sh/blog/src/content/docs/guides/deploying.mdx +++ b/opensaas-sh/blog/src/content/docs/guides/deploying.mdx @@ -4,65 +4,64 @@ banner: content: | Have an Open SaaS app in production? We'll send you some swag! 👕 --- -import { Image } from 'astro:assets'; -import npmVersion from '@assets/stripe/npm-version.png'; import stripeListenEvents from '@assets/stripe/listen-to-stripe-events.png'; +import npmVersion from '@assets/stripe/npm-version.png'; import stripeSigningSecret from '@assets/stripe/stripe-webhook-signing-secret.png'; +import { Image } from 'astro:assets'; -Because this SaaS app is a React/NodeJS/Postgres app built on top of [Wasp](https://wasp.sh), Open SaaS can take advantage of Wasp's easy, one-command deploy to Fly.io or manual deploy to any provider of your choice. +Because this SaaS app is a React/NodeJS/Postgres app built on top of [Wasp](https://wasp.sh), Open SaaS can take advantage of Wasp's easy, one-command deploy or manual deploy to any provider of your choice. -The simplest and quickest option is to take advantage of Wasp's one-command deploy to Fly.io. +The simplest and quickest option is to take advantage of Wasp's one-command deploy to Fly.io or Railway. -Or if you prefer to deploy to a different provider, or your frontend and backend separately, you can follow the Deploying Manually section below. +Or if you prefer to deploy to a different provider, or your frontend and backend separately, you can follow the [Deploying Manually](#deploying-manually) section below. -## Deploying your App -### Steps for Deploying +## Steps for Deploying These are the steps necessary for you to deploy your app. We recommend you follow these steps in order. -- [ ] Get your [production API keys and environment variables](#prerequisites) -- [ ] Deploy your app easily to [Fly.io](#deploying-to-flyio) or [manually](#deploying-manually--to-other-providers) to any provider. -- [ ] Add the correct [redirect URL's to your social auth credentials](#adding-server-redirect-urls-to-social-auth) -- [ ] Set up your [production webhooks for either [Stripe](#setting-up-your-production-stripe-webhook) or [Lemon Squeezy](#setting-up-your-production-lemon-squeezy-webhook) -- [ ] Set your [production environment variables](#other-vars) on your deployed apps -- [ ] (Optional) [Deploy your blog](#deploying-your-blog) +- [ ] Get your [production API keys and environment variables](#prerequisites). +- [ ] Set your [production environment variables](#other-vars) on your deployed apps. +- [ ] Add the correct [redirect URL's to your social auth credentials](#adding-server-redirect-urls-to-social-auth). +- [ ] Set your [production settings for your payment provider](#payment-providers-production-settings). +- [ ] [Deploy your App](#deploying-your-app) through Wasp Deploy commands or [manually](#deploying-manually--to-other-providers) to any provider. +- [ ] (Optional) [Deploy your blog](#deploying-your-blog). Each of these steps is covered in more detail below. -### Prerequisites -#### AWS S3 CORS configuration +## Prerequisites +### AWS S3 CORS configuration If you're storing files in AWS S3, ensure you've listed your production domain in the bucket's CORS configuration under `AllowedOrigins`. Check the [File uploading guide](/guides/file-uploading/#change-the-cors-settings) for details. -#### Env Vars +### Env Vars Make sure you've got all your API keys and environment variables set up before you deploy. -##### Payment Processor Vars -In the [Payments Processor integration guide](/guides/payments-integration/), you set up your API keys using test keys and test product ids. You'll need to get the live/production versions of those keys. To get these, repeat the instructions in the [Integration Guide](/guides/payments-integration/) without being in test mode. Add the new keys to your deployed environment secrets. +#### Payment Processor Vars +In the [Payments Processor integration guide](/guides/payment-integrations/), you set up your API keys using test keys and test product ids. You'll need to get the live/production versions of those keys. To get these, repeat the instructions in the [Integration Guide](/guides/payment-integrations/) without being in test mode. Add the new keys to your deployed environment secrets. -##### Other Vars +#### Other Vars Many of your other environment variables will probably be the same as in development, but you should double-check that they are set correctly for production. Here are a list of all of them (some of which you may not be using, e.g. Analytics, Social Auth) in case you need to check: -###### General Vars +##### General Vars - [ ] `DATABASE_URL` - [ ] `JWT_SECRET` - [ ] `WASP_WEB_CLIENT_URL` - [ ] `WASP_SERVER_URL` -###### Open AI API Key +##### Open AI API Key - [ ] `OPENAI_API_KEY` -###### Sendgrid API Key +##### Sendgrid API Key - [ ] `SENDGRID_API_KEY` -###### Social Auth Vars +##### Social Auth Vars - [ ] `GOOGLE_CLIENT_ID` - [ ] `GOOGLE_CLIENT_SECRET` - [ ] `GITHUB_CLIENT_ID` - [ ] `GITHUB_CLIENT_SECRET` -###### Analytics Vars +##### Analytics Vars - [ ] `REACT_APP_PLAUSIBLE_ANALYTICS_ID` (for client-side) - [ ] `PLAUSIBLE_API_KEY` - [ ] `PLAUSIBLE_SITE_ID` @@ -73,7 +72,7 @@ Here are a list of all of them (some of which you may not be using, e.g. Analyti - [ ] `GOOGLE_ANALYTICS_PRIVATE_KEY` (Make sure you convert the private key within the JSON file to base64 first with `echo -n "PRIVATE_KEY" | base64`. See the [Analytics docs](/guides/analytics/#google-analytics) for more info) -###### AWS S3 Vars +##### AWS S3 Vars - [ ] `AWS_S3_IAM_ACCESS_KEY` - [ ] `AWS_S3_IAM_SECRET_KEY` - [ ] `AWS_S3_FILES_BUCKET` @@ -85,49 +84,13 @@ Do you have an Open SaaS app running in production? If yes, we'd love to send so ::: -### Deploying to Fly.io +## Adding Server Redirect URL's to Social Auth -[Fly.io](https://fly.io) is a platform for running your apps globally. It's a great choice for deploying your SaaS app because it's free to get started, can host your entire full-stack app in one place, scales well, and has one-command deploy integration with Wasp. +After deploying your server, you need to add the correct redirect URIs to the credential settings. For this, refer to [the guides from the Wasp Docs](https://wasp.sh/docs/auth/social-auth/overview). -**Wasp provides the handy `wasp deploy` command to deploy your entire full-stack app (DB, server, and client) in one command.** +## Payment Providers Production Settings -To learn how, please follow the detailed guide for [deploying to Fly via the Wasp CLI](https://wasp.sh/docs/deployment/deployment-methods/cli) from the Wasp documentation. We suggest you follow this guide carefully to get your app deployed. - -:::caution[Setting Environment Variables] -Remember, because we've set certain client-side env variables, make sure to pass them to the `wasp deploy` commands so that they can be included in the build: -```sh -REACT_APP_CLIENT_ENV_VAR_1=<...> REACT_APP_CLIENT_ENV_VAR_2=<...> wasp deploy -``` - -The `wasp deploy` command will also take care of setting the following server-side environment variables for you so you don't have to: -- `DATABASE_URL` -- `PORT` -- `JWT_SECRET` -- `WASP_WEB_CLIENT_URL` -- `WASP_SERVER_URL` - -For setting the remaining server-side environment variables, please refer to the [Deploying with the Wasp CLI Guide](https://wasp.sh/docs/deployment/deployment-methods/cli#launch). -::: - -### Deploying Manually / to Other Providers - -If you prefer to deploy manually, your frontend and backend separately, or just prefer using your favorite provider you can follow [Wasp's Manual Deployment Guide](https://wasp.sh/docs/deployment/deployment-methods/paas). - -:::caution[Client-side Environment Variables] -Remember to always set additional client-side environment variables, such as `REACT_APP_STRIPE_CUSTOMER_PORTAL` by appending them to the build command, e.g. -```sh -REACT_APP_CLIENT_ENV_VAR_1=<...> npm run build -``` -::: - -### Adding Server Redirect URL's to Social Auth - -After deploying your server, you need to add the correct redirect URIs to the credential settings. For this, refer to the following guides from the Wasp Docs: - -- [Google Auth](https://wasp.sh/docs/auth/social-auth/google#3-creating-a-google-oauth-app:~:text=Under%20Authorized%20redirect%20URIs) -- [Github Auth](https://wasp.sh/docs/auth/social-auth/github#3-creating-a-github-oauth-app:~:text=Authorization%20callback%20URL) - -### Setting up your Production Stripe Webhook +### Stripe Now you need to set up your stripe webhook for production use. Below are some important steps and considerations you should take as you prepare to deploy your app to production. @@ -139,8 +102,10 @@ Because this template was built with a specific version of the Stripe API in min :::note ```ts title="stripeClient.ts" -export const stripe = new Stripe(process.env.STRIPE_API_KEY!, { - apiVersion: 'YYYY-MM-DD', // e.g. 2023-08-16 +const STRIPE_API_VERSION = "2025-04-30.basil"; // or e.g. 2023-08-16 if using older format + +export const stripeClient = new Stripe(requireNodeEnvVar("STRIPE_API_KEY"), { + apiVersion: STRIPE_API_VERSION, }); ``` When you specify a specific API version in your Stripe client, the requests you send to Stripe from your server, along with their responses, will match that API version. On the other hand, Stripe will send all other events to your webhook that didn't originate as a request sent from your server, like those made after a user completes a payment on checkout, using the default API version of the API. @@ -152,61 +117,126 @@ To make sure your app is consistent with your Stripe account, here are some step 1. You can find your `default` API version in the Stripe dashboard under the [Developers](https://dashboard.stripe.com/developers) section. 2. Check that the API version in your `/src/payment/stripe/stripeClient.ts` file matches the default API version in your dashboard: -```ts title="stripeClient.ts" {2} -export const stripe = new Stripe(process.env.STRIPE_KEY!, { - apiVersion: 'YYYY-MM-DD', // e.g. 2023-08-16 -}); -``` + ```ts title="stripeClient.ts" {1} + const STRIPE_API_VERSION = "2025-04-30.basil"; // or e.g. 2023-08-16 if using older format + + export const stripeClient = new Stripe(requireNodeEnvVar("STRIPE_API_KEY"), { + apiVersion: STRIPE_API_VERSION, + }); + ``` 3. If they don't match, you can upgrade/downgrade your Stripe NPM package in `package.json` to match the API version in your dashboard: - If your default version on the Stripe dashboard is also the latest version of the API, you can simply upgrade your Stripe NPM package to the latest version. - If your default version on the Stripe dashboard is not the latest version, and you don't want to [upgrade to the latest version](https://docs.stripe.com/upgrades#how-can-i-upgrade-my-api), because e.g. you have other projects that depend on the current version, you can find and install the Stripe NPM package version that matches your default API version by following these steps: - Find and note the date of your default API version in the [developer dashboard](https://dashboard.stripe.com/developers). - - Go to the [Stripe NPM package](https://www.npmjs.com/package/stripe) page and hover over `Published` date column until you find the package release that matches your version. For example, here we find the NPM version that matches the default API version of `2023-08-16` in our dashboard, which is `13.x.x`. - npm version - - Install the correct version of the Stripe NPM package by running, : - ```sh - npm install stripe@x.x.x # e.g. npm install stripe@13.11.0 - ``` + - Go to the [Stripe NPM package](https://www.npmjs.com/package/stripe) page and hover over `Published` date column until you find the package release that matches your version. For example, here we find the NPM version `13.x.x` matches the API version of `2023-08-16`. + npm version + - Install the correct version of the Stripe NPM package by running: + ```sh + npm install stripe@x.x.x # e.g. npm install stripe@13.11.0 + ``` 4. **Test your app thoroughly** to make sure that the changes you made to your Stripe client are working as expected before deploying to production. #### Creating Your Production Webhook -1. go to [https://dashboard.stripe.com/webhooks](https://dashboard.stripe.com/webhooks) -2. click on `+ add endpoint` -3. enter your endpoint url, which will be the url of your deployed server + `/payments-webhook`, e.g. `https://open-saas-wasp-sh-server.fly.dev/payments-webhook` -listen events -4. select the events you want to listen to. These should be the same events you're consuming in your webhook which you can find listed in [`src/payment/stripe/webhookPayload.ts`](https://github.com/wasp-lang/open-saas/blob/main/template/app/src/payment/stripe/webhookPayload.ts): -signing secret -5. after that, go to the webhook you just created and `reveal` the new signing secret. -6. add this secret to your deployed server's `STRIPE_WEBHOOK_SECRET=` environment variable.
If you've deployed to Fly.io, you can do that easily with the following command: -```sh -wasp deploy fly cmd --context server secrets set STRIPE_WEBHOOK_SECRET=whsec_... -``` +1. Go to [https://dashboard.stripe.com/webhooks](https://dashboard.stripe.com/webhooks) +2. Click on `+ add endpoint` +3. Enter your endpoint url, which will be the url of your deployed server + `/payments-webhook`, e.g. `https://open-saas-wasp-sh-server.fly.dev/payments-webhook` + listen events +4. Select the events you want to listen to. These should be the same events you're consuming in your webhook which are handled in [`src/payment/stripe/webhook.ts`](https://github.com/wasp-lang/open-saas/blob/main/template/app/src/payment/stripe/webhook.ts): + signing secret +5. After that, go to the webhook you just created and `reveal` the new signing secret. +6. Add this secret to your deployed server's `STRIPE_WEBHOOK_SECRET` environment variable. If you've deployed to Fly.io, you can do that easily with the following command: + ```sh + wasp deploy fly cmd --context server secrets set STRIPE_WEBHOOK_SECRET=whsec_... + ``` + +### Lemon Squeezy -### Setting up your Production Lemon Squeezy Webhook +#### Creating Your Production Webhook To set up your Lemon Squeezy webhook, you'll need the URL of you newly deployed server + `/payments-webhook`, e.g. `https://open-saas-wasp-sh-server.fly.dev/payments-webhook`. With the webhook url ready, go to your [Lemon Squeezy Webhooks Dashboard](https://app.lemonsqueezy.com/settings/webhooks): -- click the `+` button. -- add the webhook forwarding url to the `Callback URL` section. -- give your webhook a signing secret (a long, random string). -- add this signing secret to your server's production environment variables under `LEMONSQUEEZY_WEBHOOK_SECRET=` -- make sure to select at least the following updates to be sent: - - order_created - - subscription_created - - subscription_updated - - subscription_cancelled -- click `save` +1. Click the `+` button. +2. Add the webhook forwarding url to the `Callback URL` section. +3. Give your webhook a signing secret (a long, random string). +4. Add this signing secret to your server's production environment variables under `LEMONSQUEEZY_WEBHOOK_SECRET`. +5. Make sure to select at least the following updates to be sent: + - `order_created` + - `subscription_created` + - `subscription_updated` + - `subscription_cancelled` +6. Click `save`. + +### Polar + +#### Turning off the Sandbox mode + +Make sure to turn off the sandbox mode in your server's production environemnt: + +```ini title=".env.server" +POLAR_SANDBOX_MODE=false +``` + +#### Creating Your Production Webhook + +To set up your Polar webhook, you'll need the URL of your newly deployed server + `/payments-webhook`, e.g. `https://open-saas-wasp-sh-server.fly.dev/payments-webhook`. + +With the webhook URL ready, in your [Polar Dashboard](https://polar.sh/dashboard): +1. Navigate to `Settings > Webhooks`. +2. Click the `Add Endpoint` button. +3. In the URL field, paste your production forwarding address. +4. Set the `Format` to `"Raw"`. +5. Select the following events to listen for: + - `order.paid` + - `subscription.updated` +6. Click `Save`. +7. Copy the generated webhook secret and set it as the `POLAR_WEBHOOK_SECRET` environment variable in production. + +## Deploying your App +### Deploying to Fly.io + +[Fly.io](https://fly.io) is a platform for running your apps globally. It's a great choice for deploying your SaaS app because it's free to get started, can host your entire full-stack app in one place, scales well, and has one-command deploy integration with Wasp. + +**Wasp provides the handy `wasp deploy` command to deploy your entire full-stack app (DB, server, and client) in one command.** + +To learn how, please follow the detailed guide for [deploying to Fly via the Wasp CLI](https://wasp.sh/docs/deployment/deployment-methods/cli) from the Wasp documentation. We suggest you follow this guide carefully to get your app deployed. + +:::caution[Setting Environment Variables] +Remember, because we've set certain client-side env variables, make sure to pass them to the `wasp deploy` commands so that they can be included in the build: +```sh +REACT_APP_CLIENT_ENV_VAR_1=<...> REACT_APP_CLIENT_ENV_VAR_2=<...> wasp deploy +``` + +The `wasp deploy` command will also take care of setting the following server-side environment variables for you so you don't have to: +- `DATABASE_URL` +- `PORT` +- `JWT_SECRET` +- `WASP_WEB_CLIENT_URL` +- `WASP_SERVER_URL` + +For setting the remaining server-side environment variables, please refer to the [Deploying with the Wasp CLI Guide](https://wasp.sh/docs/deployment/deployment-methods/cli#launch). +::: + +### Deploying Manually + +If you prefer to deploy manually, your frontend and backend separately, or just prefer using your favorite provider you can follow [Wasp's Manual Deployment Guide](https://wasp.sh/docs/deployment/deployment-methods/paas). + +:::caution[Client-side Environment Variables] +Remember to always set additional client-side environment variables, such as `REACT_APP_STRIPE_CUSTOMER_PORTAL` by appending them to the build command, e.g. +```sh +REACT_APP_CLIENT_ENV_VAR_1=<...> npm run build +``` +::: ## Deploying your Blog Deploying your Astro Starlight blog is a bit different than deploying your SaaS app. As an example, we will show you how to deploy your blog for free to Netlify. You will need a Netlify account and [Netlify CLI](https://docs.netlify.com/cli/get-started/) installed to follow these instructions. -Make sure you are logged in with Netlify CLI. -- You can check if you are logged in with `netlify status`, -- you can log in with `netlify login`. +Make sure you are logged in with Netlify CLI: +- You can check if you are logged in with `netlify status`. +- You can log in with `netlify login`. Position yourself in the `blog` directory and run the following command: diff --git a/opensaas-sh/blog/src/content/docs/guides/email-sending.mdx b/opensaas-sh/blog/src/content/docs/guides/email-sending.mdx index e10c60e41..d6f0f3bec 100644 --- a/opensaas-sh/blog/src/content/docs/guides/email-sending.mdx +++ b/opensaas-sh/blog/src/content/docs/guides/email-sending.mdx @@ -4,7 +4,7 @@ banner: content: | Have an Open SaaS app in production? We'll send you some swag! 👕 --- -import { Tabs, TabItem } from '@astrojs/starlight/components'; +import { TabItem, Tabs } from '@astrojs/starlight/components'; This guide explains how to use the integrated email sender and how you can integrate your own account in this template. @@ -87,9 +87,9 @@ To set up your email sender, you first need an account with one of the supported - Go to [Mailgun](https://mailgun.com) and create an account. - Go to [API Keys](https://app.mailgun.com/settings/api_security/api_keys?onboardingTask=api-key) and create a new API key. - - Copy the API key and add it to your .env.server file under the `MAILGUN_API_KEY=` variable. + - Copy the API key and add it to your .env.server file under the `MAILGUN_API_KEY` variable. - Go to [Domains](https://app.mailgun.com/mg/sending/new-domain?onboardingTask=add-verify-domain) and create a new domain. - - Copy the domain and add it to your .env.server file as `MAILGUN_DOMAIN=`. + - Copy the domain and add it to your .env.server file as `MAILGUN_DOMAIN`. Make sure to change the `defaultFrom` email address in the `main.wasp` file to use the same email address that you configured your account to send out emails with! diff --git a/opensaas-sh/blog/src/content/docs/guides/file-uploading.mdx b/opensaas-sh/blog/src/content/docs/guides/file-uploading.mdx index bdb8ab19c..c97f1a146 100644 --- a/opensaas-sh/blog/src/content/docs/guides/file-uploading.mdx +++ b/opensaas-sh/blog/src/content/docs/guides/file-uploading.mdx @@ -143,6 +143,116 @@ To begin customizing file uploads, is important to know where everything lives i - The `getAllFilesByUser` fetches all File information uploaded by the user. Note that the files do not exist in the app database, but rather the file data, its name and its `key`, which is used to fetch the file from S3. - The `getDownloadFileSignedURL` query fetches the presigned URL for a file to be downloaded from S3 using the file's `key` stored in the app's database. +### Cleaning up "orphaned" files in S3 + +In the current logic, files are first deleted from the app's database before attempting to delete them from S3. If, for some reason, the S3 deletion were to fail, the file would remain in S3 and not in the app's database and be orphaned: + +```ts +// src/file-upload/operations.ts +export const deleteFile: DeleteFile = async (args, context) => { + + const deletedFile = await context.entities.File.delete(args.fileId); + + try { + return await deleteFileFromS3({ s3Key: deletedFile.s3Key }); + } catch (error) { + console.error(`S3 deletion failed. Orphaned file s3Key: ${deletedFile.s3Key}`, error); + } +}; +``` + +To clean up these orphaned files, you could add a cleanup job that runs at an interval of your choosing to: + +1. Fetch all file keys from S3 +2. Fetch all file keys from the app's database +3. Compare the two lists and delete any files from S3 that are not in the database + +Here's an example of how you could implement this: + +```ts +// .wasp config file +job cleanUpOrphanedFilesS3Job { + executor: PgBoss, + perform: { + fn: import { cleanUpOrphanedFilesS3 } from "@src/file-upload/workers" + }, + schedule: { + cron: "0 5 * * 0" // every week on Sunday at 5am + }, + entities: [File] +} +``` + +```ts +// src/file-upload/workers.ts + +import type { CleanUpOrphanedFilesS3Job } from 'wasp/server/jobs'; +import { s3Client, deleteFileFromS3 } from './s3Utils'; +import { ListObjectsV2Command, ListObjectsV2CommandOutput } from '@aws-sdk/client-s3'; + +export const cleanUpOrphanedFilesS3: CleanUpOrphanedFilesS3Job = async ( + _args, + context +) => { + const allFileKeysFromS3 = await fetchAllFileKeysFromS3(); + const allFileKeysFromDb = await context.entities.File.findMany({ + select: { s3Key: true }, + }); + await findAndDeleteOrphanedFilesInS3(allFileKeysFromS3, allFileKeysFromDb); +}; + +const fetchAllFileKeysFromS3 = async () => { + const allS3Keys: string[] = []; + let continuationToken: string | undefined = undefined; + + do { + const command = new ListObjectsV2Command({ + Bucket: process.env.AWS_S3_FILES_BUCKET, + ContinuationToken: continuationToken, + }); + + const response: ListObjectsV2CommandOutput = await s3Client.send(command); + + if (response.Contents) { + const keys = response.Contents.reduce((acc: string[], object) => { + if (object.Key) { + acc.push(object.Key); + } + return acc; + }, []); + allS3Keys.push(...keys); + } + + continuationToken = response.NextContinuationToken; + } while (continuationToken); + + console.log(`Found ${allS3Keys.length} total files in S3`); + + return allS3Keys; +}; + +const findAndDeleteOrphanedFilesInS3 = async ( + allFileKeysFromS3: string[], + allFileKeysFromDb: { s3Key: string }[] +) => { + const s3KeysNotFoundInDb = allFileKeysFromS3.filter( + (s3Key) => !allFileKeysFromDb.some((file) => file.s3Key === s3Key) + ); + + // Delete files from S3 that are not in the database + // If any file deletion fails, the job can continue and pick it up next run. + const s3DeletionResults = await Promise.allSettled( + s3KeysNotFoundInDb.map((s3Key) => deleteFileFromS3({ s3Key })) + ); + + const successfulDeletions = s3DeletionResults.filter((result) => result.status === 'fulfilled'); + + console.log( + `Successfully deleted ${successfulDeletions.length} out of ${s3KeysNotFoundInDb.length} orphaned files from S3` + ); +}; +``` + ## Using Multer to upload files to your server If you're looking to upload files to the app server, you can use the Multer middleware to handle file uploads. This will allow you to store files on your server and is a good option if you need a quick and dirty, free solution for simple file uploads. diff --git a/opensaas-sh/blog/src/content/docs/guides/payment-integrations/index.mdx b/opensaas-sh/blog/src/content/docs/guides/payment-integrations/index.mdx new file mode 100644 index 000000000..cb92ec666 --- /dev/null +++ b/opensaas-sh/blog/src/content/docs/guides/payment-integrations/index.mdx @@ -0,0 +1,58 @@ +--- +title: Overview +banner: + content: | + Have an Open SaaS app in production? We'll send you some swag! 👕 +--- +import { TabItem, Tabs } from '@astrojs/starlight/components'; + +This guide will show you how to set up payments for testing and local development with the following payment processors: +- Stripe +- Lemon Squeezy +- Polar + +:::note[Which should I choose?] +Stripe is the industry standard, is more configurable, and has cheaper fees. +Lemon Squeezy and Polar act as a [Merchant of Record](https://www.lemonsqueezy.com/reporting/merchant-of-record). This means they take care of paying taxes in multiple countries for you, but charge higher fees per transaction. +::: + +## Important First Steps + +First, go to `/src/payment/paymentProcessor.ts` and choose which payment processor you'd like to use: + +```ts title="src/payment/paymentProcessor.ts" ins={7, 9, 11} +import { stripePaymentProcessor } from './stripe/paymentProcessor'; +import { lemonSqueezyPaymentProcessor } from './lemonSqueezy/paymentProcessor'; +import { polarPaymentProcessor } from './polar/paymentProcessor'; + +// ... + +export const paymentProcessor: PaymentProcessor = stripePaymentProcessor; +// or +export const paymentProcessor: PaymentProcessor = lemonSqueezyPaymentProcessor; +// or +export const paymentProcessor: PaymentProcessor = polarPaymentProcessor; +``` + +Then you should delete: +- The unused payment processor code within the `/src/payment/` directories. +- Any unused environment variables from `.env.server` (they will be prefixed with the name of the provider your are not using): + - E.g. `STRIPE_API_KEY`, `LEMONSQUEEZY_API_KEY`, `POLAR_ORGANIZATION_ACCESS_TOKEN`. +- Make sure to also run `npm uninstall` for providers you didn't use: + - Stripe: `npm uninstall stripe` + - Polar: `npm uninstall @polar-sh/sdk` + - Lemon Squeezy: `npm uninstall @lemonsqueezy/lemonsqueezy.js` +- If you are not using Lemon Squeezy remove the `lemonSqueezyCustomerPortalUrl` field from the `User` model in the `schema.prisma` file. + +Now your code is ready to go with your preferred payment processor and it's time to configure your payment processor's API keys, products, and other settings. + +Follow the steps for your selected processor: + +- [Stripe](stripe/) +- [Lemon Squeezy](lemon-squeezy/) +- [Polar](polar/) + + +## Deploying + +Once you're ready to deploy your app, follow the steps from the [deploying guide](/guides/deploying/) to set up the production settings for your prayment provider. diff --git a/opensaas-sh/blog/src/content/docs/guides/payment-integrations/lemon-squeezy.mdx b/opensaas-sh/blog/src/content/docs/guides/payment-integrations/lemon-squeezy.mdx new file mode 100644 index 000000000..ca0de6e4e --- /dev/null +++ b/opensaas-sh/blog/src/content/docs/guides/payment-integrations/lemon-squeezy.mdx @@ -0,0 +1,96 @@ +--- +title: Lemon Squeezy +banner: + content: | + Have an Open SaaS app in production? We'll send you some swag! 👕 +--- +import addProduct from '@assets/lemon-squeezy/add-product.png'; +import addVariant from '@assets/lemon-squeezy/add-variant.png'; +import storeId from '@assets/lemon-squeezy/store-id.png'; +import subscriptionVariantIds from '@assets/lemon-squeezy/subscription-variant-ids.png'; +import variantId from '@assets/lemon-squeezy/variant-id.png'; +import ngrok from '@assets/ngrok.png'; +import { Image } from 'astro:assets'; + +First, make sure you've defined your payment processor in `src/payment/paymentProcessor.ts`, as described in the [important first steps](/guides/payment-integrations/). + +Next, you'll need to create a Lemon Squeezy account in test mode. You can do that [here](https://lemonsqueezy.com). + +:::tip[Star our Repo on GitHub! 🌟] +We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free! + +If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp) +::: + +### Get your test Lemon Squeezy API Keys + +Once you've created your account, you'll need to get your test API keys. You can do that by navigating to [https://app.lemonsqueezy.com/settings/api](https://app.lemonsqueezy.com/settings/api) and creating a new API key: +1. Click on the `+` button. +2. Give your API key a name. +3. Copy and paste it in your `.env.server` file under `LEMONSQUEEZY_API_KEY`. + +### Get your Lemon Squeezy Store ID + +To get your store ID, go to the [Lemon Squeezy Dashboard](https://app.lemonsqueezy.com/settings/stores) and copy the `Store ID` from the top right corner. + +store id + +Copy and paste this number in your `.env.server` file under `LEMONSQUEEZY_STORE_ID`. + +### Creating Products + +To create a product, go to the test products url [https://app.lemonsqueezy.com/products](https://app.lemonsqueezy.com/products): + +1. Click on the `+ New Product` button and fill in the relevant information for your product. +2. Fill in the general information. +3. For pricing, select the type of product you'd like to create, e.g. `Subscription` for a recurring monthly payment product or `Single Payment` for credits-based product. + add product +4. Make sure you select `Software as a service (SaaS)` as the Tax category type. +5. If you want to add different price tiers for `Subscription` products, click on `add variant` under the `variants` tab. Here you can input the name of the variant (e.g. "Hobby", "Pro"), and that variant's price. + add variant +6. For a product with no variants, on the product page, click the `...` menu button and select `Copy variant ID` + variant id +7. For a product with variants, on the product page, click on the product, go to the variants tab and select `Copy ID` for each variant. + subscription variant ids +8. Paste these IDs in the `.env.server` file: + - We've set you up with two example subscription product environment variables, `PAYMENTS_HOBBY_SUBSCRIPTION_PLAN_ID` and `PAYMENTS_PRO_SUBSCRIPTION_PLAN_ID`. + - As well as a one-time payment product/credits-based environment variable, `PAYMENTS_CREDITS_10_PLAN_ID`. + +:::note[Naming products differently] +Note that if you change names of your products, you'll need to update your app code in `src/payment/plants.ts` to match these names as well. +::: + +### Using Lemon Squeezy Webhook in Local Development + +#### Exposing your Webhook Endpoint to the Internet + +Lemon Squeezy notifies your Wasp app about customer and payment events through a webhook. However, to make it available to Lemon Squeezy during development, you need to expose your locally running Wasp server (started with `wasp start`) to the internet. + +You can do that by running `ngrok` on port 3001 (Wasp server runs on port 3001 by default). `ngrok` will then generate a public URL that we can provide to Lemon Squeezy: + +1. First, make sure you have installed [`ngrok`](https://ngrok.com/docs/getting-started/). +2. Once `ngrok` is installed and your Wasp app is running, run: + ```sh + ngrok http 3001 + ``` + ngrok +3. `ngrok` will display a forwarding address. Copy this address and append `/payments-webhook` to it. It should look something like this: + ```sh title="Callback URL" + https://89e5-2003-c7-153c-72a5-f837.ngrok-free.app/payments-webhook + ``` + +#### Creating your Lemon Squeezy Webhook + +Next, go to your [Lemon Squeezy Webhooks Dashboard](https://app.lemonsqueezy.com/settings/webhooks): +1. Click the `+` button. +2. Add the newly created webhook forwarding url to the `Callback URL` section. +3. Give your webhook a signing secret (a long, random string). +4. Copy and paste this same signing secret into your `.env.server` file under `LEMONSQUEEZY_WEBHOOK_SECRET`. +5. Make sure to select at least the following updates to be sent: + - `order_created` + - `subscription_created` + - `subscription_updated` + - `subscription_cancelled` +6. Click `save`. + +You're now ready to start consuming Lemon Squeezy webhook events in local development. \ No newline at end of file diff --git a/opensaas-sh/blog/src/content/docs/guides/payment-integrations/polar.mdx b/opensaas-sh/blog/src/content/docs/guides/payment-integrations/polar.mdx new file mode 100644 index 000000000..d1d7d1c9a --- /dev/null +++ b/opensaas-sh/blog/src/content/docs/guides/payment-integrations/polar.mdx @@ -0,0 +1,140 @@ +--- +title: Polar +banner: + content: | + Have an Open SaaS app in production? We'll send you some swag! 👕 +--- +import ngrok from '@assets/ngrok.png'; +import polarUserTable from '@assets/polar/user-table.png'; +import polarWebhookLogs from '@assets/polar/webhook-log.png'; +import { Image } from 'astro:assets'; + +First, make sure you've defined your payment processor in `src/payment/paymentProcessor.ts`, as described in the [important first steps](/guides/payment-integrations/). + +Next, you'll need to create a Polar account in the sandbox mode. You can do that [here](https://sandbox.polar.sh/). + +:::tip[Star our Repo on GitHub! 🌟] +We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free! + +If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp) +::: + +### Sandbox and Production Mode + +Polar features two separate environments: +- [Sandbox](https://sandbox.polar.sh/dashboard). +- [Production](https://polar.sh/dashboard). + +They are fully isolated from each other, which means that you need to create seperate organizations for each of them. They also feature independent products, sales, access tokens, etc. + +For local development and testing, you'll want to use Polar's sandbox mode. +To enable sandbox mode, make sure that `POLAR_SANDBOX_MODE` is set to `true` in your `.env.server` file: + +```ini title=".env.server" +POLAR_SANDBOX_MODE=true +``` + +When you're ready to [deploy your app](/guides/deploying/), we'll remind you to set this to `false`. + +### Polar Organization Access Token + +Once you've created your account, you'll need to get your organization access token. +You can do that by: +1. Navigating to the `Developers` section under the `Settings > General` page. +2. Click on the `New Token` button to create a new token. +3. Give your token a name (e.g., "Open SaaS Development"). +4. Select an expiration date. `No expiration` is fine for sandbox mode, but discouraged for production mode. +5. Select the following scopes: + - `checkouts:write` + - `customer_sessions:write` + - `customers:read` + - `customers:write` + - `orders:read` +6. Copy the generated token and add it to your `.env.server` file: + ```ini title=".env.server" + POLAR_ORGANIZATION_ACCESS_TOKEN=polar_oat_... + ``` + +### Creating Products + +To create Polar products, in your Polar dashboard: +1. Navigate to `Prodcuts > Catalogue` page. +2. Click on the `+ New Product` button to create a new product. +3. Fill in the product details. + - For subscription products, select `Recurring subscription` pricing. + - For one-time payment products, select `One-time purchase` pricing. +4. Click `Create Product`. +5. Tap the `⠇` icon next to your product and select `Copy Product ID`. +6. Add the `Product ID` to your `.env.server` file. + Open SaaS by default expects three products, two subscriptions and one one-time purchase plan: + ```ini title=".env.server" + PAYMENTS_HOBBY_SUBSCRIPTION_PLAN_ID= + PAYMENTS_PRO_SUBSCRIPTION_PLAN_ID= + PAYMENTS_CREDITS_10_PLAN_ID= + ``` + +:::note[Naming products differently] +Note that if you change names of your products, you'll need to update your app code in `src/payment/plants.ts` to match these names as well. +::: + +### Using Polar Webhook in Local Development + +#### Exposing your Webhook Endpoint to the Internet + +Polar notifies your Wasp app about customer and payment events through a webhook. However, to make it available to Polar during development, you need to expose your locally running Wasp server (started with `wasp start`) to the internet. + +You can do that by running `ngrok` on port 3001 (Wasp server runs on port 3001 by default). `ngrok` will then generate a public URL that we can provide to Polar: + +1. First, make sure you have installed [`ngrok`](https://ngrok.com/docs/getting-started/). +2. Once `ngrok` is installed and your Wasp app is running, run: + ```sh + ngrok http 3001 + ``` + ngrok +3. `ngrok` will display a forwarding address. Copy this address and append `/payments-webhook` to it. It should look something like this: + ```sh title="Callback URL" + https://89e5-2003-c7-153c-72a5-f837.ngrok-free.app/payments-webhook + ``` + +#### Creating your Polar Webhook + +Next, configure the webhook in your Polar dashboard: +1. Navigate to `Settings > Webhooks` page. +2. Click `Add Endpoint` button. +3. In the URL field, paste your `ngrok` forwarding address with `/payments-webhook` appended (for example: `https://abc123.ngrok-free.app/payments-webhook`). +4. Set the `Format` to `"Raw"`. +5. Select the following events to listen for: + - `order.paid` + - `subscription.updated` +6. Click `Save`. +7. Copy the generated webhook secret and add it to your `.env.server` file: + ```ini title=".env.server" + POLAR_WEBHOOK_SECRET=polar_whs_... + ``` + +### Testing Payments in Local Development + +Before testing payments, make sure that you created and set all the required env variables in `.env.server` (`POLAR_ORGANIZATION_ACCESS_TOKEN`, `_PLAN_ID` and `POLAR_WEBHOOK_SECRET` ) and that your `ngrok` tunnel is running. + +You can then test the payment flow: + +1. Click a `Buy` button for any product on the homepage. +2. You should be redirected to Polar's checkout page. +3. Fill in the checkout form with [test payment information](https://docs.polar.sh/integrate/sandbox#testing-payments). +4. Complete the payment. +5. You should be redirected back to the checkout success page. + +To verify everything executed correctly you can: +- Check Polar webhook event logs: + 1. Navigate to `Settings > Webhooks` page + 2. Click the `Details` button of your previously created webhook. + 3. Confirm all of the events have been successful: + How to check webhook logs in Polar dashboard +- Inspect your database's `User` table: + 1. Run Wasp DB studio: + ```sh + wasp db studio + ``` + 2. Navigate to `localhost:5555` and check the `User` table. + 3. Confirm the `subscriptionStatus` is `active` for the user who made the purchase. + User table showing updated user diff --git a/opensaas-sh/blog/src/content/docs/guides/payment-integrations/stripe.mdx b/opensaas-sh/blog/src/content/docs/guides/payment-integrations/stripe.mdx new file mode 100644 index 000000000..e3ec28802 --- /dev/null +++ b/opensaas-sh/blog/src/content/docs/guides/payment-integrations/stripe.mdx @@ -0,0 +1,151 @@ +--- +title: Stripe +banner: + content: | + Have an Open SaaS app in production? We'll send you some swag! 👕 +--- +import ngrok from '@assets/ngrok.png'; +import testApiKeys from '@assets/stripe/api-keys.png'; +import dbStudio from '@assets/stripe/db-studio.png'; +import priceIds from '@assets/stripe/price-ids.png'; +import switchPlans from '@assets/stripe/switch-plans.png'; +import testProduct from '@assets/stripe/test-product.png'; +import { Image } from 'astro:assets'; + +First, make sure you've defined your payment processor in `src/payment/paymentProcessor.ts`, as described in the [important first steps](/guides/payment-integrations/). + +Next, you'll need to create a Stripe account. You can do that [here](https://dashboard.stripe.com/register). + +:::tip[Star our Repo on GitHub! 🌟] +We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free! + +If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp) +::: + +### Get your test Stripe API Keys + +Once you've created your account, you'll need to get your test API keys. You can do that by navigating to [https://dashboard.stripe.com/test/apikeys](https://dashboard.stripe.com/test/apikeys) or by going to the [Stripe Dashboard](https://dashboard.stripe.com/test/dashboard) and clicking on the `Developers`: +1. Click on the `Reveal test key token` button and copy the `Secret key`. +2. Paste it in your `.env.server` file under `STRIPE_API_KEY`. + +test api keys + +### Create Test Products + +To create a test product, go to the test products url [https://dashboard.stripe.com/test/products](https://dashboard.stripe.com/test/products), or after navigating to your dashboard, click the `test mode` toggle: +1. Click on the `Add a product` button and fill in the relevant information for your product. + test product + - Select `Software as a service (SaaS)` as the product type. + - For Subscription products, select `Recurring` as the billing type. + - For One-time payment products, select `One-time` as the billing type. +2. If you want to add different price tiers for the same product (e.g. monthly and yearly), click the `Add another price` button at the buttom. + price ids +3. After you save the product, you'll be directed to the product page. +4. Copy the price IDs and paste them in the `.env.server` file. + - We've set you up with two example subscription product environment variables, `PAYMENTS_HOBBY_SUBSCRIPTION_PLAN_ID` and `PAYMENTS_PRO_SUBSCRIPTION_PLAN_ID`. + - As well as a one-time payment product/credits-based environment variable, `PAYMENTS_CREDITS_10_PLAN_ID`. + +If you intend to let your users switch between two subscription plans, e.g. upgrade from hobby to pro, you'll need to create two separate products with their own price IDs. The ability for users to swich plans can then be configured later in the [Customer Portal](#set-up-the-customer-portal). + +:::note[Naming products differently] +Note that if you change names of your products, you'll need to update your app code in `src/payment/plants.ts` to match these names as well. +::: + +### Create a Test Customer + +You can create a test customer in the [Stripe Dashboard](https://dashboard.stripe.com/test/customers). + +Click on the `Add a customer` button and fill in the relevant information for your test customer. + +Alternatively, Open SasS automatically creates a test customer the first time a user starts a checkout session. +This customer is linked to the email address associated with the user in your app. + +### Set up the Customer Portal + +You can set up your customer portal in your [Stripe Dashboard](https://dashboard.stripe.com/test/settings/billing/portal). + +By default, OpenSaas generates a unique customer portal link for each user on the back end. +If you'd rather provide a permanent link to the customer portal, activate it and copy the `Portal link`. + +If you'd like to give users the ability to switch between different plans, e.g., upgrade from a "Hobby" to a "Pro" subscription, go down to the `Subscriptions` dropdown and select `customers can switch plans`. + +switch plans + +Then select the products you'd like them to be able to switch between. + +Now, after your users have paid, they can click on `Manage Subscription` in the client and will be taken to the customer portal where they can update their current plan. + +### Stripe CLI + +To install the Stripe CLI follow the instructions [here](https://docs.stripe.com/stripe-cli/install). + +Make sure to login after you install the Stripe CLI: +```sh +stripe login +``` + +:::caution[Errors running the Stripe CLI] +If you're seeing errors, consider appending `sudo` to the stripe commands. +See this [GitHuh issue](https://github.com/stripe/stripe-cli/issues/933) for more details. +::: + +### Testing Webhooks via the Stripe CLI + +Start the Stripe CLI webhook forwarding on port 3001 where your Node server is running: + +```sh +stripe listen --forward-to localhost:3001/payments-webhook +``` + +:::caution[Webhook URL] +In older versions of this template, the webhook URL was `http://localhost:3001/stripe-webhook`. +If you're using an older version, **make sure to use the url that matches the webhook url in your `main.wasp` file payemnts API definition.** +::: + +Remember to copy and paste the outputted webhook signing secret (`whsec_...`) into your `.env.server` file under `STRIPE_WEBHOOK_SECRET`. + +In another terminal window, trigger a test event: + +```sh +stripe trigger payment_intent.succeeded +``` + +The results of the event firing will be visible in the initial terminal window. You should see messages like this: + +```sh +... +2023-11-21 09:31:09 --> invoice.paid [evt_1OEpMPILOQf67J5TjrUgRpk4] +2023-11-21 09:31:09 <-- [200] POST http://localhost:3001/payments-webhook [evt_1OEpMPILOQf67J5TjrUgRpk4] +2023-11-21 09:31:10 --> invoice.payment_succeeded [evt_1OEpMPILOQf67J5T3MFBr1bq] +2023-11-21 09:31:10 <-- [200] POST http://localhost:3001/payments-webhook [evt_1OEpMPILOQf67J5T3MFBr1bq] +2023-11-21 09:31:10 --> checkout.session.completed [evt_1OEpMQILOQf67J5ThTZ0999r] +2023-11-21 09:31:11 <-- [200] POST http://localhost:3001/payments-webhook [evt_1OEpMQILOQf67J5ThTZ0999r] +``` + +For more info on testing webhooks, check out https://stripe.com/docs/webhooks#test-webhook. + +:::tip[Star our Repo on GitHub! 🌟] +We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free! + +If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp) +::: + +### Testing Checkout and Payments via the Client + +Make sure the **Stripe CLI is running** by following the steps above. +You can then test the payment flow via the client by doing the following: + +1. Click on a Buy button on the for any of the products on the homepage. You should be redirected to the checkout page. +2. Fill in the form with the following test credit card number `4242 4242 4242 4242` and any future date for the expiration date and any 3 digits for the CVC. +3. Click on the "Pay" button. You should be redirected to the success page. +4. Check your terminal window for status messages and logs. +5. You can also check your Database via the DB Studio to see if the user entity has been updated by running: + ```sh + wasp db studio + ``` +6. Navigate to `localhost:5555` and click on the `users` table. You should see the `subscriptionStatus` is `active` for the user that just made the purchase. + db studio + +:::note +If you want to learn more about how a user's payment status, subscription status, and subscription tier affect a user's priveledges within the app, check out the [User Overview](/general/user-overview/) reference. +::: \ No newline at end of file diff --git a/opensaas-sh/blog/src/content/docs/guides/payments-integration.mdx b/opensaas-sh/blog/src/content/docs/guides/payments-integration.mdx deleted file mode 100644 index df9c0c7a6..000000000 --- a/opensaas-sh/blog/src/content/docs/guides/payments-integration.mdx +++ /dev/null @@ -1,311 +0,0 @@ ---- -title: Payments Integration -banner: - content: | - Have an Open SaaS app in production? We'll send you some swag! 👕 ---- -import { Image } from 'astro:assets'; -import testApiKeys from '@assets/stripe/api-keys.png'; -import testProduct from '@assets/stripe/test-product.png'; -import priceIds from '@assets/stripe/price-ids.png'; -import switchPlans from '@assets/stripe/switch-plans.png'; -import dbStudio from '@assets/stripe/db-studio.png'; -import addProduct from '@assets/lemon-squeezy/add-product.png'; -import addVariant from '@assets/lemon-squeezy/add-variant.png'; -import variantId from '@assets/lemon-squeezy/variant-id.png'; -import subscriptionVariantIds from '@assets/lemon-squeezy/subscription-variant-ids.png'; -import ngrok from '@assets/lemon-squeezy/ngrok.png'; -import storeId from '@assets/lemon-squeezy/store-id.png'; - -This guide will show you how to set up Payments for testing and local development with the following payment processors: -- Stripe -- Lemon Squeezy - -:::note[Which should I choose?] -Stripe is the industry standard, is more configurable, and has cheaper fees. -Lemon Squeezy acts a [Merchant of Record](https://www.lemonsqueezy.com/reporting/merchant-of-record). This means they take care of paying taxes in multiple countries for you, but charge higher fees per transaction. -::: - -## Important First Steps - -First, go to `/src/payment/paymentProcessor.ts` and choose which payment processor you'd like to use, e.g. Stripe or Lemon Squeezy: - -```ts title="src/payment/paymentProcessor.ts" ins={5, 7} -import { stripePaymentProcessor } from './stripe/paymentProcessor'; -import { lemonSqueezyPaymentProcessor } from './lemonSqueezy/paymentProcessor'; -//... - -export const paymentProcessor: PaymentProcessor = stripePaymentProcessor; -// or... -export const paymentProcessor: PaymentProcessor = lemonSqueezyPaymentProcessor; -``` - -At this point, you can delete: -- the unused payment processor code within the `/src/payment/` directory, -- any unused environment variables from `.env.server` (they will be prefixed with the name of the provider your are not using): - - e.g. `STRIPE_API_KEY`, `STRIPE_CUSTOMER_PORTAL_URL`, `LEMONSQUEEZY_API_KEY`, `LEMONSQUEEZY_WEBHOOK_SECRET` -- Make sure to also uninstall the unused dependencies: - - `npm uninstall @lemonsqueezy/lemonsqueezy.js` - - or - - `npm uninstall stripe` -- Remove any unused fields from the `User` model in the `schema.prisma` file if they exist: - - e.g. `lemonSqueezyCustomerPortalUrl` - -Now your code is ready to go with your preferred payment processor and it's time to configure your payment processor's API keys, products, and other settings. - -## Stripe - -First, you'll need to create a Stripe account. You can do that [here](https://dashboard.stripe.com/register). - -:::tip[Star our Repo on GitHub! 🌟] -We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free! - -If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp) -::: - -### Get your test Stripe API Keys - -Once you've created your account, you'll need to get your test API keys. You can do that by navigating to [https://dashboard.stripe.com/test/apikeys](https://dashboard.stripe.com/test/apikeys) or by going to the [Stripe Dashboard](https://dashboard.stripe.com/test/dashboard) and clicking on the `Developers`. - -test api keys - -- Click on the `Reveal test key token` button and copy the `Secret key`. -- Paste it in your `.env.server` file under `STRIPE_API_KEY=` - -### Create Test Products - -To create a test product, go to the test products url [https://dashboard.stripe.com/test/products](https://dashboard.stripe.com/test/products), or after navigating to your dashboard, click the `test mode` toggle. - -test product - -- Click on the `Add a product` button and fill in the relevant information for your product. -- Make sure you select `Software as a service (SaaS)` as the product type. -- For Subscription products, make sure you select `Recurring` as the billing type. -- For One-time payment products, make sure you select `One-time` as the billing type. -- If you intend to let your users switch between two subscription plans, e.g. upgrade from hobby to pro, you'll need to create two separate products and with their own price IDs. The ability for users to swich plans can then be configured later in the [Customer Portal](#set-up-the-customer-portal). -- If you want to add different price tiers for the same product (e.g. monthly and yearly), click the `Add another price` button at the buttom. - -price ids - -- After you save the product, you'll be directed to the product page. -- Copy the price IDs and paste them in the `.env.server` file - - We've set you up with two example subscription product environment variables, `PAYMENTS_HOBBY_SUBSCRIPTION_PLAN_ID=` and `PAYMENTS_PRO_SUBSCRIPTION_PLAN_ID=`. - - As well as a one-time payment product/credits-based environment variable, `PAYMENTS_CREDITS_10_PLAN_ID=`. -- Note that if you change the names of the price IDs, you'll need to update your server code to match these names as well - -### Create a Test Customer - -To create a test customer, go to the test customers url [https://dashboard.stripe.com/test/customers](https://dashboard.stripe.com/test/customers). - -- Click on the `Add a customer` button and fill in the relevant information for your test customer. -:::note - When filling in the test customer email address, use an address you have access to and will use when logging into your SaaS app. This is important because the email address is used to identify the customer when creating a subscription and allows you to manage your test user's payments/subscriptions via the test customer portal -::: - -### Set up the Customer Portal - -Go to https://dashboard.stripe.com/test/settings/billing/portal in the Stripe Dashboard and activate and copy the `Customer portal link`. Paste it in your `.env.server` file: - -```ts title=".env.server" -STRIPE_CUSTOMER_PORTAL_URL= -``` - -If you'd like to give users the ability to switch between different plans, e.g. upgrade from a hobby to a pro subscription, go down to the `Subscriptions` dropdown and select `customers can switch plans`. - -switch plans - -Then select the products you'd like them to be able to switch between. - -Now, after your users have paid, they can click on `Manage Subscription` in the client and will be taken to the customer portal where they can update their current plan. - -### Install the Stripe CLI - -To install the Stripe CLI with homebrew, run the following command in your terminal: - -```sh -brew install stripe/stripe-cli/stripe -``` - -or for other install scripts or OSes, follow the instructions [here](https://stripe.com/docs/stripe-cli#install). - -Now, let's start the webhook server and get our webhook signing secret. - -First, login: -```sh -stripe login -``` - -:::caution[Errors running the Stripe CLI] -If you're seeing errors, consider appending `sudo` to the stripe commands. -See this [GitHuh issue](https://github.com/stripe/stripe-cli/issues/933) for more details. -::: - -```sh -stripe listen --forward-to localhost:3001/payments-webhook -``` - -You should see a message like this: - -```sh -> Ready! You are using Stripe API Version [2023-08-16]. Your webhook signing secret is whsec_8a... (^C to quit) -``` - -copy this secret to your `.env.server` file under `STRIPE_WEBHOOK_SECRET=`. - -### Testing Webhooks via the Stripe CLI - -- In a new terminal window, run the following command: - -```sh -stripe login -``` - -- start the Stripe CLI webhook forwarding on port 3001 where your Node server is running. - -```sh -stripe listen --forward-to localhost:3001/payments-webhook -``` - -:::caution[Webhook URL] -In older versions of this template, the webhook URL was `http://localhost:3001/stripe-webhook`. -If you're using an older version, **make sure to use the url that matches the webhook url in your `main.wasp` file payemnts API definition.** -::: - -remember to copy and paste the outputted webhook signing secret (`whsec_...`) into your `.env.server` file under `STRIPE_WEBHOOK_SECRET=` if you haven't already. - -- In another terminal window, trigger a test event: - -```sh -stripe trigger payment_intent.succeeded -``` - -The results of the event firing will be visible in the initial terminal window. You should see messages like this: - -```sh -... -2023-11-21 09:31:09 --> invoice.paid [evt_1OEpMPILOQf67J5TjrUgRpk4] -2023-11-21 09:31:09 <-- [200] POST http://localhost:3001/payments-webhook [evt_1OEpMPILOQf67J5TjrUgRpk4] -2023-11-21 09:31:10 --> invoice.payment_succeeded [evt_1OEpMPILOQf67J5T3MFBr1bq] -2023-11-21 09:31:10 <-- [200] POST http://localhost:3001/payments-webhook [evt_1OEpMPILOQf67J5T3MFBr1bq] -2023-11-21 09:31:10 --> checkout.session.completed [evt_1OEpMQILOQf67J5ThTZ0999r] -2023-11-21 09:31:11 <-- [200] POST http://localhost:3001/payments-webhook [evt_1OEpMQILOQf67J5ThTZ0999r] -``` - -For more info on testing webhooks, check out https://stripe.com/docs/webhooks#test-webhook - -:::tip[Star our Repo on GitHub! 🌟] -We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free! - -If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp) -::: - -### Testing Checkout and Payments via the Client - -Make sure the **Stripe CLI is running** by following the steps above. -You can then test the payment flow via the client by doing the following: - -- Click on a Buy button on the for any of the products on the homepage. You should be redirected to the checkout page. -- Fill in the form with the following test credit card number `4242 4242 4242 4242` and any future date for the expiration date and any 3 digits for the CVC. - -- Click on the "Pay" button. You should be redirected to the success page. - -- Check your terminal window for status messages and logs - -- You can also check your Database via the DB Studio to see if the user entity has been updated by running: - -```sh -wasp db studio -``` - -db studio - -- Navigate to `localhost:5555` and click on the `users` table. You should see the `subscriptionStatus` is `active` for the user that just made the purchase. - -:::note -If you want to learn more about how a user's payment status, subscription status, and subscription tier affect a user's priveledges within the app, check out the [User Overview](/general/user-overview) reference. -::: - -## Lemon Squeezy - -First, make sure you've defined your payment processor in `src/payment/paymentProcessor.ts`, as described in the [important first steps](#important-first-steps). - -Next, you'll need to create a Lemon Squeezy account in test mode. You can do that [here](https://lemonsqueezy.com). - -:::tip[Star our Repo on GitHub! 🌟] -We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free! - -If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp) -::: - -### Get your test Lemon Squeezy API Keys - -Once you've created your account, you'll need to get your test API keys. You can do that by navigating to [https://app.lemonsqueezy.com/settings/api](https://app.lemonsqueezy.com/settings/api) and creating a new API key. - -- Click on the `+` button -- Give your API key a name -- Copy and paste it in your `.env.server` file under `LEMONSQUEEZY_API_KEY=` - -### Get your Lemon Squeezy Store ID - -store id - -To get your store ID, go to the [Lemon Squeezy Dashboard](https://app.lemonsqueezy.com/settings/stores) and copy the `Store ID` from the top right corner. - -Copy and paste this number in your `.env.server` file under `LEMONSQUEEZY_STORE_ID=` - -### Create Test Products - -To create a test product, go to the test products url [https://app.lemonsqueezy.com/products](https://app.lemonsqueezy.com/products). - -- Click on the `+ New Product` button and fill in the relevant information for your product. -- Fill in the general information. -- For pricing, select the type of product you'd like to create, e.g. `Subscription` for a recurring monthly payment product or `Single Payment` for credits-based product. -add product -- Make sure you select `Software as a service (SaaS)` as the Tax category type. -- If you want to add different price tiers for `Subscription` products, click on `add variant` under the `variants` tab. Here you can input the name of the variant (e.g. "Hobby", "Pro"), and that variant's price. -add variant -- For a product with no variants, on the product page, click the `...` menu button and select `Copy variant ID` -variant id -- For a product with variants, on the product page, click on the product, go to the variants tab and select `Copy ID` for each variant. -subscription variant ids -- Paste these IDs in the `.env.server` file: - - We've set you up with two example subscription product environment variables, `PAYMENTS_HOBBY_SUBSCRIPTION_PLAN_ID=` and `PAYMENTS_PRO_SUBSCRIPTION_PLAN_ID=`. - - As well as a one-time payment product/credits-based environment variable, `PAYMENTS_CREDITS__10_PLAN_ID=`. -- Note that if you change the names of the these environment variables, you'll need to update your app code to match these names as well. - -### Create and Use the Lemon Squeezy Webhook in Local Development - -Lemon Squeezy sends messages/updates to your Wasp app via its webhook, e.g. when a payment is successful. For that to work during development, we need to expose our locally running (via `wasp start`) Wasp app and make it available online, specifically the server part of it. Since the Wasp server runs on port 3001, you should run ngrok on port 3001, which will provide you with a public URL that you can use to configure Lemon Squeezy with. - -To do this, first make sure you have installed [ngrok](https://ngrok.com/docs/getting-started/). - -Once installed, and with your wasp app running, run: -```sh -ngrok http 3001 -``` - -ngrok - -Ngrok will output a forwarding address for you. Copy and paste this address and add `/payments-webhook` to the end (this URL path has been configured for you already in `main.wasp` under the `api paymentsWebhook` definition). It should look something like this: - -```sh title="Callback URL" -https://89e5-2003-c7-153c-72a5-f837.ngrok-free.app/payments-webhook -``` - -Now go to your [Lemon Squeezy Webhooks Dashboard](https://app.lemonsqueezy.com/settings/webhooks): -- click the `+` button. -- add the newly created webhook forwarding url to the `Callback URL` section. -- give your webhook a signing secret (a long, random string). -- copy and paste this same signing secret into your `.env.server` file under `LEMONSQUEEZY_WEBHOOK_SECRET=` -- make sure to select at least the following updates to be sent: - - order_created - - subscription_created - - subscription_updated - - subscription_cancelled -- click `save` - -You're now ready to start consuming Lemon Squeezy webhook events in local development. - -## Deploying - -Once you deploy your app, you can follow the same steps, just make sure that you are no longer in test mode within the Stripe or Lemon Squeezy Dashboards. After you've repeated the steps in live mode, add the new API keys and price/variant IDs to your environment variables in your deployed environment. diff --git a/opensaas-sh/blog/src/content/docs/guides/vibe-coding.mdx b/opensaas-sh/blog/src/content/docs/guides/vibe-coding.mdx index b7ec3780f..5c352bcb1 100644 --- a/opensaas-sh/blog/src/content/docs/guides/vibe-coding.mdx +++ b/opensaas-sh/blog/src/content/docs/guides/vibe-coding.mdx @@ -32,8 +32,8 @@ The template comes with: We've also created a bunch of LLM-friendly documentation: - [Open SaaS Docs - LLMs.txt](https://docs.opensaas.sh/llms.txt) - Links to the raw text docs. - **[Open SaaS Docs - LLMs-full.txt](https://docs.opensaas.sh/llms-full.txt) - Complete docs as one text file.** ✅😎 -- Coming Soon! ~~[Wasp Docs - LLMs.txt](https://wasp.sh/llms.txt)~~ - Links to the raw text docs. -- Coming Soon! ~~[Wasp Docs - LLMs-full.txt](https://wasp.sh/llms-full.txt)~~ - Complete docs as one text file. +- [Wasp Docs - LLMs.txt](https://wasp.sh/llms.txt) - Links to the raw text docs. +- **[Wasp Docs - LLMs-full.txt](https://wasp.sh/llms-full.txt) - Complete docs as one text file.** Add these to your AI-assisted IDE settings so you can easily reference them in your chat sessions with the LLM. **In most cases, you'll want to pass the `llms-full.txt` to the LLM and ask it to help you with a specific task.** diff --git a/opensaas-sh/blog/src/content/docs/start/guided-tour.md b/opensaas-sh/blog/src/content/docs/start/guided-tour.md index 34facc663..a9a13c07d 100644 --- a/opensaas-sh/blog/src/content/docs/start/guided-tour.md +++ b/opensaas-sh/blog/src/content/docs/start/guided-tour.md @@ -186,19 +186,19 @@ For development purposes, Wasp provides a `Dummy` email sender which Open SaaS c We will explain more about these auth methods, and how to properly integrate them into your app, in the [Authentication Guide](/guides/authentication/). -### Subscription Payments with Stripe or Lemon Squeezy +### Subscription Payments with Stripe, Lemon Squeezy or Polar -No SaaS is complete without payments, specifically subscription payments. That's why this template comes with a fully functional Stripe or Lemon Squeezy integration. +No SaaS is complete without payments, specifically subscription payments. That's why this template comes with a fully functional Stripe, Lemon Squeezy and Polar integration. Let's take a quick look at how payments are handled in this template. 1. a user clicks the `BUY` button and a **Checkout session** is created on the server 2. the user is redirected to the Checkout page where they enter their payment info 3. the user is redirected back to the app and the Checkout session is completed -4. Stripe / Lemon Squeezy sends a webhook event to the server with the payment info +4. Stripe / Lemon Squeezy / Polar sends a webhook event to the server with the payment info 5. The app server's **webhook handler** handles the event and updates the user's subscription status -The payment processor you choose (Stripe or Lemon Squeezy) and its related functions can be found at `src/payment/paymentProcessor.ts`. The `Payment Processor` object holds the logic for creating checkout sessions, webhooks, etc. +The payment processor you choose (Stripe, Lemon Squeezy or Polar) and its related functions can be found at `src/payment/paymentProcessor.ts`. The `PaymentProcessor` object holds the logic for creating checkout sessions, webhooks, etc. The logic for creating the Checkout session is defined in the `src/payment/operation.ts` file. [Actions](https://wasp.sh/docs/data-model/operations/actions) are a type of Wasp Operation, specifically your server-side functions that are used to **write** or **update** data to the database. Once they're defined in the `main.wasp` file, you can easily call them on the client-side: @@ -226,7 +226,7 @@ const handleBuyClick = async (paymentPlanId) => { }; ``` -The webhook handler is defined in the `src/payment/webhook.ts` file. Unlike Actions and Queries in Wasp which are only to be used internally, we define the webhook handler in the `main.wasp` file as an API endpoint in order to expose it externally to Stripe +The webhook handler is defined in the `src/payment/webhook.ts` file. Unlike Actions and Queries in Wasp which are only to be used internally, we define the webhook handler in the `main.wasp` file as an API endpoint in order to expose it externally to the payment processor. ```js title="main.wasp" api paymentsWebhook { @@ -238,7 +238,7 @@ api paymentsWebhook { Within the webhook handler, we look for specific events that the Payment Processor sends us to let us know which payment was completed and for which user. Then we update the user's subscription status in the database. -To learn more about configuring the app to handle your products and payments, check out the [Payments Integration guide](/guides/payments-integration/). +To learn more about configuring the app to handle your products and payments, check out the [Payment Integrations guide](/guides/payment-integrations/). :::tip[Star our Repo on GitHub! 🌟] We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free! @@ -277,7 +277,7 @@ For more info on integrating Plausible or Google Analytics, check out the [Analy When you first start your Open SaaS app straight from the template, it will run, but many of the services won't work because they lack your own API keys. Here are list of services that need your API keys to work properly: - Auth Methods (Google, GitHub) -- Stripe or Lemon Squeezy +- Stripe, Lemon Squeezy or Polar - OpenAI (Chat GPT API) - Email Sending (Sendgrid) -- you must set this up if you're using the `email` Auth method - Analytics (Plausible or Google Analytics) diff --git a/opensaas-sh/tools/diff.sh b/opensaas-sh/tools/diff.sh index 6314be867..16e6c0d98 100755 --- a/opensaas-sh/tools/diff.sh +++ b/opensaas-sh/tools/diff.sh @@ -1,7 +1,10 @@ #!/usr/bin/env bash -TOOLS_DIR=$(dirname "$(realpath "$0")") # Assumes this script is in `tools/`. -cd "${TOOLS_DIR}" && cd ../.. +SCRIPT_DIR=$(dirname "$(realpath "$0")") +# Assumes this script is in `opensaas-sh/tools/`. +ROOT_DIR="${SCRIPT_DIR}/../.." + +cd "${ROOT_DIR}" rm -rf opensaas-sh/app_diff -"${TOOLS_DIR}/dope.sh" template/app opensaas-sh/app diff +"${ROOT_DIR}/tools/dope.sh" template/app opensaas-sh/app diff diff --git a/opensaas-sh/tools/patch.sh b/opensaas-sh/tools/patch.sh index 42e43154d..41a7b2f4a 100755 --- a/opensaas-sh/tools/patch.sh +++ b/opensaas-sh/tools/patch.sh @@ -1,9 +1,12 @@ #!/usr/bin/env bash -TOOLS_DIR=$(dirname "$(realpath "$0")") # Assumes this script is in `tools/`. -cd "${TOOLS_DIR}" && cd ../.. +SCRIPT_DIR=$(dirname "$(realpath "$0")") +# Assumes this script is in `opensaas-sh/tools/`. +ROOT_DIR="${SCRIPT_DIR}/../.." + +cd "${ROOT_DIR}" # Removes all files except for some gitignored files that we don't want to bother regenerating each time, # like node_modules and certain .env files. find opensaas-sh/app -mindepth 1 \( -path node_modules -o -name .env.server -o -name .env.me \) -prune -o -exec rm -rf {} + -"${TOOLS_DIR}/dope.sh" template/app opensaas-sh/app patch +"${ROOT_DIR}/tools/dope.sh" template/app opensaas-sh/app patch diff --git a/template-test/.gitignore b/template-test/.gitignore new file mode 100644 index 000000000..908f7bbc5 --- /dev/null +++ b/template-test/.gitignore @@ -0,0 +1 @@ +app/ diff --git a/template-test/README.md b/template-test/README.md new file mode 100644 index 000000000..0c1342bd9 --- /dev/null +++ b/template-test/README.md @@ -0,0 +1,40 @@ +# Template Testing + +Tests the production version of the Open SaaS template that users get from `wasp new app -t saas`. + +**Why this exists:** As a part of our release checklist, we want to test that the production version of the Open SaaS template works. + +**How it works:** We create a new Wasp app from the production Open SaaS template, and then modify the app to be production-ready (e.g. replace `Dummy` email service with `SMTP`, add Dotenv Vault with real credentials, etc.). + +## Testing a new Wasp release + +When testing a new Wasp release: + +```bash +cd template-test +# Create a new Wasp app and apply our patches to it. +./tools/patch.sh + +cd app + +# Get the environment variables from Dotenv Vault. +npm run env:pull + +# Run the dev database and create migrations. +wasp db start +wasp db migrate-dev --name "init" + +# You can stop the dev database after you created the migration. + +# Test that the app works in dev and prod modes. +# Make sure the `@wasp.sh/wasp-app-runner` is updated to the latest version. +npx @wasp.sh/wasp-app-runner dev +npx @wasp.sh/wasp-app-runner build +``` + +### If you want to update the template test app + +- Generate `app/` from template and diffs: `./tools/patch.sh` +- Modify the app in `app/` as needed and then update diffs: `./tools/diff.sh` + +For detailed information about the diff/patch workflow and MacOS setup requirements, see [../tools/README.md](../tools/README.md). diff --git a/template-test/app_diff/.env.vault.diff b/template-test/app_diff/.env.vault.diff new file mode 100644 index 000000000..4e424df12 --- /dev/null +++ b/template-test/app_diff/.env.vault.diff @@ -0,0 +1,28 @@ +--- /Users/ilakovac/dev/open-saas/template-test/tools/../../template-test/base-app/app/.env.vault ++++ template-test/app/.env.vault +@@ -0,0 +1,25 @@ ++#/-------------------.env.vault---------------------/ ++#/ cloud-agnostic vaulting standard / ++#/ [how it works](https://dotenv.org/env-vault) / ++#/--------------------------------------------------/ ++ ++# development ++DOTENV_VAULT_DEVELOPMENT="dQ0x2jNApn5/MH4GgGYz744ge51whVMQaCv4PwI4NOICbl8qoG0aNkECpOylASwZy2T6qxhIaJFhTZFxh7kN4QtyERPfvlnEmbvhDqyo1fw8hsPqypQVCo1PgWx6XfdnOAHcikkXyQGvE//bPeKhyvSHy6sctxIJYYdqhI7523RF6IYJr0gfK79B4Sv60R6Y/XhH12s1A+pFpBMZlQfSbzBdyVXoSt1O9N49gzACdZPx2gvyvPbb4r1gbAJVyEkH9DXiad3npqQ+S+sR8YgR9UlUr1wdXArS3h7G7FQwgllUyX4okHeSZOyD366wRgPMPb7Ver7YOCGfYo65effQgJ+BMtQXYCVxLAygfV3csl1txhtg/qL0Uk1EblDvdEcUc0/LglBaGEwtr9/vpBzoqCs5fxxdCSN9EHjF7b1X5u7rh1PRhpS9yA9H0B9JtuU6ZpSOqoz3hulzqXijhBaz3oPaOqa+LMZ20IOvHn5vqliY8H1SQvVrVSg8XsQL4bvk3rHNR6OOFddQ0GpmNTZLHfBDMpusXQBx3+9IGLYOqSOy6/QP4SHKZ608lIbCqiQsMmc1kKU+PmmvM4xcejRK2u1dy3FPn/qRCgROuh201aunRC5tq2Pkw3g8fJ9La2Q2bhguFtZIrI8eOZoMi6q5dmOePl48s20/Yp1K1QygdVtXNYLv1vXUU6YXCO28UWahyfVOjvpK8ZHunHoELNUvRMCVYU1uKGAoOO6Jdhq0JEG8tg7gT+n3Kv01XQIpXPtpk7gzArL02qqEHsYvPc+2WmyIndRewQ1RLbKhIujPwdYsPfcgVUg14oKAY0b5IPRVfGrjkG2xEXof8DHyOFm+eVOhNBW5O1ZtCUQ0uPqVKwatiDvlNgCwaw0D1eVDHbm2TaWL32EXMSFnNql5pRNQExUmL6QA5622l/+vG2c7wpN86Te5bh3U+rjq1Jfw71RgX4VIYqYVsUfeKgXcxG4VNN35uhSc6esH7dRhZkLJCjmsBynGLGsyrjdQ48kqxkaKPFiSSr178P1kN9c7Xrp6j607NgAoj0+EzNNyIm40cBHaMV9UCCRB0X8bOorNa8zn/e+VLXvrOuTz1XIK6Q5uautNVYErSQAYhs0GvPHmiLH7MXqVhN4W2I+xUKjjqM6oQyKqzbH70WcPUJ+mQZTHDajP4jp6YEIDw6PTx/DCgsc605QNbzayGIJzC1H4vy2jSjLulnYUNJC3BuhkeDFHRwbom1K5Ni+XZUtBk89DvL1wRxjy7gB+kyz4nbhRqyW7151RJed1Yq4/3/n3tRsf9z3QmMWAHRM6xa6Uxp8wqYM0H/C5K5oVfJU4RnzeSdWR4Q4yB02r+L3ZRrSqxvgPtuStRWYY3SkqlM1i+486i3OAAYnS80QJ7f2+jNjhI6aBKsHZgtX4sygCYOMHYvT5KrV8CSSRqeczrOB4C4M76OtJ7TIyouygdTmmsnVJksAjdMx7sDEDDFC1LpXGyLEIyROltR2exZMxmXcTFzmICODDwyZ3dZOQHMx+zUryBISOVMJbNREHN4zkbvESOyHklmA7VjwrdtGjEKDuYjZ9fMWOGQcTa/0+fcZtxW4HhFa1irszoWIuvT989HR3yZkcvRVgUen9LOVbE8B61ZfnAUSKQ6bfyfhCGK2NZ8rcukbs3NclG7nRSCUkD7Yg6KLrtPJwdYDMwkP3pWExH9WqGg0N/kkf9n5wDxr5q28lfanweP+uRjJBWu/7cpA5FcoZ09AktKS1KEcWPrZrbp4GHufeBFBMljGZB2k/XGlcX04dcmAykYCaGU8zKxKWo6QlN7GpXETpbl2/8cSs/+Mx6D/UHtyrjE0F7N7puBI+vRtZIraY+w+dUArDHPaD8cFP7DlzQVNHM8+W7JctuOFzqPSi4I3fj1vDZTaOJW2Jc8U9PcxifU5Zxm4sBWStzm8Hi8mCfPjcvk1eA+hHEyjix5g/Mzh1Q8tu9ODNyXI2plXewna3T1T6QwpXisEbC6IA2gKltbP+FXWQXR8SKi2r6vGf3vgCyj1D1tlw+UWu7mrFGxwlOYTd92Cwhdlr3bRzu0ynfpQaHePxF8yYK6q5LIZHLQPJ7KHA1CF4clLyLYNwaPtFcznhFTHRZAN+VHoZMjfe4UGp5iUrXLeT0xmnSio8OAmXxWK1YtMsNEC8TkZ7mw4aFnNPkS+hpg5Uxe8ujdgLMbX3ekT8cR0UHD2LjnJxfx0R8LbDnV1XjX2MRT6PmxKmxdq8DEyB9IzwSNFTlmpO9BSQfwc9ulK+B6PQqf2F1TLnybE3kPUfN8i48HkEwpl45cGsDueA66rNIsopiPzSDnOa7IFSqpqKG+a2HBjMT00BbmgAVEaxvxJlZhCF1GnO5ApW5mdNoJ8HsUvaMSk51ifjnYUHn/fa/VOjLH1gl90fkR57OBTuWKNWTO3IAfkBuhKfEZAHMB1mMGIJ5WPtvgVnDFCmFH/zj7yq6naHYZyDeAeRtFIjp7jSLnZoQoOYtNEhOj83oIcXgh636SciH6YHLbSrqsKs6giXferADxMg2pjSoU9aFTJnJg26gLM40A3LVgwYQK5KOsFQiSSHPCkU4YYs4MV4cinMsLLhG4gVpc7sVqdPyokcmJg9g73M6R289Qbyxc1673/tFAAKjzovosSvXxra7uiFSG4wgHOlr2ao7j8CD2fR8+4a6NHULkA5UxAjpjMO7SdDH9dTugrLe2vCX2zK4OPlfXxZBCp1N6ImSthaJfKmKlWvgZaVUweAP53riuZyK+4PzuK1cg8max1CpwROk+Ms7736P+OrqYGPgJVzwLM37YdnymBH9nCCy2Kq9e6ptzMrpIb6dvsmnsEbOQRTdBC9xmWATDf9iVXOwPCjUx4kP5eX6pES65v1+OLZKZdO2ioup6amsauTCpPUDBbYi+ztXhTOZYxpsWaaarGcl394S+YzruCTromF4kN+fhuFoRS0/H6hHTFfUWiTW69ij+yqoHkj9LUk4OsVlDIrrB+6Xs2lWyPOgum+OrZDiZKZFCTWyr0365vRmqiFCLY36iV76LZ111HMZjhlYXw5hRRg2MuMJxa8iEd6dd0/eNu75hGyQMt5ca6a882iqYjmzXZAD1iyqojNrLmlz5JE6LDmIZR70YFo1Ep95oWbiiGnN8OW5er/yvYR8XIsT1iX/iM3eTtIhcmHydfyxaTaMOJs5kkGbH39rNIfvKcizTT0v+hLC4kWvcZDr+p+vXuAWFhf8vZTHK2TiPARJ1oKmKWiTovk6l5Ml0RUckbRAYQZ62rMMURQPvEl/okhYkKzu9kOxcQE57A6ladBDcJ6aByLpVtbdRFDdg0ZJqQiHTyvzGgubVAdLn7C12UBQhh7jO07SpAlj5OgsEAda5r6aEqormejjJFw6mMM3vDEqkOVH5In7SLjdhTf37ILzyPJG5TRlUdg3WbRxOwdR+IA+Djw2G6KeIPPMeX1xU1zWxlCo03eaILZBxWLDSXpfw5TaxNa5+YGAYqi+ojiPfGcf75lE7HrgNtL+O7Q3QWBpzrVTKsdi6UrKZPuL9f1BW/BqwxVlFDz+hY5MjAyRIz0ve1/46nKXMGsc7V8ss59NQmWjx/4mjG6GjHxN2p8mAwzvAygBhM1s1TDRPfo9y3/gzzhWjNtiiUC1pU3F/8h/IrfnuzHKnOcDxkhmBzd3IVTc5IxJ4tGLcQQ3mHe2uJjTgiM3olPSEEsr+mUhVLzmzYrEUjnQ+lW8R0diK2qa4pOS6CJEhh3bizfktM+f1me+Qe+WfZ23CiapRqneJOqNtKN6gjnaYeH2gT14GGAnNY9KBRqaMqpfQIPMVUcw4aub5o29zoXIKRs56UURU04Sk0qpGel7/OyHrNxPXSbsAxTcaXABO4U1cdoh6LPdtSPaU2+r+Um8NPKyE/IwGRnB8J7uqpMqOMuHgkJuOp0udQNrU9fC9/u0TJFN7MF8Mc+mFXif5+lHy0F2PSRQTPZzIdFdZmotZC4Oi+W5RVr3r7G9kVnoNp7Rp9kc2nv1mZ9ACqZA7sTNoPxuF2PVv+V5+ALytQYCwhSRnMlJPG+upiSQ1xonn6PU4GTNHr5S2QKPtfa5mFXJjWM1Xgh1MHahum0Uky1EOi7PkceGERBXR4TAGlGNcYThn5xf7Em9yNsUKSa1XZDsbtBgclp0kMs6AIwTCBOkymhHvWYaCPAY3rY0H1PXSEmCFMMdOrR3GuJ8ySItZDzA7PHId+VItsq5JCWDbsMeZaMnvJMuVM55vAl3594qvCI1W1R8L/t+MM9uWsnGzCD+m434twbrp+9N2ve3ZiMMDzlaSRZXB66fssRgtscFASfTapW3xwwxO+JxPVm1OIVbD6jaMHcUxgskc3nQoHsHBQgdRlquciCZu7TAozOrgggBf21dd2/2UQnar4xyB55ZUIo6YmlwpmxaxLDGOenAH80UPI1nuzoaKpkAWT1l5OQbBzMjhPHNUKB/5JR63QB7xyrF/9sojQS7ZOOqyZsXRooatFhuRprchbRdLAQoSNk3VEVkwiXJlD/6sW8PU3R0KONx6MVlq3DPF5lnMLadwJnfzaOhkidlH/AKVfwKFElMFAwfq9k04qNy9fW6fbqqhMSgGCtBCwWw451AThoePce+/a7s5o9++6lLqVfk56EWHH9NxcrocqV3SR3GhjHjbaoqNSpNazQy/05srwElFT6n945yFfq3pwvsxP2DZexVjA7SqliZMsuOsacKzOfYBGYnZs0rz5EvU+rjGaD4GnnvCrDBKRxpaDz1vGWFm44oCiEVaM5Qzn9eQ5l4nvul5R8Xn6Pd+5EX6YCUJiEz/niv5e5es2cHBb6lk3hEcJ4XE9NrwXAziB6G8Gq5PcgnqPlBH1I5RBWv4VjLtlQe5qleOx8gOsdlnUHrrA0GqYFh4omfMpU2LV49Ym/" ++DOTENV_VAULT_DEVELOPMENT_VERSION=3 ++ ++# ci ++DOTENV_VAULT_CI="0kVgjX4gDSFLdOkWOqXHcN6VOvVhMN5wJ9zgF3M7enHE7613DjnvWxS/Xtr2kFNv71jp+Qf51mLpmajTR4kb0whC+uLFa/7xZtXBFV0k5ac4NglcAF2dPbbi2ymqqFzjz5SwTVjQ3xzqpGbmaw/spmoapH1YEaWphA6GSOkJciI8NRqOYsLq7T1AdPrPudt6SUaroRKHD/kPk6ipt7c6sWdkMzKRF2Yv9FVLHDSRr86HCfVjW5FDfgc2eo2ITOONys72Wk/JK1RB1fntT9YIxUP2mX5rWtxsRDw5PPiu4OmrfC93p81BhwlPFMBBV7fOnJKs7jx5NPUEWlPS8xf4StQ9by2/oPmBrsFdzI84ikF6yNvoac7idj2tHsnaOjPWMmTT3Fk88Bt+K0HqMjdpNT7TilEbXbIxP4vq5SjSK9/whnsEqymWFO1aimo+Z6rzC9HNXecoryjtvDk7Y9D60V4oLyNOnSMf4v/+hvIYoAwIfnajVGUQ3y5rw1TLOEm8YQ61DgmArWDk8GhHnk/U6zVIpE8r/wuvYhn5GjMrG+5ZreUJMjRN58Fo/PGJszAciQh65gD5iTlL7qiaM5qgRA7SWuywxJc+3q8YmcHLBAKS1MeSsOw4bHMH1mphO1tSobRvZCS5T/kpCSwbAZviZq5Vx+oFgk39ymAkQ9aKwiTrHtKr0V/BxpqeUt223UC/y3M1TuaCAjs2szKckGa5FPjzWMAdHKShum4ikwGncX6CA5i+c1zimO8+7gj1qQPdZPBpcgHQ2kzX5Njg3InENowIE3RXd+a/ZmKVyP1IkThofsozOgxC+V3OTqriI4SRBIU+ZQ2xfwnsuTETpe/0Bf/pvKanxqebkouHhSUTPK8aF4HOzPbyiPpjFLtw/dmlN9MXdZag02+JGhlMCQ==" ++DOTENV_VAULT_CI_VERSION=2 ++ ++# staging ++DOTENV_VAULT_STAGING="JMEI1tbOz427qXvVvq9eL5gCiTE62+uCHKjt20WI80iwr2ROHrVjeS4rOXVazZ+BWlnyI8utwk1h4qLqxnqE9PlaCxH78MZX/K8Z8DV30QciubrmgnleUuO25EPlivbHqWWn0nQEMGaLemERXisx/fb/F2VG4aJwMzHd0kEGTnIWjEczGV2VlAgrEobPSZBz6BC02ioIOh6+nbJd5aDaMxaxrxuEJUFTxXDjbEigsJfaZyj2BknsUu/hHdCIPP8TNmAZdPBxmjkiu6rLEKxJ/mC8Hjdu/deZa6fiL8rfLN5ckmIJC+oWW57ectOyPMTq2Byxq/GenB1gMCpB/eOpltfXJvwqSPOrk4EsD62Ycdpr5Dxn2Ui/MN7IW0PREQwkawEmgN/hFp/AVqV5x/dQakqnpFek3qG4nl9COvHWkujd0thfjxRg+iYPEZxKvw6jXPMuG5rUAq0hz2uiiQu+sl7EoPgVtMhmHAeA0XLLPN9yAcdTRH0fzXbgv+DCHR9Xi+my/9PNNi3FCQCqcKUiMSWRqA8pL9xGP7J45LICcmHbHuXhbffSp8sGvPthAKLbCiYBEH97PPOzH6qjaU5gz0qd/tM5XhwDYrWE8i/NXCFTX++l4BOJBN9zb71Sx2KALK5c2aQKjpN5LT1bnF30OchDkFXY/p79kuvopQIEkGJlcBBLyf8hgnlgQdx0e/nXFJVTh/evhc3ndp8nATFwZtoKx1Vgvrq4ux09aaO6ErII4QJZqMBjgpGuAMj9RNE0iUFwm1S9BVVyHqndDnr+mTyKpkUEqzGQHWuZ+KuAsnYQHcOJUo1Wh3zYs0BHswBvbenWcrM/0QRRLoSKziNIRfCudT+J/5ED6v/3JOhACvfq6dD7vlLgURof4b8y9bXb27GX2ycyHZGXjlbZgg==" ++DOTENV_VAULT_STAGING_VERSION=2 ++ ++# production ++DOTENV_VAULT_PRODUCTION="eSIUBg42a0f0KXdMcsp1tfg3nEHp6ESJNhNOysVtT8AZC/dAQculRnSkA07aY4mNPyAB7B1Y0MayMdNnnB44drdepJYUNNwsBwCxANVl6hgjll8q6CWEgE8e+vW6jTQJDq0myaCSvCgBqfQYDATTTHVJ1Q4FZECavJlqjfNIaAy09evubbhzTmL9z/i+S7jQJCLmirLET438Hnz04/i2S3CtdtsLEtjLWbUvrSkiVALBYkCFxQcmasoit2U/0fgjGaeNuWtfAB7Iku3CSdmcO8kQBtH0j+7TbW7I0cfVTUYH9ipcQCy0vjwx1GwsHWBi5IqPfj4bD/HUTPwinn7MrEBtM2cZ3uksSeWk1x+Ks7A5sQnL7XzgUxnl070TiRZJSb0TYuzXM8ILt7gq0frgKSUKRXkeH+egxjBrMAGQ7vY0ebdBHlBHPHOeF9iiFoaujWdReBitbawHYORG8Ct8pvstiTdj98w7g7WB434/Eh0L0pi8Ydepj40/8OpA3nCES9cCIi0uaFlr+arIh3w6h5FsTqaLgh+MmbpNXnRR99/r/0zzKq3/9d1wnIbc7uHkoOSEkTlD5ceEf+IO85A4jsQFOXYJpGV8ns8z4xWLTsU+3GyWIIGDkkNR0CY8PjdkBeQJPEvZNPDnn+LK9C42Clkm1eQy4aqHaagJxFGYp/j79S7Lm4kLJZMHywKi628CsHZO/moPvkfUGrEO2KmqWHvnxWDh5PggV3TAJWOMk7p2ykuFRRb26MgMf9f8oTa+CSs2Jk5W7TF10xaYZDz7fCyUBdMK+JcguPcq0mFw9eoDf9i8FLOKF26VuK7Z14+fRTZN/YHU+u75ru6WDymRpm0ZhCBDdYy80gM45089Scp2bpnKIWg6s7bLdb9R/NRUhK1PZRDEzw3SkDZBVQ==" ++DOTENV_VAULT_PRODUCTION_VERSION=2 ++ ++#/----------------settings/metadata-----------------/ ++DOTENV_VAULT="vlt_57f67ae090fbd4c01066b1af1342a8ba518da1296c6595b9fcd233af37a5fce4" ++DOTENV_API_URL="https://vault.dotenv.org" ++DOTENV_CLI="npx dotenv-vault@latest" diff --git a/template-test/app_diff/.gitignore.diff b/template-test/app_diff/.gitignore.diff new file mode 100644 index 000000000..ade794676 --- /dev/null +++ b/template-test/app_diff/.gitignore.diff @@ -0,0 +1,27 @@ +--- /Users/ilakovac/dev/open-saas/template-test/tools/../../template-test/base-app/app/.gitignore ++++ template-test/app/.gitignore +@@ -1,2 +1,24 @@ + node_modules/ + .wasp/ ++ ++# Dotenv Vault entries ++# These are automatically added by Dotenv Vault when running `npm run env:pull`. ++# They're kept in this diff to avoid having to manually revert .gitignore changes ++# each time we test modifications. They don't cause any harm and make the process ++# less annoying. ++.env* ++.flaskenv* ++!.env.project ++!.env.vault ++ ++# Don't ignore example dotenv files. ++!.env.example ++!.env.*.example ++ ++# Ignore migrations dir to avoid including them in the diffs. We want to test ++# generating migrations each time. ++migrations/ ++ ++# Ignore package-lock.json to avoid including it in the diffs. Our users start ++# new projects without it, and we want to test the template like our users. ++package-lock.json diff --git a/template-test/app_diff/deletions b/template-test/app_diff/deletions new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/template-test/app_diff/deletions @@ -0,0 +1 @@ + diff --git a/template-test/app_diff/main.wasp.diff b/template-test/app_diff/main.wasp.diff new file mode 100644 index 000000000..20fc4b88b --- /dev/null +++ b/template-test/app_diff/main.wasp.diff @@ -0,0 +1,28 @@ +--- /Users/ilakovac/dev/open-saas/template-test/tools/../../template-test/base-app/app/main.wasp ++++ template-test/app/main.wasp +@@ -39,7 +39,7 @@ + email: { + fromField: { + name: "Open SaaS App", +- email: "me@example.com" ++ email: "opensaastemplate@mg.wasp.sh" + }, + emailVerification: { + clientRoute: EmailVerificationRoute, +@@ -84,14 +84,11 @@ + }, + + emailSender: { +- // NOTE: "Dummy" provider is just for local development purposes. +- // Make sure to check the server logs for the email confirmation url (it will not be sent to an address)! +- // Once you are ready for production, switch to e.g. "SendGrid" or "Mailgun" providers. Check out https://docs.opensaas.sh/guides/email-sending/ . +- provider: Dummy, ++ provider: SMTP, + defaultFrom: { + name: "Open SaaS App", + // When using a real provider, e.g. SendGrid, you must use the same email address that you configured your account to send out emails with! +- email: "me@example.com" ++ email: "opensaastemplate@mg.wasp.sh" + }, + }, + } diff --git a/template-test/app_diff/package.json.diff b/template-test/app_diff/package.json.diff new file mode 100644 index 000000000..bcc318846 --- /dev/null +++ b/template-test/app_diff/package.json.diff @@ -0,0 +1,13 @@ +--- /Users/ilakovac/dev/open-saas/template-test/tools/../../template-test/base-app/app/package.json ++++ template-test/app/package.json +@@ -1,6 +1,10 @@ + { + "name": "opensaas", + "type": "module", ++ "scripts": { ++ "env:pull": "npx dotenv-vault@latest pull development .env.server", ++ "env:push": "npx dotenv-vault@latest push development .env.server" ++ }, + "workspaces": [ + ".wasp/build/*", + ".wasp/out/*" diff --git a/template-test/tools/diff.sh b/template-test/tools/diff.sh new file mode 100755 index 000000000..9a903513d --- /dev/null +++ b/template-test/tools/diff.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -e + +command -v wasp >/dev/null || { echo "Error: Wasp CLI not found"; exit 1; } + +SCRIPT_DIR=$(dirname "$(realpath "$0")") +ROOT_DIR="${SCRIPT_DIR}/../.." +BASE_DIR="${ROOT_DIR}/template-test/base-app" + +# Clean up the temporary base app directory on exit. +trap 'rm -rf "${BASE_DIR}"' EXIT + +cd "${ROOT_DIR}" + +rm -rf "${BASE_DIR}" +(cd "${ROOT_DIR}/template-test" && wasp new -t saas base-app) +(cd "${BASE_DIR}/app" && git init -b main -q && git add .) + +rm -rf template-test/app_diff +"${ROOT_DIR}/tools/dope.sh" "${BASE_DIR}/app" template-test/app diff diff --git a/template-test/tools/patch.sh b/template-test/tools/patch.sh new file mode 100755 index 000000000..be0109e88 --- /dev/null +++ b/template-test/tools/patch.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +set -e + +command -v wasp >/dev/null || { echo "Error: Wasp CLI not found"; exit 1; } + +SCRIPT_DIR=$(dirname "$(realpath "$0")") +ROOT_DIR="${SCRIPT_DIR}/../.." +BASE_DIR="${ROOT_DIR}/template-test/base-app" + +# Clean up the temporary base app directory on exit. +trap 'rm -rf "${BASE_DIR}"' EXIT + +cd "${ROOT_DIR}" + +rm -rf "${BASE_DIR}" +(cd "${ROOT_DIR}/template-test" && wasp new -t saas base-app) +(cd "${BASE_DIR}/app" && git init -b main -q && git add .) + +# Clean up existing derived app directory if it exists. +rm -rf template-test/app + +"${ROOT_DIR}/tools/dope.sh" "${BASE_DIR}/app" template-test/app patch diff --git a/template/.gitignore b/template/.gitignore index a532fe552..3536bd95c 100644 --- a/template/.gitignore +++ b/template/.gitignore @@ -1,4 +1,4 @@ -# MacOS-specific files. +# macOS-specific files. .DS_Store # Ignore all dotenv files by default to prevent accidentally committing any secrets. diff --git a/template/app/.cursor/rules/authentication.mdc b/template/app/.cursor/rules/authentication.mdc index cf8e7addc..25e622f43 100644 --- a/template/app/.cursor/rules/authentication.mdc +++ b/template/app/.cursor/rules/authentication.mdc @@ -92,16 +92,18 @@ See the Wasp Auth docs for available methods and complete guides [wasp-overview. - Redirect or show alternative content if the user is not authenticated. ```typescript import { useAuth } from 'wasp/client/auth'; - import { Redirect } from 'wasp/client/router'; // Or use Link + import { useNavigate } from 'react-router-dom'; const MyProtectedPage = () => { const { data: user, isLoading, error } = useAuth(); // Returns AuthUser | null + const navigate = useNavigate(); if (isLoading) return
Loading...
; // If error, it likely means the auth session is invalid/expired if (error || !user) { // Redirect to login page defined in main.wasp (auth.onAuthFailedRedirectTo) - // Or return ; + // or use the navigate hook from react-router-dom for more fine-grained control + navigate('/some-other-path'); return
Please log in to access this page.
; } diff --git a/template/app/.cursor/rules/ui-components.mdc b/template/app/.cursor/rules/ui-components.mdc index 2c89d1e32..23e413562 100644 --- a/template/app/.cursor/rules/ui-components.mdc +++ b/template/app/.cursor/rules/ui-components.mdc @@ -2,40 +2,45 @@ description: Describes where the ui components built on top with Shadcn UI exist, how they are customized, and Wasp specific rules concerning how new ShadCN UI components should be installed alwaysApply: false --- -Only ShadCN UI version 2.3.0 should be used with Wasp at the moment. Due to dependency conflicts Wasp cannot be used with Tailwindcss v4, which the newer version of Shadcn depends on. +Due to dependency conflicts Wasp cannot be used with Tailwindcss v4. -Shadcn has already been setup with this project template, so there is no need to install it. All the ShadCN specific components exist in [src/components/ui](../../src/components/ui/) +ShadCN has already been setup with this project template, so there is no need to install it. All the ShadCN specific components exist in [src/client/components/ui](../../src/client/components/ui/) ## Adding a new ShadCN component -### 1. Add a new component + +We will use button as an example. +Same rules apply to any ShadCN component. + +### 1. Add a new component ```bash -npx shadcn@2.3.0 add button +npx shadcn@latest add button ``` -### 2. Adjust the `utils` import in `button.tsx` (for each component you add) +This will generate a button component in `src/client/components/ui/button.tsx`. + +### 2. Adjust the `utils` import in `button.tsx` -There will be a brand new `button.tsx` file in `src/components/ui`. We need to fix some import issues: ```diff import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" --import { cn } from "s/lib/utils" -+import { cn } from "../../lib/utils" +-import { cn } from "src/lib/utils" ++import { cn } from "../../utils" ``` -### 3. Use the `Button` component -Now you are ready to use the `Button` component. That's it! -```jsx -import './Main.css' +### 3. Use the `Button` React component +Now you are ready to use the `Button` component. That's it! +```tsx import { Button } from './components/ui/button' -export const MainPage = () => { +function SomePage() { return ( -
+
) } +``` diff --git a/template/app/.env.server.example b/template/app/.env.server.example index 65d02b9e1..c426bcf45 100644 --- a/template/app/.env.server.example +++ b/template/app/.env.server.example @@ -3,53 +3,60 @@ # If you use `wasp start db` then you DO NOT need to add a DATABASE_URL env variable here. # DATABASE_URL= -# For testing, go to https://dashboard.stripe.com/test/apikeys and get a test stripe key that starts with "sk_test_..." +# For testing, go to https://dashboard.stripe.com/test/apikeys and get a test stripe key that starts with "sk_test_...". STRIPE_API_KEY=sk_test_... -# After downloading starting the stripe cli (https://stripe.com/docs/stripe-cli) with `stripe listen --forward-to localhost:3001/payments-webhook` it will output your signing secret +# After downloading starting the stripe cli (https://stripe.com/docs/stripe-cli) with `stripe listen --forward-to localhost:3001/payments-webhook` it will output your signing secret. STRIPE_WEBHOOK_SECRET=whsec_... -# You can find your Stripe customer portal URL in the Stripe Dashboard under the 'Customer Portal' settings. -STRIPE_CUSTOMER_PORTAL_URL=https://billing.stripe.com/... -# For testing, create a new store in test mode on https://lemonsqueezy.com +# For testing, create a new store in test mode on https://lemonsqueezy.com. LEMONSQUEEZY_API_KEY=eyJ... -# After creating a store, you can find your store id in the store settings https://app.lemonsqueezy.com/settings/stores +# After creating a store, you can find your store id in the store settings https://app.lemonsqueezy.com/settings/stores. LEMONSQUEEZY_STORE_ID=012345 -# define your own webhook secret when creating a new webhook on https://app.lemonsqueezy.com/settings/webhooks +# define your own webhook secret when creating a new webhook on https://app.lemonsqueezy.com/settings/webhooks. LEMONSQUEEZY_WEBHOOK_SECRET=my-webhook-secret -# If using Stripe, go to https://dashboard.stripe.com/test/products and click on + Add Product -# If using Lemon Squeezy, go to https://app.lemonsqueezy.com/products and create new products and variants +# Generate a token at https://sandbox.polar.sh/dashboard/[your-org-slug]/settings. +POLAR_ORGANIZATION_ACCESS_TOKEN=polar_oat_... +# Define your own webhook secret when creating a new webhook at https://sandbox.polar.sh/dashboard/[your-org-slug]/settings/webhooks. +POLAR_WEBHOOK_SECRET=polar_whs_... +# For production, set this to false, make sure to generate production organization and products. +POLAR_SANDBOX_MODE=true + +# If using Stripe, go to https://dashboard.stripe.com/test/products and click on `+ Add Product`. +# If using Lemon Squeezy, go to https://app.lemonsqueezy.com/products and create new products and variants. +# If using Polar, go to https://sandbox.polar.sh/dashboard/[your-org-slug]/products and click on `+ New Product`. PAYMENTS_HOBBY_SUBSCRIPTION_PLAN_ID=012345 PAYMENTS_PRO_SUBSCRIPTION_PLAN_ID=012345 PAYMENTS_CREDITS_10_PLAN_ID=012345 -# set this as a comma-separated list of emails you want to give admin privileges to upon registeration +# Set this as a comma-separated list of emails you want to give admin privileges to upon registeration. ADMIN_EMAILS=me@example.com,you@example.com,them@example.com -# see our guide for setting up google auth: https://wasp.sh/docs/auth/social-auth/google +# See our guide for setting up google auth: https://wasp.sh/docs/auth/social-auth/google. GOOGLE_CLIENT_ID=722... GOOGLE_CLIENT_SECRET=GOC... -# get your sendgrid api key at https://app.sendgrid.com/settings/api_keys +# Get your sendgrid api key at https://app.sendgrid.com/settings/api_keys. SENDGRID_API_KEY=test... -# (OPTIONAL) get your openai api key at https://platform.openai.com/account +# (OPTIONAL) Get your openai api key at https://platform.openai.com/account. OPENAI_API_KEY=sk-k... -# (OPTIONAL) get your plausible api key at https://plausible.io/login or https://your-plausible-instance.com/login +# (OPTIONAL) Get your plausible api key at https://plausible.io/login or https://your-plausible-instance.com/login. PLAUSIBLE_API_KEY=gUTgtB... -# You will find your site id in the Plausible dashboard. It will look like 'opensaas.sh' +# You will find your site id in the Plausible dashboard. It will look like 'opensaas.sh'. PLAUSIBLE_SITE_ID=yoursite.com -PLAUSIBLE_BASE_URL=https://plausible.io/api # if you are self-hosting plausible, change this to your plausible instance's base url +# If you are self-hosting plausible, change this to your plausible instance's base url. +PLAUSIBLE_BASE_URL=https://plausible.io/api -# (OPTIONAL) get your google service account key at https://console.cloud.google.com/iam-admin/serviceaccounts +# (OPTIONAL) Get your google service account key at https://console.cloud.google.com/iam-admin/serviceaccounts. GOOGLE_ANALYTICS_CLIENT_EMAIL=email@example.gserviceaccount.com -# Make sure you convert the private key within the JSON file to base64 first with `echo -n "PRIVATE_KEY" | base64`. see the docs for more info. +# Make sure you convert the private key within the JSON file to base64 first. See the docs for more info. GOOGLE_ANALYTICS_PRIVATE_KEY=LS02... -# You will find your Property ID in the Google Analytics dashboard. It will look like '987654321' +# You will find your Property ID in the Google Analytics dashboard. It will look like '987654321'. GOOGLE_ANALYTICS_PROPERTY_ID=123456789 -# (OPTIONAL) get your aws s3 credentials at https://console.aws.amazon.com and create a new IAM user with S3 access +# (OPTIONAL) Get your aws s3 credentials at https://console.aws.amazon.com and create a new IAM user with S3 access. AWS_S3_IAM_ACCESS_KEY=ACK... AWS_S3_IAM_SECRET_KEY=t+33a... AWS_S3_FILES_BUCKET=your-bucket-name diff --git a/template/app/README.md b/template/app/README.md index e15c82638..b63e8c03a 100644 --- a/template/app/README.md +++ b/template/app/README.md @@ -2,10 +2,6 @@ Built with [Wasp](https://wasp.sh), based on the [Open Saas](https://opensaas.sh) template. -## UI Components - -This template includes [ShadCN UI](https://ui.shadcn.com/) v2 for beautiful, accessible React components. See [SHADCN_SETUP.md](./SHADCN_SETUP.md) for details on how to use ShadCN components in your app. - ## Development ### Running locally diff --git a/template/app/components.json b/template/app/components.json index 4ec2fd42d..498df4ba8 100644 --- a/template/app/components.json +++ b/template/app/components.json @@ -11,11 +11,11 @@ "prefix": "" }, "aliases": { - "components": "src/components", - "utils": "src/lib/utils", - "ui": "src/components/ui", - "lib": "src/lib", - "hooks": "src/hooks" + "lib": "src/client", + "components": "src/client/components", + "ui": "src/client/components/ui", + "utils": "src/client/utils", + "hooks": "src/client/hooks" }, "iconLibrary": "lucide" } diff --git a/template/app/main.wasp b/template/app/main.wasp index f91afb58c..36a497b30 100644 --- a/template/app/main.wasp +++ b/template/app/main.wasp @@ -1,6 +1,6 @@ app OpenSaaS { wasp: { - version: "^0.18.0" + version: "^0.20.0" }, title: "My Open SaaS App", @@ -11,7 +11,7 @@ app OpenSaaS { "", "", "", - + "", "", "", @@ -221,8 +221,13 @@ page FileUploadPage { component: import FileUpload from "@src/file-upload/FileUploadPage" } -action createFile { - fn: import { createFile } from "@src/file-upload/operations", +action createFileUploadUrl { + fn: import { createFileUploadUrl } from "@src/file-upload/operations", + entities: [User, File] +} + +action addFileToDb { + fn: import { addFileToDb } from "@src/file-upload/operations", entities: [User, File] } @@ -235,6 +240,11 @@ query getDownloadFileSignedURL { fn: import { getDownloadFileSignedURL } from "@src/file-upload/operations", entities: [User, File] } + +action deleteFile { + fn: import { deleteFile } from "@src/file-upload/operations", + entities: [User, File] +} //#endregion //#region Analytics @@ -295,7 +305,7 @@ page NotFoundPage { //#endregion //#region Contact Form Messages -// TODO: +// TODO: // add functionality to allow users to send messages to admin // and make them accessible via the admin dashboard route AdminMessagesRoute { path: "/admin/messages", to: AdminMessagesPage } diff --git a/template/app/package.json b/template/app/package.json index a0b190c89..216bebdee 100644 --- a/template/app/package.json +++ b/template/app/package.json @@ -1,14 +1,18 @@ { "name": "opensaas", "type": "module", + "workspaces": [ + ".wasp/build/*", + ".wasp/out/*" + ], "dependencies": { "@aws-sdk/client-s3": "^3.523.0", "@aws-sdk/s3-presigned-post": "^3.750.0", "@aws-sdk/s3-request-presigner": "^3.523.0", "@google-analytics/data": "4.1.0", - "@headlessui/react": "1.7.13", "@hookform/resolvers": "^5.1.1", "@lemonsqueezy/lemonsqueezy.js": "^3.2.0", + "@polar-sh/sdk": "^0.34.3", "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.2", @@ -20,22 +24,20 @@ "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.5", + "@radix-ui/react-toast": "^1.2.14", "@tailwindcss/forms": "^0.5.3", "@tailwindcss/typography": "^0.5.7", "apexcharts": "3.41.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "headlessui": "^0.0.0", "lucide-react": "^0.525.0", - "node-fetch": "3.3.0", "openai": "^4.55.3", "prettier": "3.1.1", "prettier-plugin-tailwindcss": "0.5.11", - "react": "^18.2.0", + "react": "^19.2.1", "react-apexcharts": "1.4.1", - "react-dom": "^18.2.0", + "react-dom": "^19.2.1", "react-hook-form": "^7.60.0", - "react-hot-toast": "^2.4.1", "react-router-dom": "^6.26.2", "stripe": "18.1.0", "tailwind-merge": "^2.2.1", @@ -48,7 +50,7 @@ "devDependencies": { "@faker-js/faker": "8.3.1", "@types/express": "^5.0.0", - "@types/react": "^18.0.37", + "@types/react": "^19.2.7", "prisma": "5.19.1", "typescript": "5.8.2", "vite": "^7.0.6" diff --git a/template/app/public/fonts/Satoshi-Black.eot b/template/app/public/fonts/Satoshi-Black.eot deleted file mode 100644 index 11747f362..000000000 Binary files a/template/app/public/fonts/Satoshi-Black.eot and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Black.ttf b/template/app/public/fonts/Satoshi-Black.ttf deleted file mode 100644 index 62015aca0..000000000 Binary files a/template/app/public/fonts/Satoshi-Black.ttf and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Black.woff b/template/app/public/fonts/Satoshi-Black.woff deleted file mode 100644 index a6bee36dd..000000000 Binary files a/template/app/public/fonts/Satoshi-Black.woff and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-BlackItalic.eot b/template/app/public/fonts/Satoshi-BlackItalic.eot deleted file mode 100644 index de2edbbc1..000000000 Binary files a/template/app/public/fonts/Satoshi-BlackItalic.eot and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-BlackItalic.ttf b/template/app/public/fonts/Satoshi-BlackItalic.ttf deleted file mode 100644 index 74410b974..000000000 Binary files a/template/app/public/fonts/Satoshi-BlackItalic.ttf and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-BlackItalic.woff b/template/app/public/fonts/Satoshi-BlackItalic.woff deleted file mode 100644 index 0e07e1c5e..000000000 Binary files a/template/app/public/fonts/Satoshi-BlackItalic.woff and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-BlackItalic.woff2 b/template/app/public/fonts/Satoshi-BlackItalic.woff2 deleted file mode 100644 index 9d5c911dc..000000000 Binary files a/template/app/public/fonts/Satoshi-BlackItalic.woff2 and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Bold.eot b/template/app/public/fonts/Satoshi-Bold.eot deleted file mode 100644 index 390ae252b..000000000 Binary files a/template/app/public/fonts/Satoshi-Bold.eot and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Bold.ttf b/template/app/public/fonts/Satoshi-Bold.ttf deleted file mode 100644 index 00bc985b2..000000000 Binary files a/template/app/public/fonts/Satoshi-Bold.ttf and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Bold.woff b/template/app/public/fonts/Satoshi-Bold.woff deleted file mode 100644 index bba8257f6..000000000 Binary files a/template/app/public/fonts/Satoshi-Bold.woff and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-BoldItalic.eot b/template/app/public/fonts/Satoshi-BoldItalic.eot deleted file mode 100644 index 426be2ac1..000000000 Binary files a/template/app/public/fonts/Satoshi-BoldItalic.eot and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-BoldItalic.ttf b/template/app/public/fonts/Satoshi-BoldItalic.ttf deleted file mode 100644 index 24f012cbf..000000000 Binary files a/template/app/public/fonts/Satoshi-BoldItalic.ttf and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-BoldItalic.woff b/template/app/public/fonts/Satoshi-BoldItalic.woff deleted file mode 100644 index 8bcb7a6e2..000000000 Binary files a/template/app/public/fonts/Satoshi-BoldItalic.woff and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-BoldItalic.woff2 b/template/app/public/fonts/Satoshi-BoldItalic.woff2 deleted file mode 100644 index 225527f78..000000000 Binary files a/template/app/public/fonts/Satoshi-BoldItalic.woff2 and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Italic.eot b/template/app/public/fonts/Satoshi-Italic.eot deleted file mode 100644 index 64039a84e..000000000 Binary files a/template/app/public/fonts/Satoshi-Italic.eot and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Italic.ttf b/template/app/public/fonts/Satoshi-Italic.ttf deleted file mode 100644 index c214f4fe5..000000000 Binary files a/template/app/public/fonts/Satoshi-Italic.ttf and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Italic.woff b/template/app/public/fonts/Satoshi-Italic.woff deleted file mode 100644 index edd4d9322..000000000 Binary files a/template/app/public/fonts/Satoshi-Italic.woff and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Italic.woff2 b/template/app/public/fonts/Satoshi-Italic.woff2 deleted file mode 100644 index 8b98599d3..000000000 Binary files a/template/app/public/fonts/Satoshi-Italic.woff2 and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Light.eot b/template/app/public/fonts/Satoshi-Light.eot deleted file mode 100644 index d8fcaccd9..000000000 Binary files a/template/app/public/fonts/Satoshi-Light.eot and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Light.ttf b/template/app/public/fonts/Satoshi-Light.ttf deleted file mode 100644 index b41a2d4a5..000000000 Binary files a/template/app/public/fonts/Satoshi-Light.ttf and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Light.woff b/template/app/public/fonts/Satoshi-Light.woff deleted file mode 100644 index 8f05e4e9f..000000000 Binary files a/template/app/public/fonts/Satoshi-Light.woff and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-LightItalic.eot b/template/app/public/fonts/Satoshi-LightItalic.eot deleted file mode 100644 index e34a0df4a..000000000 Binary files a/template/app/public/fonts/Satoshi-LightItalic.eot and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-LightItalic.ttf b/template/app/public/fonts/Satoshi-LightItalic.ttf deleted file mode 100644 index 08f5db573..000000000 Binary files a/template/app/public/fonts/Satoshi-LightItalic.ttf and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-LightItalic.woff b/template/app/public/fonts/Satoshi-LightItalic.woff deleted file mode 100644 index a03a50d7b..000000000 Binary files a/template/app/public/fonts/Satoshi-LightItalic.woff and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-LightItalic.woff2 b/template/app/public/fonts/Satoshi-LightItalic.woff2 deleted file mode 100644 index 6bd15ad54..000000000 Binary files a/template/app/public/fonts/Satoshi-LightItalic.woff2 and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Medium.eot b/template/app/public/fonts/Satoshi-Medium.eot deleted file mode 100644 index 83caceca5..000000000 Binary files a/template/app/public/fonts/Satoshi-Medium.eot and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Medium.ttf b/template/app/public/fonts/Satoshi-Medium.ttf deleted file mode 100644 index ab149b719..000000000 Binary files a/template/app/public/fonts/Satoshi-Medium.ttf and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Medium.woff b/template/app/public/fonts/Satoshi-Medium.woff deleted file mode 100644 index cef3226e5..000000000 Binary files a/template/app/public/fonts/Satoshi-Medium.woff and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-MediumItalic.eot b/template/app/public/fonts/Satoshi-MediumItalic.eot deleted file mode 100644 index 25d229a5b..000000000 Binary files a/template/app/public/fonts/Satoshi-MediumItalic.eot and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-MediumItalic.ttf b/template/app/public/fonts/Satoshi-MediumItalic.ttf deleted file mode 100644 index 387f278e2..000000000 Binary files a/template/app/public/fonts/Satoshi-MediumItalic.ttf and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-MediumItalic.woff b/template/app/public/fonts/Satoshi-MediumItalic.woff deleted file mode 100644 index 46d8995af..000000000 Binary files a/template/app/public/fonts/Satoshi-MediumItalic.woff and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-MediumItalic.woff2 b/template/app/public/fonts/Satoshi-MediumItalic.woff2 deleted file mode 100644 index 212adc92a..000000000 Binary files a/template/app/public/fonts/Satoshi-MediumItalic.woff2 and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Regular.eot b/template/app/public/fonts/Satoshi-Regular.eot deleted file mode 100644 index 452666f4a..000000000 Binary files a/template/app/public/fonts/Satoshi-Regular.eot and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Regular.ttf b/template/app/public/fonts/Satoshi-Regular.ttf deleted file mode 100644 index fe85cd6c9..000000000 Binary files a/template/app/public/fonts/Satoshi-Regular.ttf and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Regular.woff b/template/app/public/fonts/Satoshi-Regular.woff deleted file mode 100644 index 03ac19525..000000000 Binary files a/template/app/public/fonts/Satoshi-Regular.woff and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Variable.eot b/template/app/public/fonts/Satoshi-Variable.eot deleted file mode 100644 index f42624e18..000000000 Binary files a/template/app/public/fonts/Satoshi-Variable.eot and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Variable.ttf b/template/app/public/fonts/Satoshi-Variable.ttf deleted file mode 100644 index 976e85cb5..000000000 Binary files a/template/app/public/fonts/Satoshi-Variable.ttf and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Variable.woff b/template/app/public/fonts/Satoshi-Variable.woff deleted file mode 100644 index f8dcd1d60..000000000 Binary files a/template/app/public/fonts/Satoshi-Variable.woff and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-Variable.woff2 b/template/app/public/fonts/Satoshi-Variable.woff2 deleted file mode 100644 index b00e833ed..000000000 Binary files a/template/app/public/fonts/Satoshi-Variable.woff2 and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-VariableItalic.eot b/template/app/public/fonts/Satoshi-VariableItalic.eot deleted file mode 100644 index 5f4554af9..000000000 Binary files a/template/app/public/fonts/Satoshi-VariableItalic.eot and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-VariableItalic.ttf b/template/app/public/fonts/Satoshi-VariableItalic.ttf deleted file mode 100644 index 4c2677c6f..000000000 Binary files a/template/app/public/fonts/Satoshi-VariableItalic.ttf and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-VariableItalic.woff b/template/app/public/fonts/Satoshi-VariableItalic.woff deleted file mode 100644 index 3fe029e25..000000000 Binary files a/template/app/public/fonts/Satoshi-VariableItalic.woff and /dev/null differ diff --git a/template/app/public/fonts/Satoshi-VariableItalic.woff2 b/template/app/public/fonts/Satoshi-VariableItalic.woff2 deleted file mode 100644 index e7ab3a09a..000000000 Binary files a/template/app/public/fonts/Satoshi-VariableItalic.woff2 and /dev/null differ diff --git a/template/app/schema.prisma b/template/app/schema.prisma index eda467170..3c4ff6e98 100644 --- a/template/app/schema.prisma +++ b/template/app/schema.prisma @@ -60,8 +60,7 @@ model File { name String type String - key String - uploadUrl String + s3Key String } model DailyStats { diff --git a/template/app/src/admin/dashboards/analytics/AnalyticsDashboardPage.tsx b/template/app/src/admin/dashboards/analytics/AnalyticsDashboardPage.tsx index 0c33529b6..e6f42d925 100644 --- a/template/app/src/admin/dashboards/analytics/AnalyticsDashboardPage.tsx +++ b/template/app/src/admin/dashboards/analytics/AnalyticsDashboardPage.tsx @@ -1,6 +1,6 @@ import { type AuthUser } from "wasp/auth"; import { getDailyStats, useQuery } from "wasp/client/operations"; -import { cn } from "../../../lib/utils"; +import { cn } from "../../../client/utils"; import DefaultLayout from "../../layout/DefaultLayout"; import RevenueAndProfitChart from "./RevenueAndProfitChart"; import SourcesTable from "./SourcesTable"; diff --git a/template/app/src/admin/dashboards/analytics/RevenueAndProfitChart.tsx b/template/app/src/admin/dashboards/analytics/RevenueAndProfitChart.tsx index dbcd2ded7..9f12690f0 100644 --- a/template/app/src/admin/dashboards/analytics/RevenueAndProfitChart.tsx +++ b/template/app/src/admin/dashboards/analytics/RevenueAndProfitChart.tsx @@ -11,7 +11,7 @@ const options: ApexOptions = { }, colors: ["#3C50E0", "#80CAEE"], chart: { - fontFamily: "Satoshi, sans-serif", + fontFamily: "system-ui, sans-serif", height: 335, type: "area", dropShadow: { diff --git a/template/app/src/admin/dashboards/analytics/TotalPageViewsCard.tsx b/template/app/src/admin/dashboards/analytics/TotalPageViewsCard.tsx index eb0c2cb15..36d48c08f 100644 --- a/template/app/src/admin/dashboards/analytics/TotalPageViewsCard.tsx +++ b/template/app/src/admin/dashboards/analytics/TotalPageViewsCard.tsx @@ -1,6 +1,10 @@ import { ArrowDown, ArrowUp, Eye } from "lucide-react"; -import { Card, CardContent, CardHeader } from "../../../components/ui/card"; -import { cn } from "../../../lib/utils"; +import { + Card, + CardContent, + CardHeader, +} from "../../../client/components/ui/card"; +import { cn } from "../../../client/utils"; type PageViewsStats = { totalPageViews: number | undefined; diff --git a/template/app/src/admin/dashboards/analytics/TotalPayingUsersCard.tsx b/template/app/src/admin/dashboards/analytics/TotalPayingUsersCard.tsx index 597cd9f20..e83a66916 100644 --- a/template/app/src/admin/dashboards/analytics/TotalPayingUsersCard.tsx +++ b/template/app/src/admin/dashboards/analytics/TotalPayingUsersCard.tsx @@ -1,8 +1,12 @@ import { ArrowDown, ArrowUp, ShoppingBag } from "lucide-react"; import { useMemo } from "react"; import { type DailyStatsProps } from "../../../analytics/stats"; -import { Card, CardContent, CardHeader } from "../../../components/ui/card"; -import { cn } from "../../../lib/utils"; +import { + Card, + CardContent, + CardHeader, +} from "../../../client/components/ui/card"; +import { cn } from "../../../client/utils"; const TotalPayingUsersCard = ({ dailyStats, isLoading }: DailyStatsProps) => { const isDeltaPositive = useMemo(() => { diff --git a/template/app/src/admin/dashboards/analytics/TotalRevenueCard.tsx b/template/app/src/admin/dashboards/analytics/TotalRevenueCard.tsx index 95ba3d456..f30204b26 100644 --- a/template/app/src/admin/dashboards/analytics/TotalRevenueCard.tsx +++ b/template/app/src/admin/dashboards/analytics/TotalRevenueCard.tsx @@ -1,8 +1,12 @@ import { ArrowDown, ArrowUp, ShoppingCart } from "lucide-react"; import { useMemo } from "react"; import { type DailyStatsProps } from "../../../analytics/stats"; -import { Card, CardContent, CardHeader } from "../../../components/ui/card"; -import { cn } from "../../../lib/utils"; +import { + Card, + CardContent, + CardHeader, +} from "../../../client/components/ui/card"; +import { cn } from "../../../client/utils"; const TotalRevenueCard = ({ dailyStats, diff --git a/template/app/src/admin/dashboards/analytics/TotalSignupsCard.tsx b/template/app/src/admin/dashboards/analytics/TotalSignupsCard.tsx index beef4402b..19354f87a 100644 --- a/template/app/src/admin/dashboards/analytics/TotalSignupsCard.tsx +++ b/template/app/src/admin/dashboards/analytics/TotalSignupsCard.tsx @@ -1,8 +1,12 @@ import { ArrowUp, UsersRound } from "lucide-react"; import { useMemo } from "react"; import { type DailyStatsProps } from "../../../analytics/stats"; -import { Card, CardContent, CardHeader } from "../../../components/ui/card"; -import { cn } from "../../../lib/utils"; +import { + Card, + CardContent, + CardHeader, +} from "../../../client/components/ui/card"; +import { cn } from "../../../client/utils"; const TotalSignupsCard = ({ dailyStats, isLoading }: DailyStatsProps) => { const isDeltaPositive = useMemo(() => { diff --git a/template/app/src/admin/dashboards/users/DropdownEditDelete.tsx b/template/app/src/admin/dashboards/users/DropdownEditDelete.tsx index c6dacaef6..0650d9650 100644 --- a/template/app/src/admin/dashboards/users/DropdownEditDelete.tsx +++ b/template/app/src/admin/dashboards/users/DropdownEditDelete.tsx @@ -4,7 +4,7 @@ import { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from "../../../components/ui/dropdown-menu"; +} from "../../../client/components/ui/dropdown-menu"; const DropdownEditDelete = () => { return ( diff --git a/template/app/src/admin/dashboards/users/UsersTable.tsx b/template/app/src/admin/dashboards/users/UsersTable.tsx index 109808adb..9b7d9a226 100644 --- a/template/app/src/admin/dashboards/users/UsersTable.tsx +++ b/template/app/src/admin/dashboards/users/UsersTable.tsx @@ -7,19 +7,19 @@ import { useQuery, } from "wasp/client/operations"; import { type User } from "wasp/entities"; -import useDebounce from "../../../client/hooks/useDebounce"; -import { Button } from "../../../components/ui/button"; -import { Checkbox } from "../../../components/ui/checkbox"; -import { Input } from "../../../components/ui/input"; -import { Label } from "../../../components/ui/label"; +import { Button } from "../../../client/components/ui/button"; +import { Checkbox } from "../../../client/components/ui/checkbox"; +import { Input } from "../../../client/components/ui/input"; +import { Label } from "../../../client/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "../../../components/ui/select"; -import { Switch } from "../../../components/ui/switch"; +} from "../../../client/components/ui/select"; +import { Switch } from "../../../client/components/ui/switch"; +import useDebounce from "../../../client/hooks/useDebounce"; import { SubscriptionStatus } from "../../../payment/plans"; import LoadingSpinner from "../../layout/LoadingSpinner"; import DropdownEditDelete from "./DropdownEditDelete"; diff --git a/template/app/src/admin/elements/settings/SettingsPage.tsx b/template/app/src/admin/elements/settings/SettingsPage.tsx index 1ec8bc384..b752a8962 100644 --- a/template/app/src/admin/elements/settings/SettingsPage.tsx +++ b/template/app/src/admin/elements/settings/SettingsPage.tsx @@ -1,30 +1,24 @@ import { FileText, Mail, Upload, User } from "lucide-react"; import { FormEvent } from "react"; -import toast from "react-hot-toast"; import { type AuthUser } from "wasp/auth"; -import { Button } from "../../../components/ui/button"; +import { Button } from "../../../client/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle, -} from "../../../components/ui/card"; -import { Input } from "../../../components/ui/input"; -import { Label } from "../../../components/ui/label"; -import { Textarea } from "../../../components/ui/textarea"; +} from "../../../client/components/ui/card"; +import { Input } from "../../../client/components/ui/input"; +import { Label } from "../../../client/components/ui/label"; +import { Textarea } from "../../../client/components/ui/textarea"; import Breadcrumb from "../../layout/Breadcrumb"; import DefaultLayout from "../../layout/DefaultLayout"; const SettingsPage = ({ user }: { user: AuthUser }) => { const handleSubmit = (event: FormEvent) => { - // TODO add toast provider / wrapper + // TODO implement event.preventDefault(); - const confirmed = confirm("Are you sure you want to save the changes?"); - if (confirmed) { - toast.success("Your changes have been saved successfully!"); - } else { - toast.error("Your changes have not been saved!"); - } + alert("Not yet implemented"); }; return ( diff --git a/template/app/src/admin/elements/ui-elements/ButtonsPage.tsx b/template/app/src/admin/elements/ui-elements/ButtonsPage.tsx index 3bf8b64a1..4da023c5e 100644 --- a/template/app/src/admin/elements/ui-elements/ButtonsPage.tsx +++ b/template/app/src/admin/elements/ui-elements/ButtonsPage.tsx @@ -1,6 +1,6 @@ import { Heart, Plus, Trash2 } from "lucide-react"; import { type AuthUser } from "wasp/auth"; -import { Button } from "../../../components/ui/button"; +import { Button } from "../../../client/components/ui/button"; import Breadcrumb from "../../layout/Breadcrumb"; import DefaultLayout from "../../layout/DefaultLayout"; diff --git a/template/app/src/admin/layout/Header.tsx b/template/app/src/admin/layout/Header.tsx index eeec0b7a7..c80c2a023 100644 --- a/template/app/src/admin/layout/Header.tsx +++ b/template/app/src/admin/layout/Header.tsx @@ -1,6 +1,6 @@ import { type AuthUser } from "wasp/auth"; import DarkModeSwitcher from "../../client/components/DarkModeSwitcher"; -import { cn } from "../../lib/utils"; +import { cn } from "../../client/utils"; import { UserDropdown } from "../../user/UserDropdown"; import MessageButton from "../dashboards/messages/MessageButton"; diff --git a/template/app/src/admin/layout/Sidebar.tsx b/template/app/src/admin/layout/Sidebar.tsx index 5d219e463..db8845423 100644 --- a/template/app/src/admin/layout/Sidebar.tsx +++ b/template/app/src/admin/layout/Sidebar.tsx @@ -11,7 +11,7 @@ import { import React, { useEffect, useRef, useState } from "react"; import { NavLink, useLocation } from "react-router-dom"; import Logo from "../../client/static/logo.webp"; -import { cn } from "../../lib/utils"; +import { cn } from "../../client/utils"; import SidebarLinkGroup from "./SidebarLinkGroup"; interface SidebarProps { diff --git a/template/app/src/analytics/stats.ts b/template/app/src/analytics/stats.ts index 87705c79d..0ee5cdeee 100644 --- a/template/app/src/analytics/stats.ts +++ b/template/app/src/analytics/stats.ts @@ -2,14 +2,17 @@ import { listOrders } from "@lemonsqueezy/lemonsqueezy.js"; import Stripe from "stripe"; import { type DailyStats } from "wasp/entities"; import { type DailyStatsJob } from "wasp/server/jobs"; -import { stripe } from "../payment/stripe/stripeClient"; +import { stripeClient } from "../payment/stripe/stripeClient"; import { getDailyPageViews, getSources, } from "./providers/plausibleAnalyticsUtils"; // import { getDailyPageViews, getSources } from './providers/googleAnalyticsUtils'; +import { OrderStatus } from "@polar-sh/sdk/models/components/orderstatus.js"; import { paymentProcessor } from "../payment/paymentProcessor"; import { SubscriptionStatus } from "../payment/plans"; +import { polarClient } from "../payment/polar/polarClient"; +import { assertUnreachable } from "../shared/utils"; export type DailyStatsProps = { dailyStats?: DailyStats; @@ -60,10 +63,11 @@ export const calculateDailyStats: DailyStatsJob = async ( case "lemonsqueezy": totalRevenue = await fetchTotalLemonSqueezyRevenue(); break; + case "polar": + totalRevenue = await fetchTotalPolarRevenue(); + break; default: - throw new Error( - `Unsupported payment processor: ${paymentProcessor.id}`, - ); + assertUnreachable(paymentProcessor.id); } const { totalViews, prevDayViewsChangePercent } = await getDailyPageViews(); @@ -156,7 +160,8 @@ async function fetchTotalStripeRevenue() { let hasMore = true; while (hasMore) { - const balanceTransactions = await stripe.balanceTransactions.list(params); + const balanceTransactions = + await stripeClient.balanceTransactions.list(params); for (const transaction of balanceTransactions.data) { if (transaction.type === "charge") { @@ -211,3 +216,24 @@ async function fetchTotalLemonSqueezyRevenue() { throw error; } } + +async function fetchTotalPolarRevenue(): Promise { + let totalRevenue = 0; + + const result = await polarClient.orders.list({ + limit: 100, + }); + + for await (const page of result) { + const orders = page.result.items || []; + + for (const order of orders) { + if (order.status === OrderStatus.Paid && order.totalAmount > 0) { + totalRevenue += order.totalAmount; + } + } + } + + // Revenue is in cents so we convert to dollars + return totalRevenue / 100; +} diff --git a/template/app/src/client/App.tsx b/template/app/src/client/App.tsx index 979950d30..7d1cd7cc8 100644 --- a/template/app/src/client/App.tsx +++ b/template/app/src/client/App.tsx @@ -1,6 +1,7 @@ import { useEffect, useMemo } from "react"; import { Outlet, useLocation } from "react-router-dom"; import { routes } from "wasp/client/router"; +import { Toaster } from "../client/components/ui/toaster"; import "./Main.css"; import NavBar from "./components/NavBar/NavBar"; import { @@ -62,6 +63,7 @@ export default function App() { )}
+ ); diff --git a/template/app/src/client/Main.css b/template/app/src/client/Main.css index 935e2dde5..2c06aad0f 100644 --- a/template/app/src/client/Main.css +++ b/template/app/src/client/Main.css @@ -58,19 +58,6 @@ } } -/* Here is an example of how to add a custom font. -* Fonts are stored in the public/fonts folder. -* They are defined first here, then need to be referenced in the tailwind.config.js file -* under `theme.extend.fontFamily`, and then can be used as a tailwind class, e.g. className='font-satoshi'. -*/ -@font-face { - font-family: "Satoshi"; - src: url("/fonts/Satoshi-Regular.woff2") format("woff2"); - font-weight: normal; - font-style: normal; - font-display: swap; -} - /* third-party libraries CSS */ .tableCheckbox:checked ~ div span { diff --git a/template/app/src/client/components/DarkModeSwitcher.tsx b/template/app/src/client/components/DarkModeSwitcher.tsx index 6113ea491..bed225b46 100644 --- a/template/app/src/client/components/DarkModeSwitcher.tsx +++ b/template/app/src/client/components/DarkModeSwitcher.tsx @@ -1,7 +1,7 @@ import { Moon, Sun } from "lucide-react"; -import { Label } from "../../components/ui/label"; -import { cn } from "../../lib/utils"; +import { Label } from "../../client/components/ui/label"; import useColorMode from "../hooks/useColorMode"; +import { cn } from "../utils"; const DarkModeSwitcher = () => { const [colorMode, setColorMode] = useColorMode(); diff --git a/template/app/src/client/components/NavBar/NavBar.tsx b/template/app/src/client/components/NavBar/NavBar.tsx index 8af0c5277..63b3de1ec 100644 --- a/template/app/src/client/components/NavBar/NavBar.tsx +++ b/template/app/src/client/components/NavBar/NavBar.tsx @@ -9,13 +9,13 @@ import { SheetHeader, SheetTitle, SheetTrigger, -} from "../../../components/ui/sheet"; -import { cn } from "../../../lib/utils"; +} from "../../../client/components/ui/sheet"; import { throttleWithTrailingInvocation } from "../../../shared/utils"; import { UserDropdown } from "../../../user/UserDropdown"; import { UserMenuItems } from "../../../user/UserMenuItems"; import { useIsLandingPage } from "../../hooks/useIsLandingPage"; import logo from "../../static/logo.webp"; +import { cn } from "../../utils"; import DarkModeSwitcher from "../DarkModeSwitcher"; import { Announcement } from "./Announcement"; diff --git a/template/app/src/client/components/NavBar/constants.ts b/template/app/src/client/components/NavBar/constants.ts index f0e1ca098..f12f1f3b8 100644 --- a/template/app/src/client/components/NavBar/constants.ts +++ b/template/app/src/client/components/NavBar/constants.ts @@ -16,6 +16,5 @@ export const marketingNavigationItems: NavigationItem[] = [ export const demoNavigationitems: NavigationItem[] = [ { name: "AI Scheduler", to: routes.DemoAppRoute.to }, { name: "File Upload", to: routes.FileUploadRoute.to }, - { name: "Pricing", to: routes.PricingPageRoute.to }, ...staticNavigationItems, ] as const; diff --git a/template/app/src/components/ui/accordion.tsx b/template/app/src/client/components/ui/accordion.tsx similarity index 98% rename from template/app/src/components/ui/accordion.tsx rename to template/app/src/client/components/ui/accordion.tsx index 6fa4f6471..cb74fea58 100644 --- a/template/app/src/components/ui/accordion.tsx +++ b/template/app/src/client/components/ui/accordion.tsx @@ -2,7 +2,7 @@ import * as AccordionPrimitive from "@radix-ui/react-accordion"; import { ChevronDown } from "lucide-react"; import * as React from "react"; -import { cn } from "../../lib/utils"; +import { cn } from "../../utils"; const Accordion = AccordionPrimitive.Root; diff --git a/template/app/src/components/ui/alert.tsx b/template/app/src/client/components/ui/alert.tsx similarity index 97% rename from template/app/src/components/ui/alert.tsx rename to template/app/src/client/components/ui/alert.tsx index e1a1d10a8..f743d5427 100644 --- a/template/app/src/components/ui/alert.tsx +++ b/template/app/src/client/components/ui/alert.tsx @@ -1,7 +1,7 @@ import { cva, type VariantProps } from "class-variance-authority"; import * as React from "react"; -import { cn } from "../../lib/utils"; +import { cn } from "../../utils"; const alertVariants = cva( "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", diff --git a/template/app/src/components/ui/avatar.tsx b/template/app/src/client/components/ui/avatar.tsx similarity index 97% rename from template/app/src/components/ui/avatar.tsx rename to template/app/src/client/components/ui/avatar.tsx index 7fa44f317..185bbd0d3 100644 --- a/template/app/src/components/ui/avatar.tsx +++ b/template/app/src/client/components/ui/avatar.tsx @@ -1,7 +1,7 @@ import * as AvatarPrimitive from "@radix-ui/react-avatar"; import * as React from "react"; -import { cn } from "../../lib/utils"; +import { cn } from "../../utils"; const Avatar = React.forwardRef< React.ElementRef, diff --git a/template/app/src/components/ui/button.tsx b/template/app/src/client/components/ui/button.tsx similarity index 85% rename from template/app/src/components/ui/button.tsx rename to template/app/src/client/components/ui/button.tsx index 4b70131b7..8ee5619fc 100644 --- a/template/app/src/components/ui/button.tsx +++ b/template/app/src/client/components/ui/button.tsx @@ -2,10 +2,10 @@ import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; import * as React from "react"; -import { cn } from "../../lib/utils"; +import { cn } from "../../utils"; const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", { variants: { variant: { diff --git a/template/app/src/components/ui/card.tsx b/template/app/src/client/components/ui/card.tsx similarity index 98% rename from template/app/src/components/ui/card.tsx rename to template/app/src/client/components/ui/card.tsx index 8da320074..637e47c75 100644 --- a/template/app/src/components/ui/card.tsx +++ b/template/app/src/client/components/ui/card.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { cva, VariantProps } from "class-variance-authority"; -import { cn } from "../../lib/utils"; +import { cn } from "../../utils"; const cardVariants = cva( "rounded-xl border shadow hover:shadow-lg transition-all duration-300 xur", diff --git a/template/app/src/components/ui/checkbox.tsx b/template/app/src/client/components/ui/checkbox.tsx similarity index 96% rename from template/app/src/components/ui/checkbox.tsx rename to template/app/src/client/components/ui/checkbox.tsx index 3658843d5..87df1e85a 100644 --- a/template/app/src/components/ui/checkbox.tsx +++ b/template/app/src/client/components/ui/checkbox.tsx @@ -2,7 +2,7 @@ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; import { Check } from "lucide-react"; import * as React from "react"; -import { cn } from "../../lib/utils"; +import { cn } from "../../utils"; const Checkbox = React.forwardRef< React.ElementRef, diff --git a/template/app/src/client/components/ui/dialog.tsx b/template/app/src/client/components/ui/dialog.tsx new file mode 100644 index 000000000..1cec40a99 --- /dev/null +++ b/template/app/src/client/components/ui/dialog.tsx @@ -0,0 +1,120 @@ +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; +import * as React from "react"; + +import { cn } from "../../utils"; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +}; diff --git a/template/app/src/components/ui/dropdown-menu.tsx b/template/app/src/client/components/ui/dropdown-menu.tsx similarity index 99% rename from template/app/src/components/ui/dropdown-menu.tsx rename to template/app/src/client/components/ui/dropdown-menu.tsx index c487f7b12..1bba22ac7 100644 --- a/template/app/src/components/ui/dropdown-menu.tsx +++ b/template/app/src/client/components/ui/dropdown-menu.tsx @@ -2,7 +2,7 @@ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; import { Check, ChevronRight, Circle } from "lucide-react"; import * as React from "react"; -import { cn } from "../../lib/utils"; +import { cn } from "../../utils"; const DropdownMenu = DropdownMenuPrimitive.Root; diff --git a/template/app/src/components/ui/form.tsx b/template/app/src/client/components/ui/form.tsx similarity index 99% rename from template/app/src/components/ui/form.tsx rename to template/app/src/client/components/ui/form.tsx index ffde32461..e213782da 100644 --- a/template/app/src/components/ui/form.tsx +++ b/template/app/src/client/components/ui/form.tsx @@ -10,7 +10,7 @@ import { type FieldValues, } from "react-hook-form"; -import { cn } from "../../lib/utils"; +import { cn } from "../../utils"; import { Label } from "./label"; const Form = FormProvider; diff --git a/template/app/src/components/ui/input.tsx b/template/app/src/client/components/ui/input.tsx similarity index 95% rename from template/app/src/components/ui/input.tsx rename to template/app/src/client/components/ui/input.tsx index 72a3cfc18..caffd7f79 100644 --- a/template/app/src/components/ui/input.tsx +++ b/template/app/src/client/components/ui/input.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { cn } from "../../lib/utils"; +import { cn } from "../../utils"; const Input = React.forwardRef>( ({ className, type, ...props }, ref) => { diff --git a/template/app/src/components/ui/label.tsx b/template/app/src/client/components/ui/label.tsx similarity index 94% rename from template/app/src/components/ui/label.tsx rename to template/app/src/client/components/ui/label.tsx index aac44a811..d67fb31ba 100644 --- a/template/app/src/components/ui/label.tsx +++ b/template/app/src/client/components/ui/label.tsx @@ -4,7 +4,7 @@ import * as LabelPrimitive from "@radix-ui/react-label"; import { cva, type VariantProps } from "class-variance-authority"; import * as React from "react"; -import { cn } from "../../lib/utils"; +import { cn } from "../../utils"; const labelVariants = cva( "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", diff --git a/template/app/src/components/ui/progress.tsx b/template/app/src/client/components/ui/progress.tsx similarity index 95% rename from template/app/src/components/ui/progress.tsx rename to template/app/src/client/components/ui/progress.tsx index 0be41f241..1c42274f2 100644 --- a/template/app/src/components/ui/progress.tsx +++ b/template/app/src/client/components/ui/progress.tsx @@ -1,7 +1,7 @@ import * as ProgressPrimitive from "@radix-ui/react-progress"; import * as React from "react"; -import { cn } from "../../lib/utils"; +import { cn } from "../../utils"; const Progress = React.forwardRef< React.ElementRef, diff --git a/template/app/src/components/ui/select.tsx b/template/app/src/client/components/ui/select.tsx similarity index 99% rename from template/app/src/components/ui/select.tsx rename to template/app/src/client/components/ui/select.tsx index 18ae0233b..7ebef428e 100644 --- a/template/app/src/components/ui/select.tsx +++ b/template/app/src/client/components/ui/select.tsx @@ -2,7 +2,7 @@ import * as SelectPrimitive from "@radix-ui/react-select"; import { Check, ChevronDown, ChevronUp } from "lucide-react"; import * as React from "react"; -import { cn } from "../../lib/utils"; +import { cn } from "../../utils"; const Select = SelectPrimitive.Root; diff --git a/template/app/src/components/ui/separator.tsx b/template/app/src/client/components/ui/separator.tsx similarity index 95% rename from template/app/src/components/ui/separator.tsx rename to template/app/src/client/components/ui/separator.tsx index 94b456e1d..33da2a6b1 100644 --- a/template/app/src/components/ui/separator.tsx +++ b/template/app/src/client/components/ui/separator.tsx @@ -1,7 +1,7 @@ import * as SeparatorPrimitive from "@radix-ui/react-separator"; import * as React from "react"; -import { cn } from "../../lib/utils"; +import { cn } from "../../utils"; const Separator = React.forwardRef< React.ElementRef, diff --git a/template/app/src/components/ui/sheet.tsx b/template/app/src/client/components/ui/sheet.tsx similarity index 99% rename from template/app/src/components/ui/sheet.tsx rename to template/app/src/client/components/ui/sheet.tsx index f7f26b769..57e113229 100644 --- a/template/app/src/components/ui/sheet.tsx +++ b/template/app/src/client/components/ui/sheet.tsx @@ -3,7 +3,7 @@ import { cva, type VariantProps } from "class-variance-authority"; import { X } from "lucide-react"; import * as React from "react"; -import { cn } from "../../lib/utils"; +import { cn } from "../../utils"; const Sheet = SheetPrimitive.Root; diff --git a/template/app/src/components/ui/switch.tsx b/template/app/src/client/components/ui/switch.tsx similarity index 96% rename from template/app/src/components/ui/switch.tsx rename to template/app/src/client/components/ui/switch.tsx index ae2f85ff5..204451999 100644 --- a/template/app/src/components/ui/switch.tsx +++ b/template/app/src/client/components/ui/switch.tsx @@ -1,7 +1,7 @@ import * as SwitchPrimitives from "@radix-ui/react-switch"; import * as React from "react"; -import { cn } from "../../lib/utils"; +import { cn } from "../../utils"; const Switch = React.forwardRef< React.ElementRef, diff --git a/template/app/src/components/ui/textarea.tsx b/template/app/src/client/components/ui/textarea.tsx similarity index 94% rename from template/app/src/components/ui/textarea.tsx rename to template/app/src/client/components/ui/textarea.tsx index 156a86ec7..4087f4d52 100644 --- a/template/app/src/components/ui/textarea.tsx +++ b/template/app/src/client/components/ui/textarea.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { cn } from "../../lib/utils"; +import { cn } from "../../utils"; const Textarea = React.forwardRef< HTMLTextAreaElement, diff --git a/template/app/src/client/components/ui/toast.tsx b/template/app/src/client/components/ui/toast.tsx new file mode 100644 index 000000000..74f3e85ce --- /dev/null +++ b/template/app/src/client/components/ui/toast.tsx @@ -0,0 +1,149 @@ +import * as ToastPrimitives from "@radix-ui/react-toast"; +import { cva, type VariantProps } from "class-variance-authority"; +import { X } from "lucide-react"; +import * as React from "react"; + +import { cn } from "../../utils"; + +const ToastProvider = ToastPrimitives.Provider; + +const ToastViewport = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + position?: + | "top-left" + | "top-center" + | "top-right" + | "bottom-left" + | "bottom-center" + | "bottom-right"; + } +>(({ className, position, ...props }, ref) => { + const positionClasses = position + ? { + "top-left": "top-0 left-0", + "top-center": "top-0 left-1/2 -translate-x-1/2", + "top-right": "top-0 right-0", + "bottom-left": "bottom-0 left-0", + "bottom-center": "bottom-0 left-1/2 -translate-x-1/2", + "bottom-right": "bottom-0 right-0", + }[position] + : "top-0 sm:bottom-0 sm:right-0 sm:top-auto"; + + return ( + + ); +}); +ToastViewport.displayName = ToastPrimitives.Viewport.displayName; + +const toastVariants = cva( + "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", + { + variants: { + variant: { + default: "border bg-background text-foreground", + destructive: + "destructive group border-destructive bg-destructive text-destructive-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +const Toast = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, variant, ...props }, ref) => { + return ( + + ); +}); +Toast.displayName = ToastPrimitives.Root.displayName; + +const ToastAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +ToastAction.displayName = ToastPrimitives.Action.displayName; + +const ToastClose = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +ToastClose.displayName = ToastPrimitives.Close.displayName; + +const ToastTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +ToastTitle.displayName = ToastPrimitives.Title.displayName; + +const ToastDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +ToastDescription.displayName = ToastPrimitives.Description.displayName; + +type ToastProps = React.ComponentPropsWithoutRef; + +type ToastActionElement = React.ReactElement; + +export { + Toast, + ToastAction, + ToastClose, + ToastDescription, + ToastProvider, + ToastTitle, + ToastViewport, + type ToastActionElement, + type ToastProps, +}; diff --git a/template/app/src/client/components/ui/toaster.tsx b/template/app/src/client/components/ui/toaster.tsx new file mode 100644 index 000000000..ad831aa92 --- /dev/null +++ b/template/app/src/client/components/ui/toaster.tsx @@ -0,0 +1,43 @@ +import { useToast } from "../../hooks/use-toast"; +import { + Toast, + ToastClose, + ToastDescription, + ToastProvider, + ToastTitle, + ToastViewport, +} from "./toast"; + +export function Toaster({ + position, +}: { + position?: + | "top-left" + | "top-center" + | "top-right" + | "bottom-left" + | "bottom-center" + | "bottom-right"; +}) { + const { toasts } = useToast(); + + return ( + + {toasts.map(function ({ id, title, description, action, ...props }) { + return ( + +
+ {title && {title}} + {description && ( + {description} + )} +
+ {action} + +
+ ); + })} + +
+ ); +} diff --git a/template/app/src/client/hooks/use-toast.ts b/template/app/src/client/hooks/use-toast.ts new file mode 100644 index 000000000..02e26fa62 --- /dev/null +++ b/template/app/src/client/hooks/use-toast.ts @@ -0,0 +1,188 @@ +import * as React from "react"; + +import type { ToastActionElement, ToastProps } from "../components/ui/toast"; + +const TOAST_LIMIT = 1; +const TOAST_REMOVE_DELAY = 1000000; + +type ToasterToast = ToastProps & { + id: string; + title?: React.ReactNode; + description?: React.ReactNode; + action?: ToastActionElement; +}; + +const actionTypes = { + ADD_TOAST: "ADD_TOAST", + UPDATE_TOAST: "UPDATE_TOAST", + DISMISS_TOAST: "DISMISS_TOAST", + REMOVE_TOAST: "REMOVE_TOAST", +} as const; + +let count = 0; + +function genId() { + count = (count + 1) % Number.MAX_SAFE_INTEGER; + return count.toString(); +} + +type ActionType = typeof actionTypes; + +type Action = + | { + type: ActionType["ADD_TOAST"]; + toast: ToasterToast; + } + | { + type: ActionType["UPDATE_TOAST"]; + toast: Partial; + } + | { + type: ActionType["DISMISS_TOAST"]; + toastId?: ToasterToast["id"]; + } + | { + type: ActionType["REMOVE_TOAST"]; + toastId?: ToasterToast["id"]; + }; + +interface State { + toasts: ToasterToast[]; +} + +const toastTimeouts = new Map>(); + +const addToRemoveQueue = (toastId: string) => { + if (toastTimeouts.has(toastId)) { + return; + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId); + dispatch({ + type: "REMOVE_TOAST", + toastId: toastId, + }); + }, TOAST_REMOVE_DELAY); + + toastTimeouts.set(toastId, timeout); +}; + +export const reducer = (state: State, action: Action): State => { + switch (action.type) { + case "ADD_TOAST": + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + }; + + case "UPDATE_TOAST": + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id ? { ...t, ...action.toast } : t, + ), + }; + + case "DISMISS_TOAST": { + const { toastId } = action; + + // ! Side effects ! - This could be extracted into a dismissToast() action, + // but I'll keep it here for simplicity + if (toastId) { + addToRemoveQueue(toastId); + } else { + state.toasts.forEach((toast) => { + addToRemoveQueue(toast.id); + }); + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t, + ), + }; + } + case "REMOVE_TOAST": + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + }; + } + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + }; + } +}; + +const listeners: Array<(state: State) => void> = []; + +let memoryState: State = { toasts: [] }; + +function dispatch(action: Action) { + memoryState = reducer(memoryState, action); + listeners.forEach((listener) => { + listener(memoryState); + }); +} + +type Toast = Omit; + +function toast({ ...props }: Toast) { + const id = genId(); + + const update = (props: ToasterToast) => + dispatch({ + type: "UPDATE_TOAST", + toast: { ...props, id }, + }); + const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); + + dispatch({ + type: "ADD_TOAST", + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) dismiss(); + }, + }, + }); + + return { + id: id, + dismiss, + update, + }; +} + +function useToast() { + const [state, setState] = React.useState(memoryState); + + React.useEffect(() => { + listeners.push(setState); + return () => { + const index = listeners.indexOf(setState); + if (index > -1) { + listeners.splice(index, 1); + } + }; + }, [state]); + + return { + ...state, + toast, + dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), + }; +} + +export { toast, useToast }; diff --git a/template/app/src/client/hooks/useColorMode.tsx b/template/app/src/client/hooks/useColorMode.ts similarity index 100% rename from template/app/src/client/hooks/useColorMode.tsx rename to template/app/src/client/hooks/useColorMode.ts diff --git a/template/app/src/client/hooks/useDebounce.tsx b/template/app/src/client/hooks/useDebounce.ts similarity index 100% rename from template/app/src/client/hooks/useDebounce.tsx rename to template/app/src/client/hooks/useDebounce.ts diff --git a/template/app/src/client/hooks/useIsLandingPage.tsx b/template/app/src/client/hooks/useIsLandingPage.ts similarity index 100% rename from template/app/src/client/hooks/useIsLandingPage.tsx rename to template/app/src/client/hooks/useIsLandingPage.ts diff --git a/template/app/src/client/hooks/useLocalStorage.tsx b/template/app/src/client/hooks/useLocalStorage.ts similarity index 100% rename from template/app/src/client/hooks/useLocalStorage.tsx rename to template/app/src/client/hooks/useLocalStorage.ts diff --git a/template/app/src/client/icons/icon-copy-alt.svg b/template/app/src/client/icons/icon-copy-alt.svg index 1f0c77026..83203a478 100644 --- a/template/app/src/client/icons/icon-copy-alt.svg +++ b/template/app/src/client/icons/icon-copy-alt.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/template/app/src/client/static/open-saas-banner-dark.png b/template/app/src/client/static/open-saas-banner-dark.png deleted file mode 100644 index 670c7af28..000000000 Binary files a/template/app/src/client/static/open-saas-banner-dark.png and /dev/null differ diff --git a/template/app/src/client/static/open-saas-banner-dark.svg b/template/app/src/client/static/open-saas-banner-dark.svg new file mode 100644 index 000000000..4ae8a5897 --- /dev/null +++ b/template/app/src/client/static/open-saas-banner-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/template/app/src/client/static/open-saas-banner-light.png b/template/app/src/client/static/open-saas-banner-light.png deleted file mode 100644 index c577fdd2f..000000000 Binary files a/template/app/src/client/static/open-saas-banner-light.png and /dev/null differ diff --git a/template/app/src/client/static/open-saas-banner-light.svg b/template/app/src/client/static/open-saas-banner-light.svg new file mode 100644 index 000000000..3bce1092b --- /dev/null +++ b/template/app/src/client/static/open-saas-banner-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/template/app/src/lib/utils.ts b/template/app/src/client/utils.ts similarity index 100% rename from template/app/src/lib/utils.ts rename to template/app/src/client/utils.ts diff --git a/template/app/src/demo-ai-app/DemoAppPage.tsx b/template/app/src/demo-ai-app/DemoAppPage.tsx index 4810c9330..18af5e59c 100644 --- a/template/app/src/demo-ai-app/DemoAppPage.tsx +++ b/template/app/src/demo-ai-app/DemoAppPage.tsx @@ -8,20 +8,23 @@ import { updateTask, useQuery, } from "wasp/client/operations"; +import { Link, routes } from "wasp/client/router"; -import { Loader2, Trash2 } from "lucide-react"; +import { ArrowRight, Loader2, Trash2 } from "lucide-react"; import { useMemo, useState } from "react"; -import { Button } from "../components/ui/button"; +import { Button } from "../client/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle, -} from "../components/ui/card"; -import { Checkbox } from "../components/ui/checkbox"; -import { Input } from "../components/ui/input"; -import { Label } from "../components/ui/label"; -import { cn } from "../lib/utils"; +} from "../client/components/ui/card"; +import { Checkbox } from "../client/components/ui/checkbox"; +import { Input } from "../client/components/ui/input"; +import { Label } from "../client/components/ui/label"; +import { ToastAction } from "../client/components/ui/toast"; +import { toast } from "../client/hooks/use-toast"; +import { cn } from "../client/utils"; import type { GeneratedSchedule, Task as ScheduleTask, @@ -146,10 +149,33 @@ function NewTaskForm({ hours: todaysHours, }); if (response) { - setResponse(response as unknown as GeneratedSchedule); + setResponse(response); } } catch (err: any) { - window.alert("Error: " + (err.message || "Something went wrong")); + if (err.statusCode === 402) { + toast({ + title: "⚠️ You are out of credits!", + style: { + minWidth: "400px", + }, + action: ( + + + Go to pricing page + + + ), + }); + } else { + toast({ + title: "Error", + description: err.message || "Something went wrong", + variant: "destructive", + }); + } } finally { setIsPlanGenerating(false); } diff --git a/template/app/src/demo-ai-app/operations.ts b/template/app/src/demo-ai-app/operations.ts index 29e2e357b..1daf4c167 100644 --- a/template/app/src/demo-ai-app/operations.ts +++ b/template/app/src/demo-ai-app/operations.ts @@ -92,7 +92,10 @@ export const generateGptResponse: GenerateGptResponse< }); transactions.push(decrementCredit); } else { - throw new HttpError(402, "User has not paid or is out of credits"); + throw new HttpError( + 402, + "User has no subscription and is out of credits", + ); } } diff --git a/template/app/src/file-upload/FileUploadPage.tsx b/template/app/src/file-upload/FileUploadPage.tsx index 8d0341c23..0ced8c5da 100644 --- a/template/app/src/file-upload/FileUploadPage.tsx +++ b/template/app/src/file-upload/FileUploadPage.tsx @@ -1,29 +1,41 @@ import { FormEvent, useEffect, useState } from "react"; import { + addFileToDb, + createFileUploadUrl, + deleteFile, getAllFilesByUser, getDownloadFileSignedURL, useQuery, } from "wasp/client/operations"; import type { File } from "wasp/entities"; -import { Alert, AlertDescription } from "../components/ui/alert"; -import { Button } from "../components/ui/button"; -import { Card, CardContent, CardTitle } from "../components/ui/card"; -import { Input } from "../components/ui/input"; -import { Label } from "../components/ui/label"; -import { Progress } from "../components/ui/progress"; -import { cn } from "../lib/utils"; + +import { Download, Trash } from "lucide-react"; +import { Alert, AlertDescription } from "../client/components/ui/alert"; +import { Button } from "../client/components/ui/button"; +import { Card, CardContent, CardTitle } from "../client/components/ui/card"; import { - type FileUploadError, - type FileWithValidType, - uploadFileWithProgress, - validateFile, -} from "./fileUploading"; + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "../client/components/ui/dialog"; +import { Input } from "../client/components/ui/input"; +import { Label } from "../client/components/ui/label"; +import { Progress } from "../client/components/ui/progress"; +import { toast } from "../client/hooks/use-toast"; +import { cn } from "../client/utils"; +import { uploadFileWithProgress, validateFile } from "./fileUploading"; import { ALLOWED_FILE_TYPES } from "./validation"; export default function FileUploadPage() { - const [fileKeyForS3, setFileKeyForS3] = useState(""); + const [fileKeyForS3, setFileKeyForS3] = useState(""); const [uploadProgressPercent, setUploadProgressPercent] = useState(0); - const [uploadError, setUploadError] = useState(null); + const [fileToDelete, setFileToDelete] = useState | null>(null); const allUserFiles = useQuery(getAllFilesByUser, undefined, { // We disable automatic refetching because otherwise files would be refetched after `createFile` is called and the S3 URL is returned, @@ -33,7 +45,7 @@ export default function FileUploadPage() { const { isLoading: isDownloadUrlLoading, refetch: refetchDownloadUrl } = useQuery( getDownloadFileSignedURL, - { key: fileKeyForS3 }, + { s3Key: fileKeyForS3 }, { enabled: false }, ); @@ -48,7 +60,11 @@ export default function FileUploadPage() { switch (urlQuery.status) { case "error": console.error("Error fetching download URL", urlQuery.error); - alert("Error fetching download"); + toast({ + title: "Error fetching download link", + description: "Please try again later.", + variant: "destructive", + }); return; case "success": window.open(urlQuery.data, "_blank"); @@ -71,152 +87,233 @@ export default function FileUploadPage() { } const formData = new FormData(formElement); - const file = formData.get("file-upload"); + const formDataFileUpload = formData.get("file-upload"); - if (!file || !(file instanceof File)) { - setUploadError({ - message: "Please select a file to upload.", - code: "NO_FILE", + if ( + !formDataFileUpload || + !(formDataFileUpload instanceof File) || + formDataFileUpload.size === 0 + ) { + toast({ + title: "No file selected", + description: "Please select a file to upload.", + variant: "destructive", }); return; } - const fileValidationError = validateFile(file); - if (fileValidationError !== null) { - setUploadError(fileValidationError); - return; - } + const file = validateFile(formDataFileUpload); + + const { s3UploadUrl, s3UploadFields, s3Key } = await createFileUploadUrl({ + fileType: file.type, + fileName: file.name, + }); await uploadFileWithProgress({ - file: file as FileWithValidType, + file, + s3UploadUrl, + s3UploadFields, setUploadProgressPercent, }); + + await addFileToDb({ + s3Key, + fileType: file.type, + fileName: file.name, + }); + formElement.reset(); allUserFiles.refetch(); + toast({ + title: "File uploaded", + description: "Your file has been successfully uploaded.", + }); } catch (error) { console.error("Error uploading file:", error); - setUploadError({ - message: - error instanceof Error - ? error.message - : "An unexpected error occurred while uploading the file.", - code: "UPLOAD_FAILED", + const errorMessage = + error instanceof Error ? error.message : "Error uploading file."; + toast({ + title: "Error uploading file", + description: errorMessage, + variant: "destructive", }); } finally { setUploadProgressPercent(0); } }; + const handleDelete = async ({ id, name }: Pick) => { + try { + await deleteFile({ id }); + toast({ + title: "File deleted", + description: ( + + File {name} deleted. + + ), + }); + allUserFiles.refetch(); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : "Error deleting file."; + toast({ + title: "Error", + description: errorMessage, + variant: "destructive", + }); + } finally { + setFileToDelete(null); + } + }; + return ( -
-
-
-

- AWS File Upload -

-
-

- This is an example file upload page using AWS S3. Maybe your app needs - this. Maybe it doesn't. But a lot of people asked for this feature, so - here you go 🤝 -

- - -
-
- - setUploadError(null)} - className="cursor-pointer" - /> -
-
- - {uploadProgressPercent > 0 && ( - + <> +
+
+
+

+ AWS File Upload +

+
+

+ This is an example file upload page using AWS S3. Maybe your app + needs this. Maybe it doesn't. But a lot of people asked for this + feature, so here you go 🤝 +

+ + + +
+ + +
+
+ + {uploadProgressPercent > 0 && ( + + )} +
+ +
+
+ + Uploaded Files + + {allUserFiles.isLoading && ( +

Loading...

)} -
- {uploadError && ( - - {uploadError.message} - - )} - -
-
- - Uploaded Files - - {allUserFiles.isLoading && ( -

Loading...

- )} - {allUserFiles.error && ( - - - Error: {allUserFiles.error.message} - - - )} - {!!allUserFiles.data && - allUserFiles.data.length > 0 && - !allUserFiles.isLoading ? ( -
- {allUserFiles.data.map((file: File) => ( - -
-

- {file.name} -

- -
-
- ))} -
- ) : ( -

- No files uploaded yet :( -

- )} -
-
-
+

+ {file.name} +

+
+ + +
+
+ + ))} +
+ ) : ( +

+ No files uploaded yet :( +

+ )} +
+
+
+
-
+ {fileToDelete && ( + !isOpen && setFileToDelete(null)} + > + + + Delete file + + Are you sure you want to delete{" "} + {fileToDelete.name}? This action cannot be + undone. + + + + + + + + + )} + ); } diff --git a/template/app/src/file-upload/fileUploading.ts b/template/app/src/file-upload/fileUploading.ts index 32e5a66ab..317bd914b 100644 --- a/template/app/src/file-upload/fileUploading.ts +++ b/template/app/src/file-upload/fileUploading.ts @@ -1,23 +1,20 @@ import axios from "axios"; -import { createFile } from "wasp/client/operations"; import { ALLOWED_FILE_TYPES, MAX_FILE_SIZE_BYTES } from "./validation"; -export type FileWithValidType = Omit & { type: AllowedFileType }; -type AllowedFileType = (typeof ALLOWED_FILE_TYPES)[number]; -interface FileUploadProgress { - file: FileWithValidType; - setUploadProgressPercent: (percentage: number) => void; -} +type AllowedFileTypes = (typeof ALLOWED_FILE_TYPES)[number]; +export type FileWithValidType = File & { type: AllowedFileTypes }; export async function uploadFileWithProgress({ file, + s3UploadUrl, + s3UploadFields, setUploadProgressPercent, -}: FileUploadProgress) { - const { s3UploadUrl, s3UploadFields } = await createFile({ - fileType: file.type, - fileName: file.name, - }); - +}: { + file: FileWithValidType; + s3UploadUrl: string; + s3UploadFields: Record; + setUploadProgressPercent: (percentage: number) => void; +}) { const formData = getFileUploadFormData(file, s3UploadFields); return axios.post(s3UploadUrl, formData, { @@ -44,29 +41,20 @@ function getFileUploadFormData( return formData; } -export interface FileUploadError { - message: string; - code: "NO_FILE" | "INVALID_FILE_TYPE" | "FILE_TOO_LARGE" | "UPLOAD_FAILED"; -} - -export function validateFile(file: File) { +export function validateFile(file: File): FileWithValidType { if (file.size > MAX_FILE_SIZE_BYTES) { - return { - message: `File size exceeds ${MAX_FILE_SIZE_BYTES / 1024 / 1024}MB limit.`, - code: "FILE_TOO_LARGE" as const, - }; + throw new Error( + `File size exceeds ${MAX_FILE_SIZE_BYTES / 1024 / 1024}MB limit.`, + ); } - if (!isAllowedFileType(file.type)) { - return { - message: `File type '${file.type}' is not supported.`, - code: "INVALID_FILE_TYPE" as const, - }; + if (!isFileWithAllowedFileType(file)) { + throw new Error(`File type '${file.type}' is not supported.`); } - return null; + return file; } -function isAllowedFileType(fileType: string): fileType is AllowedFileType { - return (ALLOWED_FILE_TYPES as readonly string[]).includes(fileType); +function isFileWithAllowedFileType(file: File): file is FileWithValidType { + return ALLOWED_FILE_TYPES.includes(file.type as AllowedFileTypes); } diff --git a/template/app/src/file-upload/operations.ts b/template/app/src/file-upload/operations.ts index d5e923197..cafb3706f 100644 --- a/template/app/src/file-upload/operations.ts +++ b/template/app/src/file-upload/operations.ts @@ -1,14 +1,18 @@ import { type File } from "wasp/entities"; import { HttpError } from "wasp/server"; import { - type CreateFile, + type AddFileToDb, + type CreateFileUploadUrl, + type DeleteFile, type GetAllFilesByUser, type GetDownloadFileSignedURL, } from "wasp/server/operations"; -import * as z from "zod"; +import * as z from "zod"; import { ensureArgsSchemaOrThrowHttpError } from "../server/validation"; import { + checkFileExistsInS3, + deleteFileFromS3, getDownloadFileSignedURLFromS3, getUploadFileSignedURLFromS3, } from "./s3Utils"; @@ -21,11 +25,12 @@ const createFileInputSchema = z.object({ type CreateFileInput = z.infer; -export const createFile: CreateFile< +export const createFileUploadUrl: CreateFileUploadUrl< CreateFileInput, { s3UploadUrl: string; s3UploadFields: Record; + s3Key: string; } > = async (rawArgs, context) => { if (!context.user) { @@ -37,27 +42,42 @@ export const createFile: CreateFile< rawArgs, ); - const { s3UploadUrl, s3UploadFields, key } = - await getUploadFileSignedURLFromS3({ - fileType, - fileName, - userId: context.user.id, - }); + return await getUploadFileSignedURLFromS3({ + fileType, + fileName, + userId: context.user.id, + }); +}; + +const addFileToDbInputSchema = z.object({ + s3Key: z.string(), + fileType: z.enum(ALLOWED_FILE_TYPES), + fileName: z.string(), +}); + +type AddFileToDbInput = z.infer; - await context.entities.File.create({ +export const addFileToDb: AddFileToDb = async ( + args, + context, +) => { + if (!context.user) { + throw new HttpError(401); + } + + const fileExists = await checkFileExistsInS3({ s3Key: args.s3Key }); + if (!fileExists) { + throw new HttpError(404, "File not found in S3."); + } + + return context.entities.File.create({ data: { - name: fileName, - key, - uploadUrl: s3UploadUrl, - type: fileType, + name: args.fileName, + s3Key: args.s3Key, + type: args.fileType, user: { connect: { id: context.user.id } }, }, }); - - return { - s3UploadUrl, - s3UploadFields, - }; }; export const getAllFilesByUser: GetAllFilesByUser = async ( @@ -80,7 +100,7 @@ export const getAllFilesByUser: GetAllFilesByUser = async ( }; const getDownloadFileSignedURLInputSchema = z.object({ - key: z.string().nonempty(), + s3Key: z.string().nonempty(), }); type GetDownloadFileSignedURLInput = z.infer< @@ -91,9 +111,44 @@ export const getDownloadFileSignedURL: GetDownloadFileSignedURL< GetDownloadFileSignedURLInput, string > = async (rawArgs, _context) => { - const { key } = ensureArgsSchemaOrThrowHttpError( + const { s3Key } = ensureArgsSchemaOrThrowHttpError( getDownloadFileSignedURLInputSchema, rawArgs, ); - return await getDownloadFileSignedURLFromS3({ key }); + return await getDownloadFileSignedURLFromS3({ s3Key }); +}; + +const deleteFileInputSchema = z.object({ + id: z.string(), +}); + +type DeleteFileInput = z.infer; + +export const deleteFile: DeleteFile = async ( + args, + context, +) => { + if (!context.user) { + throw new HttpError(401); + } + + const deletedFile = await context.entities.File.delete({ + where: { + id: args.id, + user: { + id: context.user.id, + }, + }, + }); + + try { + await deleteFileFromS3({ s3Key: deletedFile.s3Key }); + } catch (error) { + console.error( + `S3 deletion failed. Orphaned file s3Key: ${deletedFile.s3Key}`, + error, + ); + } + + return deletedFile; }; diff --git a/template/app/src/file-upload/s3Utils.ts b/template/app/src/file-upload/s3Utils.ts index 126322093..3b3713355 100644 --- a/template/app/src/file-upload/s3Utils.ts +++ b/template/app/src/file-upload/s3Utils.ts @@ -1,11 +1,17 @@ -import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3"; +import { + DeleteObjectCommand, + GetObjectCommand, + HeadObjectCommand, + S3Client, + S3ServiceException, +} from "@aws-sdk/client-s3"; import { createPresignedPost } from "@aws-sdk/s3-presigned-post"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; import { randomUUID } from "crypto"; import * as path from "path"; import { MAX_FILE_SIZE_BYTES } from "./validation"; -const s3Client = new S3Client({ +export const s3Client = new S3Client({ region: process.env.AWS_S3_REGION, credentials: { accessKeyId: process.env.AWS_S3_IAM_ACCESS_KEY!, @@ -24,12 +30,12 @@ export const getUploadFileSignedURLFromS3 = async ({ fileType, userId, }: S3Upload) => { - const key = getS3Key(fileName, userId); + const s3Key = getS3Key(fileName, userId); const { url: s3UploadUrl, fields: s3UploadFields } = await createPresignedPost(s3Client, { Bucket: process.env.AWS_S3_FILES_BUCKET!, - Key: key, + Key: s3Key, Conditions: [["content-length-range", 0, MAX_FILE_SIZE_BYTES]], Fields: { "Content-Type": fileType, @@ -37,21 +43,45 @@ export const getUploadFileSignedURLFromS3 = async ({ Expires: 3600, }); - return { s3UploadUrl, key, s3UploadFields }; + return { s3UploadUrl, s3Key, s3UploadFields }; }; export const getDownloadFileSignedURLFromS3 = async ({ - key, + s3Key, }: { - key: string; + s3Key: string; }) => { const command = new GetObjectCommand({ Bucket: process.env.AWS_S3_FILES_BUCKET, - Key: key, + Key: s3Key, }); return await getSignedUrl(s3Client, command, { expiresIn: 3600 }); }; +export const deleteFileFromS3 = async ({ s3Key }: { s3Key: string }) => { + const command = new DeleteObjectCommand({ + Bucket: process.env.AWS_S3_FILES_BUCKET, + Key: s3Key, + }); + await s3Client.send(command); +}; + +export const checkFileExistsInS3 = async ({ s3Key }: { s3Key: string }) => { + const command = new HeadObjectCommand({ + Bucket: process.env.AWS_S3_FILES_BUCKET, + Key: s3Key, + }); + try { + await s3Client.send(command); + return true; + } catch (error) { + if (error instanceof S3ServiceException && error.name === "NotFound") { + return false; + } + throw error; + } +}; + function getS3Key(fileName: string, userId: string) { const ext = path.extname(fileName).slice(1); return `${userId}/${randomUUID()}.${ext}`; diff --git a/template/app/src/landing-page/components/ExamplesCarousel.tsx b/template/app/src/landing-page/components/ExamplesCarousel.tsx index cefba1d68..7cb0940f1 100644 --- a/template/app/src/landing-page/components/ExamplesCarousel.tsx +++ b/template/app/src/landing-page/components/ExamplesCarousel.tsx @@ -1,5 +1,5 @@ import { forwardRef, useEffect, useRef, useState } from "react"; -import { Card, CardContent } from "../../components/ui/card"; +import { Card, CardContent } from "../../client/components/ui/card"; const EXAMPLES_CAROUSEL_INTERVAL = 3000; const EXAMPLES_CAROUSEL_SCROLL_TIMEOUT = 200; diff --git a/template/app/src/landing-page/components/FAQ.tsx b/template/app/src/landing-page/components/FAQ.tsx index c230c059e..c57862672 100644 --- a/template/app/src/landing-page/components/FAQ.tsx +++ b/template/app/src/landing-page/components/FAQ.tsx @@ -3,7 +3,7 @@ import { AccordionContent, AccordionItem, AccordionTrigger, -} from "../../components/ui/accordion"; +} from "../../client/components/ui/accordion"; interface FAQ { id: number; diff --git a/template/app/src/landing-page/components/FeaturesGrid.tsx b/template/app/src/landing-page/components/FeaturesGrid.tsx index a780388d3..3bd284e01 100644 --- a/template/app/src/landing-page/components/FeaturesGrid.tsx +++ b/template/app/src/landing-page/components/FeaturesGrid.tsx @@ -4,8 +4,8 @@ import { CardContent, CardDescription, CardTitle, -} from "../../components/ui/card"; -import { cn } from "../../lib/utils"; +} from "../../client/components/ui/card"; +import { cn } from "../../client/utils"; import { Feature } from "./Features"; import SectionTitle from "./SectionTitle"; diff --git a/template/app/src/landing-page/components/Hero.tsx b/template/app/src/landing-page/components/Hero.tsx index 401e55ec5..c96000a62 100644 --- a/template/app/src/landing-page/components/Hero.tsx +++ b/template/app/src/landing-page/components/Hero.tsx @@ -1,7 +1,7 @@ import { Link as WaspRouterLink, routes } from "wasp/client/router"; -import openSaasBannerDark from "../../client/static/open-saas-banner-dark.png"; -import openSaasBannerLight from "../../client/static/open-saas-banner-light.png"; -import { Button } from "../../components/ui/button"; +import { Button } from "../../client/components/ui/button"; +import openSaasBannerDark from "../../client/static/open-saas-banner-dark.svg"; +import openSaasBannerLight from "../../client/static/open-saas-banner-light.svg"; export default function Hero() { return ( diff --git a/template/app/src/landing-page/components/HighlightedFeature.tsx b/template/app/src/landing-page/components/HighlightedFeature.tsx index dbb119aea..77cfb050e 100644 --- a/template/app/src/landing-page/components/HighlightedFeature.tsx +++ b/template/app/src/landing-page/components/HighlightedFeature.tsx @@ -1,4 +1,4 @@ -import { cn } from "../../lib/utils"; +import { cn } from "../../client/utils"; interface FeatureProps { name: string; diff --git a/template/app/src/landing-page/components/Testimonials.tsx b/template/app/src/landing-page/components/Testimonials.tsx index 10d1396a5..06c210b62 100644 --- a/template/app/src/landing-page/components/Testimonials.tsx +++ b/template/app/src/landing-page/components/Testimonials.tsx @@ -5,7 +5,7 @@ import { CardDescription, CardFooter, CardTitle, -} from "../../components/ui/card"; +} from "../../client/components/ui/card"; import SectionTitle from "./SectionTitle"; interface Testimonial { diff --git a/template/app/src/landing-page/contentSections.ts b/template/app/src/landing-page/contentSections.tsx similarity index 100% rename from template/app/src/landing-page/contentSections.ts rename to template/app/src/landing-page/contentSections.tsx diff --git a/template/app/src/payment/PricingPage.tsx b/template/app/src/payment/PricingPage.tsx index 4633a2665..38012b974 100644 --- a/template/app/src/payment/PricingPage.tsx +++ b/template/app/src/payment/PricingPage.tsx @@ -7,15 +7,15 @@ import { getCustomerPortalUrl, useQuery, } from "wasp/client/operations"; -import { Alert, AlertDescription } from "../components/ui/alert"; -import { Button } from "../components/ui/button"; +import { Alert, AlertDescription } from "../client/components/ui/alert"; +import { Button } from "../client/components/ui/button"; import { Card, CardContent, CardFooter, CardTitle, -} from "../components/ui/card"; -import { cn } from "../lib/utils"; +} from "../client/components/ui/card"; +import { cn } from "../client/utils"; import { PaymentPlanId, paymentPlans, @@ -125,9 +125,9 @@ const PricingPage = () => {

- Choose between Stripe and LemonSqueezy as your payment provider. Just - add your Product IDs! Try it out below with test credit card number{" "} -
+ Choose between Stripe, LemonSqueezy or Polar as your payment provider. + Just add your Product IDs! Try it out below with test credit card + number
4242 4242 4242 4242 4242 diff --git a/template/app/src/payment/paymentProcessor.ts b/template/app/src/payment/paymentProcessor.ts index 498eb952f..bd362417e 100644 --- a/template/app/src/payment/paymentProcessor.ts +++ b/template/app/src/payment/paymentProcessor.ts @@ -1,22 +1,24 @@ import { PrismaClient } from "@prisma/client"; +import { User } from "wasp/entities"; import type { MiddlewareConfigFn } from "wasp/server"; import type { PaymentsWebhook } from "wasp/server/api"; import type { PaymentPlan } from "./plans"; import { stripePaymentProcessor } from "./stripe/paymentProcessor"; export interface CreateCheckoutSessionArgs { - userId: string; - userEmail: string; + userId: User["id"]; + userEmail: NonNullable; paymentPlan: PaymentPlan; prismaUserDelegate: PrismaClient["user"]; } + export interface FetchCustomerPortalUrlArgs { - userId: string; + userId: User["id"]; prismaUserDelegate: PrismaClient["user"]; } export interface PaymentProcessor { - id: "stripe" | "lemonsqueezy"; + id: "stripe" | "lemonsqueezy" | "polar"; createCheckoutSession: ( args: CreateCheckoutSessionArgs, ) => Promise<{ session: { id: string; url: string } }>; @@ -31,5 +33,6 @@ export interface PaymentProcessor { * Choose which payment processor you'd like to use, then delete the * other payment processor code that you're not using from `/src/payment` */ -// export const paymentProcessor: PaymentProcessor = lemonSqueezyPaymentProcessor; export const paymentProcessor: PaymentProcessor = stripePaymentProcessor; +// export const paymentProcessor: PaymentProcessor = lemonSqueezyPaymentProcessor; +// export const paymentProcessor: PaymentProcessor = polarPaymentProcessor; diff --git a/template/app/src/payment/plans.ts b/template/app/src/payment/plans.ts index b0ac3bb62..8081fea13 100644 --- a/template/app/src/payment/plans.ts +++ b/template/app/src/payment/plans.ts @@ -14,8 +14,11 @@ export enum PaymentPlanId { } export interface PaymentPlan { - // Returns the id under which this payment plan is identified on your payment processor. - // E.g. this might be price id on Stripe, or variant id on LemonSqueezy. + /** + * Returns the id under which this payment plan is identified on your payment processor. + * + * E.g. price id on Stripe, or variant id on LemonSqueezy. + */ getPaymentProcessorPlanId: () => string; effect: PaymentPlanEffect; } @@ -24,7 +27,7 @@ export type PaymentPlanEffect = | { kind: "subscription" } | { kind: "credits"; amount: number }; -export const paymentPlans: Record = { +export const paymentPlans = { [PaymentPlanId.Hobby]: { getPaymentProcessorPlanId: () => requireNodeEnvVar("PAYMENTS_HOBBY_SUBSCRIPTION_PLAN_ID"), @@ -40,7 +43,7 @@ export const paymentPlans: Record = { requireNodeEnvVar("PAYMENTS_CREDITS_10_PLAN_ID"), effect: { kind: "credits", amount: 10 }, }, -}; +} as const satisfies Record; export function prettyPaymentPlanName(planId: PaymentPlanId): string { const planToName: Record = { @@ -64,3 +67,23 @@ export function getSubscriptionPaymentPlanIds(): PaymentPlanId[] { (planId) => paymentPlans[planId].effect.kind === "subscription", ); } + +/** + * Returns Open SaaS `PaymentPlanId` for some payment provider's plan ID. + * + * Different payment providers track plan ID in different ways. + * e.g. Stripe price ID, Polar product ID... + */ +export function getPaymentPlanIdByPaymentProcessorPlanId( + paymentProcessorPlanId: string, +): PaymentPlanId { + for (const [planId, plan] of Object.entries(paymentPlans)) { + if (plan.getPaymentProcessorPlanId() === paymentProcessorPlanId) { + return planId as PaymentPlanId; + } + } + + throw new Error( + `Unknown payment processor plan ID: ${paymentProcessorPlanId}`, + ); +} diff --git a/template/app/src/payment/polar/checkoutUtils.ts b/template/app/src/payment/polar/checkoutUtils.ts new file mode 100644 index 000000000..bf81842d1 --- /dev/null +++ b/template/app/src/payment/polar/checkoutUtils.ts @@ -0,0 +1,44 @@ +import { Checkout } from "@polar-sh/sdk/models/components/checkout.js"; +import { Customer } from "@polar-sh/sdk/models/components/customer.js"; +import { config } from "wasp/server"; +import { polarClient } from "./polarClient"; + +/** + * Returns a Polar customer for the given User email, creating a customer if none exist. + * + * NOTE: Polar enforces unique emails and `externalId`. + * Additionally, `externalId` can't be changed once set. + */ +export async function ensurePolarCustomer( + userId: string, + userEmail: string, +): Promise { + const polarCustomers = await polarClient.customers.list({ + email: userEmail, + }); + + if (polarCustomers.result.items.length === 0) { + return polarClient.customers.create({ + externalId: userId, + email: userEmail, + }); + } else { + return polarCustomers.result.items[0]; + } +} + +interface CreatePolarCheckoutSessionArgs { + productId: string; + customerId: string; +} + +export function createPolarCheckoutSession({ + productId, + customerId, +}: CreatePolarCheckoutSessionArgs): Promise { + return polarClient.checkouts.create({ + products: [productId], + successUrl: `${config.frontendUrl}/checkout?status=success`, + customerId, + }); +} diff --git a/template/app/src/payment/polar/paymentProcessor.ts b/template/app/src/payment/polar/paymentProcessor.ts new file mode 100644 index 000000000..fd54d2536 --- /dev/null +++ b/template/app/src/payment/polar/paymentProcessor.ts @@ -0,0 +1,65 @@ +import { + type CreateCheckoutSessionArgs, + type FetchCustomerPortalUrlArgs, + type PaymentProcessor, +} from "../paymentProcessor"; +import { + fetchUserPaymentProcessorUserId, + updateUserPaymentProcessorUserId, +} from "../user"; +import { + createPolarCheckoutSession, + ensurePolarCustomer, +} from "./checkoutUtils"; +import { polarClient } from "./polarClient"; +import { polarMiddlewareConfigFn, polarWebhook } from "./webhook"; + +export const polarPaymentProcessor: PaymentProcessor = { + id: "polar", + createCheckoutSession: async ({ + userId, + userEmail, + paymentPlan, + prismaUserDelegate, + }: CreateCheckoutSessionArgs) => { + const customer = await ensurePolarCustomer(userId, userEmail); + + await updateUserPaymentProcessorUserId( + { userId, paymentProcessorUserId: customer.id }, + prismaUserDelegate, + ); + + const checkoutSession = await createPolarCheckoutSession({ + productId: paymentPlan.getPaymentProcessorPlanId(), + customerId: customer.id, + }); + + return { + session: { + id: checkoutSession.id, + url: checkoutSession.url, + }, + }; + }, + fetchCustomerPortalUrl: async ({ + userId, + prismaUserDelegate, + }: FetchCustomerPortalUrlArgs) => { + const paymentProcessorUserId = await fetchUserPaymentProcessorUserId( + userId, + prismaUserDelegate, + ); + + if (!paymentProcessorUserId) { + return null; + } + + const customerSession = await polarClient.customerSessions.create({ + customerId: paymentProcessorUserId, + }); + + return customerSession.customerPortalUrl; + }, + webhook: polarWebhook, + webhookMiddlewareConfigFn: polarMiddlewareConfigFn, +}; diff --git a/template/app/src/payment/polar/polarClient.ts b/template/app/src/payment/polar/polarClient.ts new file mode 100644 index 000000000..edb80d8f3 --- /dev/null +++ b/template/app/src/payment/polar/polarClient.ts @@ -0,0 +1,10 @@ +import { Polar } from "@polar-sh/sdk"; +import { requireNodeEnvVar } from "../../server/utils"; + +export const polarClient = new Polar({ + accessToken: requireNodeEnvVar("POLAR_ORGANIZATION_ACCESS_TOKEN"), + server: + requireNodeEnvVar("POLAR_SANDBOX_MODE") === "true" + ? "sandbox" + : "production", +}); diff --git a/template/app/src/payment/polar/webhook.ts b/template/app/src/payment/polar/webhook.ts new file mode 100644 index 000000000..e59f8c327 --- /dev/null +++ b/template/app/src/payment/polar/webhook.ts @@ -0,0 +1,169 @@ +import { Subscription } from "@polar-sh/sdk/models/components/subscription.js"; +import { SubscriptionStatus } from "@polar-sh/sdk/models/components/subscriptionstatus.js"; +import { WebhookOrderPaidPayload } from "@polar-sh/sdk/models/components/webhookorderpaidpayload.js"; +import { WebhookSubscriptionUpdatedPayload } from "@polar-sh/sdk/models/components/webhooksubscriptionupdatedpayload.js"; +import { validateEvent } from "@polar-sh/sdk/webhooks"; +import express from "express"; +import type { MiddlewareConfigFn, PrismaClient } from "wasp/server"; +import type { PaymentsWebhook } from "wasp/server/api"; +import { requireNodeEnvVar } from "../../server/utils"; +import { assertUnreachable } from "../../shared/utils"; +import { UnhandledWebhookEventError } from "../errors"; +import { + getPaymentPlanIdByPaymentProcessorPlanId, + SubscriptionStatus as OpenSaasSubscriptionStatus, + PaymentPlanId, + paymentPlans, +} from "../plans"; +import { updateUserCredits, updateUserSubscription } from "../user"; + +/** + * Polar requires a raw request to construct events successfully. + */ +export const polarMiddlewareConfigFn: MiddlewareConfigFn = ( + middlewareConfig, +) => { + middlewareConfig.delete("express.json"); + middlewareConfig.set( + "express.raw", + express.raw({ type: "application/json" }), + ); + + return middlewareConfig; +}; + +export const polarWebhook: PaymentsWebhook = async ( + request, + response, + context, +) => { + const prismaUserDelegate = context.entities.User; + try { + const event = validateEvent( + request.body, + request.headers as Record, + requireNodeEnvVar("POLAR_WEBHOOK_SECRET"), + ); + + switch (event.type) { + case "order.paid": + await handleOrderPaid(event, prismaUserDelegate); + break; + case "subscription.updated": + await handleSubscriptionUpdated(event, prismaUserDelegate); + break; + default: + throw new UnhandledWebhookEventError(event.type); + } + return response.status(204).send(); + } catch (error) { + if (error instanceof UnhandledWebhookEventError) { + // In development, it is likely that we will receive events that we are not handling. + if (process.env.NODE_ENV === "development") { + console.info("Unhandled Polar webhook event in development: ", error); + } else if (process.env.NODE_ENV === "production") { + console.error("Unhandled Polar webhook event in production: ", error); + } + + // We must return a 2XX status code, otherwise Polar will keep retrying the event. + return response.status(200).json({ error: error.message }); + } + + console.error("Polar webhook error: ", error); + if (error instanceof Error) { + return response.status(400).json({ error: error.message }); + } else { + return response + .status(500) + .json({ error: "Error processing Polar webhook event" }); + } + } +}; + +async function handleOrderPaid( + { data: order }: WebhookOrderPaidPayload, + userDelegate: PrismaClient["user"], +): Promise { + const paymentPlanId = getPaymentPlanIdByPaymentProcessorPlanId( + order.productId, + ); + + switch (paymentPlanId) { + case PaymentPlanId.Credits10: + await updateUserCredits( + { + paymentProcessorUserId: order.customerId, + numOfCreditsPurchased: paymentPlans[paymentPlanId].effect.amount, + datePaid: order.createdAt, + }, + userDelegate, + ); + break; + case PaymentPlanId.Hobby: + case PaymentPlanId.Pro: + await updateUserSubscription( + { + paymentProcessorUserId: order.customerId, + paymentPlanId, + subscriptionStatus: OpenSaasSubscriptionStatus.Active, + datePaid: order.createdAt, + }, + userDelegate, + ); + break; + default: + assertUnreachable(paymentPlanId); + } +} + +async function handleSubscriptionUpdated( + { data: subscription }: WebhookSubscriptionUpdatedPayload, + userDelegate: PrismaClient["user"], +): Promise { + const newSubscriptionStatus = getOpenSaasSubscriptionStatus(subscription); + if (!newSubscriptionStatus) { + return; + } + + const paymentPlanId = getPaymentPlanIdByPaymentProcessorPlanId( + subscription.productId, + ); + + await updateUserSubscription( + { + paymentProcessorUserId: subscription.customer.id, + subscriptionStatus: newSubscriptionStatus, + paymentPlanId, + }, + userDelegate, + ); +} + +function getOpenSaasSubscriptionStatus( + subscription: Subscription, +): OpenSaasSubscriptionStatus | undefined { + const polarToOpenSaasSubscriptionStatus: Record< + SubscriptionStatus, + OpenSaasSubscriptionStatus | undefined + > = { + trialing: OpenSaasSubscriptionStatus.Active, + active: OpenSaasSubscriptionStatus.Active, + past_due: OpenSaasSubscriptionStatus.PastDue, + canceled: OpenSaasSubscriptionStatus.Deleted, + unpaid: OpenSaasSubscriptionStatus.Deleted, + incomplete_expired: OpenSaasSubscriptionStatus.Deleted, + incomplete: undefined, + }; + + const subscriptionStatus = + polarToOpenSaasSubscriptionStatus[subscription.status]; + + if ( + subscriptionStatus === OpenSaasSubscriptionStatus.Active && + subscription.cancelAtPeriodEnd + ) { + return OpenSaasSubscriptionStatus.CancelAtPeriodEnd; + } + + return subscriptionStatus; +} diff --git a/template/app/src/payment/stripe/checkoutUtils.ts b/template/app/src/payment/stripe/checkoutUtils.ts index adc6475b3..03a1504e8 100644 --- a/template/app/src/payment/stripe/checkoutUtils.ts +++ b/template/app/src/payment/stripe/checkoutUtils.ts @@ -1,64 +1,70 @@ -import type { StripeMode } from "./paymentProcessor"; - import Stripe from "stripe"; -import { stripe } from "./stripeClient"; +import { User } from "wasp/entities"; +import { config } from "wasp/server"; +import { stripeClient } from "./stripeClient"; -// WASP_WEB_CLIENT_URL will be set up by Wasp when deploying to production: https://wasp.sh/docs/deploying -const DOMAIN = process.env.WASP_WEB_CLIENT_URL || "http://localhost:3000"; +/** + * Returns a Stripe customer for the given User email, creating a customer if none exist. + * Implements email uniqueness logic since Stripe doesn't enforce unique emails. + */ +export async function ensureStripeCustomer( + userEmail: NonNullable, +): Promise { + const customers = await stripeClient.customers.list({ + email: userEmail, + }); -export async function fetchStripeCustomer(customerEmail: string) { - let customer: Stripe.Customer; - try { - const stripeCustomers = await stripe.customers.list({ - email: customerEmail, + if (customers.data.length === 0) { + return stripeClient.customers.create({ + email: userEmail, }); - if (!stripeCustomers.data.length) { - console.log("creating customer"); - customer = await stripe.customers.create({ - email: customerEmail, - }); - } else { - console.log("using existing customer"); - customer = stripeCustomers.data[0]; - } - return customer; - } catch (error) { - console.error(error); - throw error; + } else { + return customers.data[0]; } } interface CreateStripeCheckoutSessionParams { - priceId: string; - customerId: string; - mode: StripeMode; + priceId: Stripe.Price["id"]; + customerId: Stripe.Customer["id"]; + mode: Stripe.Checkout.Session.Mode; } -export async function createStripeCheckoutSession({ +export function createStripeCheckoutSession({ priceId, customerId, mode, -}: CreateStripeCheckoutSessionParams) { - try { - return await stripe.checkout.sessions.create({ - line_items: [ - { - price: priceId, - quantity: 1, - }, - ], - mode: mode, - success_url: `${DOMAIN}/checkout?status=success`, - cancel_url: `${DOMAIN}/checkout?status=canceled`, - automatic_tax: { enabled: true }, - allow_promotion_codes: true, - customer_update: { - address: "auto", +}: CreateStripeCheckoutSessionParams): Promise { + return stripeClient.checkout.sessions.create({ + customer: customerId, + line_items: [ + { + price: priceId, + quantity: 1, }, - customer: customerId, - }); - } catch (error) { - console.error(error); - throw error; - } + ], + mode, + success_url: `${config.frontendUrl}/checkout?status=success`, + cancel_url: `${config.frontendUrl}/checkout?status=canceled`, + automatic_tax: { enabled: true }, + allow_promotion_codes: true, + customer_update: { + address: "auto", + }, + invoice_creation: getInvoiceCreationConfig(mode), + }); +} + +/** + * Stripe automatically creates invoices for subscriptions. + * For one-time payments, we must enable them manually. + * However, enabling invoices for subscriptions will throw an error. + */ +function getInvoiceCreationConfig( + mode: Stripe.Checkout.Session.Mode, +): Stripe.Checkout.SessionCreateParams["invoice_creation"] { + return mode === "payment" + ? { + enabled: true, + } + : undefined; } diff --git a/template/app/src/payment/stripe/paymentDetails.ts b/template/app/src/payment/stripe/paymentDetails.ts deleted file mode 100644 index 3d67941a2..000000000 --- a/template/app/src/payment/stripe/paymentDetails.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { PrismaClient } from "@prisma/client"; -import type { SubscriptionStatus } from "../plans"; -import { PaymentPlanId } from "../plans"; - -export const updateUserStripePaymentDetails = async ( - { - userStripeId, - subscriptionPlan, - subscriptionStatus, - datePaid, - numOfCreditsPurchased, - }: { - userStripeId: string; - subscriptionPlan?: PaymentPlanId; - subscriptionStatus?: SubscriptionStatus; - numOfCreditsPurchased?: number; - datePaid?: Date; - }, - userDelegate: PrismaClient["user"], -) => { - return userDelegate.update({ - where: { - paymentProcessorUserId: userStripeId, - }, - data: { - paymentProcessorUserId: userStripeId, - subscriptionPlan, - subscriptionStatus, - datePaid, - credits: - numOfCreditsPurchased !== undefined - ? { increment: numOfCreditsPurchased } - : undefined, - }, - }); -}; diff --git a/template/app/src/payment/stripe/paymentProcessor.ts b/template/app/src/payment/stripe/paymentProcessor.ts index e52a00934..b2941d365 100644 --- a/template/app/src/payment/stripe/paymentProcessor.ts +++ b/template/app/src/payment/stripe/paymentProcessor.ts @@ -1,18 +1,23 @@ -import { requireNodeEnvVar } from "../../server/utils"; +import Stripe from "stripe"; +import { config } from "wasp/server"; +import { assertUnreachable } from "../../shared/utils"; import type { CreateCheckoutSessionArgs, FetchCustomerPortalUrlArgs, PaymentProcessor, } from "../paymentProcessor"; import type { PaymentPlanEffect } from "../plans"; +import { + fetchUserPaymentProcessorUserId, + updateUserPaymentProcessorUserId, +} from "../user"; import { createStripeCheckoutSession, - fetchStripeCustomer, + ensureStripeCustomer, } from "./checkoutUtils"; +import { stripeClient } from "./stripeClient"; import { stripeMiddlewareConfigFn, stripeWebhook } from "./webhook"; -export type StripeMode = "subscription" | "payment"; - export const stripePaymentProcessor: PaymentProcessor = { id: "stripe", createCheckoutSession: async ({ @@ -21,40 +26,66 @@ export const stripePaymentProcessor: PaymentProcessor = { paymentPlan, prismaUserDelegate, }: CreateCheckoutSessionArgs) => { - const customer = await fetchStripeCustomer(userEmail); - const stripeSession = await createStripeCheckoutSession({ - priceId: paymentPlan.getPaymentProcessorPlanId(), + const customer = await ensureStripeCustomer(userEmail); + + await updateUserPaymentProcessorUserId( + { userId, paymentProcessorUserId: customer.id }, + prismaUserDelegate, + ); + + const checkoutSession = await createStripeCheckoutSession({ customerId: customer.id, - mode: paymentPlanEffectToStripeMode(paymentPlan.effect), + priceId: paymentPlan.getPaymentProcessorPlanId(), + mode: paymentPlanEffectToStripeCheckoutSessionMode(paymentPlan.effect), }); - await prismaUserDelegate.update({ - where: { - id: userId, - }, - data: { - paymentProcessorUserId: customer.id, + + if (!checkoutSession.url) { + throw new Error( + "Stripe checkout session URL is missing. Checkout session might not be active.", + ); + } + + return { + session: { + url: checkoutSession.url, + id: checkoutSession.id, }, - }); - if (!stripeSession.url) - throw new Error("Error creating Stripe Checkout Session"); - const session = { - url: stripeSession.url, - id: stripeSession.id, }; - return { session }; }, - fetchCustomerPortalUrl: async (_args: FetchCustomerPortalUrlArgs) => - requireNodeEnvVar("STRIPE_CUSTOMER_PORTAL_URL"), + fetchCustomerPortalUrl: async ({ + prismaUserDelegate, + userId, + }: FetchCustomerPortalUrlArgs) => { + const paymentProcessorUserId = await fetchUserPaymentProcessorUserId( + userId, + prismaUserDelegate, + ); + + if (!paymentProcessorUserId) { + return null; + } + + const billingPortalSession = + await stripeClient.billingPortal.sessions.create({ + customer: paymentProcessorUserId, + return_url: `${config.frontendUrl}/account`, + }); + + return billingPortalSession.url; + }, webhook: stripeWebhook, webhookMiddlewareConfigFn: stripeMiddlewareConfigFn, }; -function paymentPlanEffectToStripeMode( - planEffect: PaymentPlanEffect, -): StripeMode { - const effectToMode: Record = { - subscription: "subscription", - credits: "payment", - }; - return effectToMode[planEffect.kind]; +function paymentPlanEffectToStripeCheckoutSessionMode({ + kind, +}: PaymentPlanEffect): Stripe.Checkout.Session.Mode { + switch (kind) { + case "subscription": + return "subscription"; + case "credits": + return "payment"; + default: + assertUnreachable(kind); + } } diff --git a/template/app/src/payment/stripe/stripeClient.ts b/template/app/src/payment/stripe/stripeClient.ts index 3dd2a4178..e4f593d78 100644 --- a/template/app/src/payment/stripe/stripeClient.ts +++ b/template/app/src/payment/stripe/stripeClient.ts @@ -1,12 +1,30 @@ import Stripe from "stripe"; import { requireNodeEnvVar } from "../../server/utils"; -export const stripe = new Stripe(requireNodeEnvVar("STRIPE_API_KEY"), { - // NOTE: - // API version below should ideally match the API version in your Stripe dashboard. - // If that is not the case, you will most likely want to (up/down)grade the `stripe` - // npm package to the API version that matches your Stripe dashboard's one. - // For more details and alternative setups check - // https://docs.stripe.com/api/versioning . - apiVersion: "2025-04-30.basil", +/** + * The Stripe client API version. + * + * By default, Stripe uses the API version set in your Dashboard. + * + * You can override this by setting `apiVersion` when creating the Stripe client. + * This is useful for testing new API versions before updating the + * default version in your Dashboard. + * + * The Stripe Node SDK works with multiple API versions and follows semantic versioning. + * Major SDK versions typically correspond to Stripe's biannual releases (like 'basil'). + * Each SDK version uses the API version that was current when it was released. + * + * Note: '2025-04-30.basil' follows Stripe's newer versioning format where: + * - The date represents the release date + * - The suffix ('basil') indicates the major release name + * + * Monthly API updates use the same suffix as the last major release but with updated dates. + * + * @see https://docs.stripe.com/api/versioning + * @see https://docs.stripe.com/sdks/versioning + */ +const STRIPE_API_VERSION = "2025-04-30.basil"; + +export const stripeClient = new Stripe(requireNodeEnvVar("STRIPE_API_KEY"), { + apiVersion: STRIPE_API_VERSION, }); diff --git a/template/app/src/payment/stripe/webhook.ts b/template/app/src/payment/stripe/webhook.ts index fed32220d..ce0808cc8 100644 --- a/template/app/src/payment/stripe/webhook.ts +++ b/template/app/src/payment/stripe/webhook.ts @@ -1,280 +1,267 @@ import { type PrismaClient } from "@prisma/client"; import express from "express"; import type { Stripe } from "stripe"; -import { HttpError, type MiddlewareConfigFn } from "wasp/server"; +import { type MiddlewareConfigFn } from "wasp/server"; import { type PaymentsWebhook } from "wasp/server/api"; import { emailSender } from "wasp/server/email"; import { requireNodeEnvVar } from "../../server/utils"; import { assertUnreachable } from "../../shared/utils"; import { UnhandledWebhookEventError } from "../errors"; import { + getPaymentPlanIdByPaymentProcessorPlanId, PaymentPlanId, paymentPlans, SubscriptionStatus, - type PaymentPlanEffect, } from "../plans"; -import { updateUserStripePaymentDetails } from "./paymentDetails"; -import { stripe } from "./stripeClient"; -import { - parseWebhookPayload, - type InvoicePaidData, - type SessionCompletedData, - type SubscriptionDeletedData, - type SubscriptionUpdatedData, -} from "./webhookPayload"; +import { updateUserCredits, updateUserSubscription } from "../user"; +import { stripeClient } from "./stripeClient"; + +/** + * Stripe requires a raw request to construct events successfully. + */ +export const stripeMiddlewareConfigFn: MiddlewareConfigFn = ( + middlewareConfig, +) => { + middlewareConfig.delete("express.json"); + middlewareConfig.set( + "express.raw", + express.raw({ type: "application/json" }), + ); + return middlewareConfig; +}; export const stripeWebhook: PaymentsWebhook = async ( request, response, context, ) => { + const prismaUserDelegate = context.entities.User; try { - const rawStripeEvent = constructStripeEvent(request); - const { eventName, data } = await parseWebhookPayload(rawStripeEvent); - const prismaUserDelegate = context.entities.User; - switch (eventName) { - case "checkout.session.completed": - await handleCheckoutSessionCompleted(data, prismaUserDelegate); - break; + const event = constructStripeEvent(request); + + // If you'd like to handle more events, you can add more cases below. + // When deploying your app, you configure your webhook in the Stripe dashboard + // to only send the events that you're handling above. + // See: https://docs.opensaas.sh/guides/deploying/#setting-up-your-stripe-webhook + switch (event.type) { case "invoice.paid": - await handleInvoicePaid(data, prismaUserDelegate); + await handleInvoicePaid(event, prismaUserDelegate); break; case "customer.subscription.updated": - await handleCustomerSubscriptionUpdated(data, prismaUserDelegate); + await handleCustomerSubscriptionUpdated(event, prismaUserDelegate); break; case "customer.subscription.deleted": - await handleCustomerSubscriptionDeleted(data, prismaUserDelegate); + await handleCustomerSubscriptionDeleted(event, prismaUserDelegate); break; default: - // If you'd like to handle more events, you can add more cases above. - // When deploying your app, you configure your webhook in the Stripe dashboard to only send the events that you're - // handling above and that are necessary for the functioning of your app. See: https://docs.opensaas.sh/guides/deploying/#setting-up-your-stripe-webhook - // In development, it is likely that you will receive other events that you are not handling, and that's fine. These can be ignored without any issues. - assertUnreachable(eventName); + throw new UnhandledWebhookEventError(event.type); } - return response.json({ received: true }); // Stripe expects a 200 response to acknowledge receipt of the webhook - } catch (err) { - if (err instanceof UnhandledWebhookEventError) { - console.error(err.message); - return response.status(422).json({ error: err.message }); + return response.status(204).send(); + } catch (error) { + if (error instanceof UnhandledWebhookEventError) { + // In development, it is likely that we will receive events that we are not handling. + // E.g. via the `stripe trigger` command. + if (process.env.NODE_ENV === "development") { + console.info("Unhandled Stripe webhook event in development: ", error); + } else if (process.env.NODE_ENV === "production") { + console.error("Unhandled Stripe webhook event in production: ", error); + } + + // We must return a 2XX status code, otherwise Stripe will keep retrying the event. + return response.status(204).send(); } - console.error("Webhook error:", err); - if (err instanceof HttpError) { - return response.status(err.statusCode).json({ error: err.message }); + console.error("Stripe webhook error:", error); + if (error instanceof Error) { + return response.status(400).json({ error: error.message }); } else { return response - .status(400) + .status(500) .json({ error: "Error processing Stripe webhook event" }); } } }; function constructStripeEvent(request: express.Request): Stripe.Event { - try { - const secret = requireNodeEnvVar("STRIPE_WEBHOOK_SECRET"); - const sig = request.headers["stripe-signature"]; - if (!sig) { - throw new HttpError(400, "Stripe webhook signature not provided"); - } - return stripe.webhooks.constructEvent(request.body, sig, secret); - } catch (err) { - throw new HttpError(500, "Error constructing Stripe webhook event"); + const stripeWebhookSecret = requireNodeEnvVar("STRIPE_WEBHOOK_SECRET"); + const stripeSignature = request.headers["stripe-signature"]; + if (!stripeSignature) { + throw new Error("Stripe webhook signature not provided"); } -} -export const stripeMiddlewareConfigFn: MiddlewareConfigFn = ( - middlewareConfig, -) => { - // We need to delete the default 'express.json' middleware and replace it with 'express.raw' middleware - // because webhook data in the body of the request as raw JSON, not as JSON in the body of the request. - middlewareConfig.delete("express.json"); - middlewareConfig.set( - "express.raw", - express.raw({ type: "application/json" }), + return stripeClient.webhooks.constructEvent( + request.body, + stripeSignature, + stripeWebhookSecret, ); - return middlewareConfig; -}; - -// Here we only update the user's payment details, and confirm credits because Stripe does not send invoices for one-time payments. -// NOTE: If you're accepting async payment methods like bank transfers or SEPA and not just card payments -// which are synchronous, checkout session completed could potentially result in a pending payment. -// If so, use the checkout.session.async_payment_succeeded event to confirm the payment. -async function handleCheckoutSessionCompleted( - session: SessionCompletedData, - prismaUserDelegate: PrismaClient["user"], -) { - const isSuccessfulOneTimePayment = - session.mode === "payment" && session.payment_status === "paid"; - if (isSuccessfulOneTimePayment) { - await saveSuccessfulOneTimePayment(session, prismaUserDelegate); - } } -async function saveSuccessfulOneTimePayment( - session: SessionCompletedData, +async function handleInvoicePaid( + event: Stripe.InvoicePaidEvent, prismaUserDelegate: PrismaClient["user"], -) { - const userStripeId = session.customer; - const lineItems = await getCheckoutLineItemsBySessionId(session.id); - const lineItemPriceId = extractPriceId(lineItems); - const planId = getPlanIdByPriceId(lineItemPriceId); - const plan = paymentPlans[planId]; - const { numOfCreditsPurchased } = getPlanEffectPaymentDetails({ - planId, - planEffect: plan.effect, - }); - return updateUserStripePaymentDetails( - { userStripeId, numOfCreditsPurchased, datePaid: new Date() }, - prismaUserDelegate, +): Promise { + const invoice = event.data.object; + const customerId = getCustomerId(invoice.customer); + const invoicePaidAtDate = getInvoicePaidAtDate(invoice); + const paymentPlanId = getPaymentPlanIdByPaymentProcessorPlanId( + getInvoicePriceId(invoice), ); -} -// This is called when a subscription is successfully purchased or renewed and payment succeeds. -// Invoices are not created for one-time payments, so we handle them above. -async function handleInvoicePaid( - invoice: InvoicePaidData, - prismaUserDelegate: PrismaClient["user"], -) { - await saveActiveSubscription(invoice, prismaUserDelegate); + switch (paymentPlanId) { + case PaymentPlanId.Credits10: + await updateUserCredits( + { + paymentProcessorUserId: customerId, + datePaid: invoicePaidAtDate, + numOfCreditsPurchased: paymentPlans[paymentPlanId].effect.amount, + }, + prismaUserDelegate, + ); + break; + case PaymentPlanId.Pro: + case PaymentPlanId.Hobby: + await updateUserSubscription( + { + paymentProcessorUserId: customerId, + datePaid: invoicePaidAtDate, + paymentPlanId, + subscriptionStatus: SubscriptionStatus.Active, + }, + prismaUserDelegate, + ); + break; + default: + assertUnreachable(paymentPlanId); + } } -async function saveActiveSubscription( - invoice: InvoicePaidData, - prismaUserDelegate: PrismaClient["user"], -) { - const userStripeId = invoice.customer; - const datePaid = new Date(invoice.period_start * 1000); - const priceId = extractPriceId(invoice.lines); - const subscriptionPlan = getPlanIdByPriceId(priceId); - return updateUserStripePaymentDetails( - { - userStripeId, - datePaid, - subscriptionPlan, - subscriptionStatus: SubscriptionStatus.Active, - }, - prismaUserDelegate, - ); +function getInvoicePriceId(invoice: Stripe.Invoice): Stripe.Price["id"] { + const invoiceLineItems = invoice.lines.data; + // We only expect one line item. + // If your workflow expects more, you should change this function to handle them. + if (invoiceLineItems.length !== 1) { + throw new Error("There should be exactly one line item in Stripe invoice"); + } + + const priceId = invoiceLineItems[0].pricing?.price_details?.price; + if (!priceId) { + throw new Error("Unable to extract price id from items"); + } + + return priceId; } async function handleCustomerSubscriptionUpdated( - subscription: SubscriptionUpdatedData, + event: Stripe.CustomerSubscriptionUpdatedEvent, prismaUserDelegate: PrismaClient["user"], -) { - const userStripeId = subscription.customer; - let subscriptionStatus: SubscriptionStatus | undefined; - const priceId = extractPriceId(subscription.items); - const subscriptionPlan = getPlanIdByPriceId(priceId); +): Promise { + const subscription = event.data.object; - // There are other subscription statuses, such as `trialing` that we are not handling and simply ignore - // If you'd like to handle more statuses, you can add more cases above. Make sure to update the `SubscriptionStatus` type in `payment/plans.ts` as well. - if (subscription.status === SubscriptionStatus.Active) { - subscriptionStatus = subscription.cancel_at_period_end - ? SubscriptionStatus.CancelAtPeriodEnd - : SubscriptionStatus.Active; - } else if (subscription.status === SubscriptionStatus.PastDue) { - subscriptionStatus = SubscriptionStatus.PastDue; + // There are other subscription statuses, such as `trialing` that we are not handling. + const subscriptionStatus = getOpenSaasSubscriptionStatus(subscription); + if (!subscriptionStatus) { + return; } - if (subscriptionStatus) { - const user = await updateUserStripePaymentDetails( - { userStripeId, subscriptionPlan, subscriptionStatus }, - prismaUserDelegate, - ); - if (subscription.cancel_at_period_end) { - if (user.email) { - await emailSender.send({ - to: user.email, - subject: "We hate to see you go :(", - text: "We hate to see you go. Here is a sweet offer...", - html: "We hate to see you go. Here is a sweet offer...", - }); - } - } - return user; - } -} -async function handleCustomerSubscriptionDeleted( - subscription: SubscriptionDeletedData, - prismaUserDelegate: PrismaClient["user"], -) { - const userStripeId = subscription.customer; - return updateUserStripePaymentDetails( - { userStripeId, subscriptionStatus: SubscriptionStatus.Deleted }, + const customerId = getCustomerId(subscription.customer); + const paymentPlanId = getPaymentPlanIdByPaymentProcessorPlanId( + getSubscriptionPriceId(subscription), + ); + + const user = await updateUserSubscription( + { paymentProcessorUserId: customerId, paymentPlanId, subscriptionStatus }, prismaUserDelegate, ); -} -// We only expect one line item, but if you set up a product with multiple prices, you should change this function to handle them. -function extractPriceId( - items: - | Stripe.ApiList - | SubscriptionUpdatedData["items"] - | InvoicePaidData["lines"], -): string { - if (items.data.length === 0) { - throw new HttpError(400, "No items in stripe event object"); + if (subscription.cancel_at_period_end && user.email) { + await emailSender.send({ + to: user.email, + subject: "We hate to see you go :(", + text: "We hate to see you go. Here is a sweet offer...", + html: "We hate to see you go. Here is a sweet offer...", + }); } - if (items.data.length > 1) { - throw new HttpError(400, "More than one item in stripe event object"); - } - const item = items.data[0]; +} - // The 'price' property is found on SubscriptionItem and LineItem. - if ("price" in item && item.price?.id) { - return item.price.id; - } +function getOpenSaasSubscriptionStatus( + subscription: Stripe.Subscription, +): SubscriptionStatus | undefined { + const stripeToOpenSaasSubscriptionStatus: Record< + Stripe.Subscription.Status, + SubscriptionStatus | undefined + > = { + trialing: SubscriptionStatus.Active, + active: SubscriptionStatus.Active, + past_due: SubscriptionStatus.PastDue, + canceled: SubscriptionStatus.Deleted, + unpaid: SubscriptionStatus.Deleted, + incomplete_expired: SubscriptionStatus.Deleted, + paused: undefined, + incomplete: undefined, + }; - // The 'pricing' property is found on InvoiceLineItem. - if ("pricing" in item) { - const priceId = item.pricing?.price_details?.price; - if (priceId) { - return priceId; - } + const subscriptionStatus = + stripeToOpenSaasSubscriptionStatus[subscription.status]; + + if ( + subscriptionStatus === SubscriptionStatus.Active && + subscription.cancel_at_period_end + ) { + return SubscriptionStatus.CancelAtPeriodEnd; } - throw new HttpError(400, "Unable to extract price id from item"); + + return subscriptionStatus; } -async function getCheckoutLineItemsBySessionId(sessionId: string) { - const { line_items } = await stripe.checkout.sessions.retrieve(sessionId, { - expand: ["line_items"], - }); - if (!line_items) { - throw new HttpError(400, "No line items found in checkout session"); +function getSubscriptionPriceId( + subscription: Stripe.Subscription, +): Stripe.Price["id"] { + const subscriptionItems = subscription.items.data; + // We only expect one subscription item. + // If your workflow expects more, you should change this function to handle them. + if (subscriptionItems.length !== 1) { + throw new Error( + "There should be exactly one subscription item in Stripe subscription", + ); } - return line_items; + + return subscriptionItems[0].price.id; } -function getPlanIdByPriceId(priceId: string): PaymentPlanId { - const planId = Object.values(PaymentPlanId).find( - (planId) => paymentPlans[planId].getPaymentProcessorPlanId() === priceId, +async function handleCustomerSubscriptionDeleted( + event: Stripe.CustomerSubscriptionDeletedEvent, + prismaUserDelegate: PrismaClient["user"], +): Promise { + const subscription = event.data.object; + const customerId = getCustomerId(subscription.customer); + + await updateUserSubscription( + { + paymentProcessorUserId: customerId, + subscriptionStatus: SubscriptionStatus.Deleted, + }, + prismaUserDelegate, ); - if (!planId) { - throw new Error(`No plan with Stripe price id ${priceId}`); +} + +function getCustomerId( + customer: string | Stripe.Customer | Stripe.DeletedCustomer | null, +): Stripe.Customer["id"] { + if (!customer) { + throw new Error("Customer is missing"); + } else if (typeof customer === "string") { + return customer; + } else { + return customer.id; } - return planId; } -function getPlanEffectPaymentDetails({ - planId, - planEffect, -}: { - planId: PaymentPlanId; - planEffect: PaymentPlanEffect; -}): { - subscriptionPlan: PaymentPlanId | undefined; - numOfCreditsPurchased: number | undefined; -} { - switch (planEffect.kind) { - case "subscription": - return { subscriptionPlan: planId, numOfCreditsPurchased: undefined }; - case "credits": - return { - subscriptionPlan: undefined, - numOfCreditsPurchased: planEffect.amount, - }; - default: - assertUnreachable(planEffect); +function getInvoicePaidAtDate(invoice: Stripe.Invoice): Date { + if (!invoice.status_transitions.paid_at) { + throw new Error("Invoice has not been paid yet"); } + + // Stripe returns timestamps in seconds (Unix time), + // so we multiply by 1000 to convert to milliseconds. + return new Date(invoice.status_transitions.paid_at * 1000); } diff --git a/template/app/src/payment/stripe/webhookPayload.ts b/template/app/src/payment/stripe/webhookPayload.ts deleted file mode 100644 index 3448a7d73..000000000 --- a/template/app/src/payment/stripe/webhookPayload.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { Stripe } from "stripe"; -import { HttpError } from "wasp/server"; -import * as z from "zod"; -import { UnhandledWebhookEventError } from "../errors"; - -export async function parseWebhookPayload(rawStripeEvent: Stripe.Event) { - try { - const event = await genericStripeEventSchema.parseAsync(rawStripeEvent); - switch (event.type) { - case "checkout.session.completed": - const session = await sessionCompletedDataSchema.parseAsync( - event.data.object, - ); - return { eventName: event.type, data: session }; - case "invoice.paid": - const invoice = await invoicePaidDataSchema.parseAsync( - event.data.object, - ); - return { eventName: event.type, data: invoice }; - case "customer.subscription.updated": - const updatedSubscription = - await subscriptionUpdatedDataSchema.parseAsync(event.data.object); - return { eventName: event.type, data: updatedSubscription }; - case "customer.subscription.deleted": - const deletedSubscription = - await subscriptionDeletedDataSchema.parseAsync(event.data.object); - return { eventName: event.type, data: deletedSubscription }; - default: - // If you'd like to handle more events, you can add more cases above. - throw new UnhandledWebhookEventError(event.type); - } - } catch (e: unknown) { - if (e instanceof UnhandledWebhookEventError) { - throw e; - } else { - console.error(e); - throw new HttpError(400, "Error parsing Stripe event object"); - } - } -} - -/** - * This is a subtype of - * @type import('stripe').Stripe.Event - */ -const genericStripeEventSchema = z.object({ - type: z.string(), - data: z.object({ - object: z.unknown(), - }), -}); - -/** - * This is a subtype of - * @type import('stripe').Stripe.Checkout.Session - */ -const sessionCompletedDataSchema = z.object({ - id: z.string(), - customer: z.string(), - payment_status: z.enum(["paid", "unpaid", "no_payment_required"]), - mode: z.enum(["payment", "subscription"]), -}); - -/** - * This is a subtype of - * @type import('stripe').Stripe.Invoice - */ -const invoicePaidDataSchema = z.object({ - id: z.string(), - customer: z.string(), - period_start: z.number(), - lines: z.object({ - data: z.array( - z.object({ - pricing: z.object({ price_details: z.object({ price: z.string() }) }), - }), - ), - }), -}); - -/** - * This is a subtype of - * @type import('stripe').Stripe.Subscription - */ -const subscriptionUpdatedDataSchema = z.object({ - customer: z.string(), - status: z.string(), - cancel_at_period_end: z.boolean(), - items: z.object({ - data: z.array( - z.object({ - price: z.object({ - id: z.string(), - }), - }), - ), - }), -}); - -/** - * This is a subtype of - * @type import('stripe').Stripe.Subscription - */ -const subscriptionDeletedDataSchema = z.object({ - customer: z.string(), -}); - -export type SessionCompletedData = z.infer; - -export type InvoicePaidData = z.infer; - -export type SubscriptionUpdatedData = z.infer< - typeof subscriptionUpdatedDataSchema ->; - -export type SubscriptionDeletedData = z.infer< - typeof subscriptionDeletedDataSchema ->; diff --git a/template/app/src/payment/user.ts b/template/app/src/payment/user.ts new file mode 100644 index 000000000..a7723a230 --- /dev/null +++ b/template/app/src/payment/user.ts @@ -0,0 +1,91 @@ +import { User } from "wasp/entities"; +import { PrismaClient } from "wasp/server"; +import { PaymentPlanId, SubscriptionStatus } from "./plans"; + +export async function fetchUserPaymentProcessorUserId( + userId: User["id"], + prismaUserDelegate: PrismaClient["user"], +): Promise { + const user = await prismaUserDelegate.findUniqueOrThrow({ + where: { + id: userId, + }, + select: { + paymentProcessorUserId: true, + }, + }); + + return user.paymentProcessorUserId; +} + +interface UpdateUserPaymentProcessorUserIdArgs { + userId: User["id"]; + paymentProcessorUserId: NonNullable; +} + +export function updateUserPaymentProcessorUserId( + { userId, paymentProcessorUserId }: UpdateUserPaymentProcessorUserIdArgs, + prismaUserDelegate: PrismaClient["user"], +): Promise { + return prismaUserDelegate.update({ + where: { + id: userId, + }, + data: { + paymentProcessorUserId, + }, + }); +} + +interface UpdateUserSubscriptionArgs { + paymentProcessorUserId: NonNullable; + subscriptionStatus: SubscriptionStatus; + paymentPlanId?: PaymentPlanId; + datePaid?: Date; +} + +export function updateUserSubscription( + { + paymentProcessorUserId, + paymentPlanId, + subscriptionStatus, + datePaid, + }: UpdateUserSubscriptionArgs, + userDelegate: PrismaClient["user"], +): Promise { + return userDelegate.update({ + where: { + paymentProcessorUserId, + }, + data: { + subscriptionPlan: paymentPlanId, + subscriptionStatus, + datePaid, + }, + }); +} + +interface UpdateUserCreditsArgs { + paymentProcessorUserId: NonNullable; + numOfCreditsPurchased: number; + datePaid: Date; +} + +export function updateUserCredits( + { + paymentProcessorUserId, + numOfCreditsPurchased, + datePaid, + }: UpdateUserCreditsArgs, + userDelegate: PrismaClient["user"], +): Promise { + return userDelegate.update({ + where: { + paymentProcessorUserId, + }, + data: { + credits: { increment: numOfCreditsPurchased }, + datePaid, + }, + }); +} diff --git a/template/app/src/shared/utils.ts b/template/app/src/shared/utils.ts index c7cd68494..d403fafed 100644 --- a/template/app/src/shared/utils.ts +++ b/template/app/src/shared/utils.ts @@ -2,8 +2,7 @@ * Used purely to help compiler check for exhaustiveness in switch statements, * will never execute. See https://stackoverflow.com/a/39419171. */ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export function assertUnreachable(x: never): never { +export function assertUnreachable(_: never): never { throw Error("This code should be unreachable"); } diff --git a/template/app/src/user/AccountPage.tsx b/template/app/src/user/AccountPage.tsx index 8055d52b3..a0482587e 100644 --- a/template/app/src/user/AccountPage.tsx +++ b/template/app/src/user/AccountPage.tsx @@ -1,15 +1,16 @@ import { getCustomerPortalUrl, useQuery } from "wasp/client/operations"; import { Link as WaspRouterLink, routes } from "wasp/client/router"; import type { User } from "wasp/entities"; -import { Button } from "../components/ui/button"; +import { Button } from "../client/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle, -} from "../components/ui/card"; -import { Separator } from "../components/ui/separator"; +} from "../client/components/ui/card"; +import { Separator } from "../client/components/ui/separator"; import { + PaymentPlanId, SubscriptionStatus, parsePaymentPlanId, prettyPaymentPlanName, @@ -29,12 +30,12 @@ export default function AccountPage({ user }: { user: User }) { {!!user.email && (

-
+
Email address -
-
+
+
{user.email} - +
)} @@ -43,12 +44,12 @@ export default function AccountPage({ user }: { user: User }) {
-
+
Username -
-
+
+
{user.username} - +
@@ -56,28 +57,39 @@ export default function AccountPage({ user }: { user: User }) {
-
+
Your Plan -
- +
-
+
+ Credits +
+
+ {user.credits} credits +
+
+ +
+
+
+ +
+
+
About - -
+
+
I'm a cool customer. - +
@@ -87,129 +99,97 @@ export default function AccountPage({ user }: { user: User }) { ); } -type UserCurrentPaymentPlanProps = { - subscriptionPlan: string | null; - subscriptionStatus: SubscriptionStatus | null; - datePaid: Date | null; - credits: number; -}; - -function UserCurrentPaymentPlan({ +function UserCurrentSubscriptionPlan({ subscriptionPlan, subscriptionStatus, datePaid, - credits, -}: UserCurrentPaymentPlanProps) { - if (subscriptionStatus && subscriptionPlan && datePaid) { - return ( - <> -
- {getUserSubscriptionStatusDescription({ - subscriptionPlan, - subscriptionStatus, - datePaid, - })} -
- {subscriptionStatus !== SubscriptionStatus.Deleted ? ( - - ) : ( - - )} - +}: Pick) { + let subscriptionPlanMessage = "Free Plan"; + if ( + subscriptionPlan !== null && + subscriptionStatus !== null && + datePaid !== null + ) { + subscriptionPlanMessage = formatSubscriptionStatusMessage( + parsePaymentPlanId(subscriptionPlan), + datePaid, + subscriptionStatus as SubscriptionStatus, ); } return ( <> -
- Credits remaining: {credits} -
- +
+ {subscriptionPlanMessage} +
+
+ +
); } -function getUserSubscriptionStatusDescription({ - subscriptionPlan, - subscriptionStatus, - datePaid, -}: { - subscriptionPlan: string; - subscriptionStatus: SubscriptionStatus; - datePaid: Date; -}) { - const planName = prettyPaymentPlanName(parsePaymentPlanId(subscriptionPlan)); - const endOfBillingPeriod = prettyPrintEndOfBillingPeriod(datePaid); - return prettyPrintStatus(planName, subscriptionStatus, endOfBillingPeriod); -} - -function prettyPrintStatus( - planName: string, +function formatSubscriptionStatusMessage( + subscriptionPlan: PaymentPlanId, + datePaid: Date, subscriptionStatus: SubscriptionStatus, - endOfBillingPeriod: string, ): string { + const paymentPlanName = prettyPaymentPlanName(subscriptionPlan); const statusToMessage: Record = { - active: `${planName}`, - past_due: `Payment for your ${planName} plan is past due! Please update your subscription payment information.`, - cancel_at_period_end: `Your ${planName} plan subscription has been canceled, but remains active until the end of the current billing period${endOfBillingPeriod}`, + active: `${paymentPlanName}`, + past_due: `Payment for your ${paymentPlanName} plan is past due! Please update your subscription payment information.`, + cancel_at_period_end: `Your ${paymentPlanName} plan subscription has been canceled, but remains active until the end of the current billing period: ${prettyPrintEndOfBillingPeriod( + datePaid, + )}`, deleted: `Your previous subscription has been canceled and is no longer active.`, }; - if (Object.keys(statusToMessage).includes(subscriptionStatus)) { - return statusToMessage[subscriptionStatus]; - } else { - throw new Error(`Invalid subscriptionStatus: ${subscriptionStatus}`); + + if (!statusToMessage[subscriptionStatus]) { + throw new Error(`Invalid subscription status: ${subscriptionStatus}`); } + + return statusToMessage[subscriptionStatus]; } function prettyPrintEndOfBillingPeriod(date: Date) { const oneMonthFromNow = new Date(date); oneMonthFromNow.setMonth(oneMonthFromNow.getMonth() + 1); - return ": " + oneMonthFromNow.toLocaleDateString(); + return oneMonthFromNow.toLocaleDateString(); } -function BuyMoreButton() { +function CustomerPortalButton() { + const { data: customerPortalUrl, isLoading: isCustomerPortalUrlLoading } = + useQuery(getCustomerPortalUrl); + + if (!customerPortalUrl) { + return null; + } + return ( -
- - Buy More/Upgrade - -
+ + + ); } -function CustomerPortalButton() { - const { - data: customerPortalUrl, - isLoading: isCustomerPortalUrlLoading, - error: customerPortalUrlError, - } = useQuery(getCustomerPortalUrl); - - const handleClick = () => { - if (customerPortalUrlError) { - console.error("Error fetching customer portal url"); - } - - if (customerPortalUrl) { - window.open(customerPortalUrl, "_blank"); - } else { - console.error("Customer portal URL is not available"); - } - }; +function BuyMoreButton({ + subscriptionStatus, +}: Pick) { + if ( + subscriptionStatus === SubscriptionStatus.Active || + subscriptionStatus === SubscriptionStatus.CancelAtPeriodEnd + ) { + return null; + } return ( -
- -
+ + + ); } diff --git a/template/app/src/user/UserDropdown.tsx b/template/app/src/user/UserDropdown.tsx index 09c8069bc..0f1b49c49 100644 --- a/template/app/src/user/UserDropdown.tsx +++ b/template/app/src/user/UserDropdown.tsx @@ -8,7 +8,7 @@ import { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from "../components/ui/dropdown-menu"; +} from "../client/components/ui/dropdown-menu"; import { userMenuItems } from "./constants"; export function UserDropdown({ user }: { user: Partial }) { diff --git a/template/app/tailwind.config.js b/template/app/tailwind.config.js index f437b3b7a..cd53f99c8 100644 --- a/template/app/tailwind.config.js +++ b/template/app/tailwind.config.js @@ -12,9 +12,6 @@ export default { darkMode: "class", theme: { extend: { - fontFamily: { - satoshi: ["Satoshi", "system-ui", "sans-serif"], - }, colors: { current: "currentColor", transparent: "transparent", diff --git a/template/blog/public/CRAIG_ROCK.png b/template/blog/public/CRAIG_ROCK.png index 526663749..6d4b1df8a 100644 Binary files a/template/blog/public/CRAIG_ROCK.png and b/template/blog/public/CRAIG_ROCK.png differ diff --git a/template/e2e-tests/package-lock.json b/template/e2e-tests/package-lock.json index efedffca3..bae06a0e0 100644 --- a/template/e2e-tests/package-lock.json +++ b/template/e2e-tests/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@playwright/test": "^1.42.1", "@prisma/client": "5.19.1", - "@wasp.sh/wasp-app-runner": "0.0.7", + "@wasp.sh/wasp-app-runner": "0.0.10", "prisma": "5.19.1" }, "devDependencies": { @@ -115,9 +115,9 @@ } }, "node_modules/@wasp.sh/wasp-app-runner": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@wasp.sh/wasp-app-runner/-/wasp-app-runner-0.0.7.tgz", - "integrity": "sha512-sN7b7DuMZMBrnnPoZDQuqPofxW9VyYudmdfbj8e5A+1sLV2WfSFGO7BBP/qcLYDIosU6c4TMiEnM0R3M0gy+uw==", + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/@wasp.sh/wasp-app-runner/-/wasp-app-runner-0.0.10.tgz", + "integrity": "sha512-gi4nCzzqJQsF3iw+jbJdqrXLzuqdQ1tEq5oQA8G857KcIIxvH0LZEgEDXEBFZitXdIulm0fOO0NcBibk5NyTOg==", "license": "MIT", "dependencies": { "@commander-js/extra-typings": "^13.1.0", @@ -146,6 +146,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -209,6 +210,7 @@ "integrity": "sha512-c5K9MiDaa+VAAyh1OiYk76PXOme9s3E992D7kvvIOhCrNsBQfy2mP2QAQtX0WNj140IgG++12kwZpYB9iIydNQ==", "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/engines": "5.19.1" }, diff --git a/template/e2e-tests/package.json b/template/e2e-tests/package.json index c35337401..e94df5023 100644 --- a/template/e2e-tests/package.json +++ b/template/e2e-tests/package.json @@ -12,14 +12,14 @@ "_comment-on-local:e2e:cleanup-stripe": "NOTE: because we are running the stripe webhook listener in the background, we want to make sure we kill the previous processes before starting a new one.", "e2e:playwright": "DEBUG=pw:webserver npx playwright test", "local:e2e:cleanup-stripe": "PID=$(ps -ef | grep 'stripe listen' | grep -v grep | awk '{print $2}') || true && kill -9 $PID || true", - "local:e2e:playwright:ui": "npx playwright test --ui", + "local:e2e:playwright:ui": "SKIP_EMAIL_VERIFICATION_IN_DEV=true npx playwright test --ui", "local:e2e:start": "npm run local:e2e:cleanup-stripe && npm run local:e2e:start-stripe && npm run local:e2e:playwright:ui && npm run local:e2e:cleanup-stripe", "local:e2e:start-stripe": "stripe listen --forward-to localhost:3001/payments-webhook &" }, "dependencies": { "@playwright/test": "^1.42.1", "@prisma/client": "5.19.1", - "@wasp.sh/wasp-app-runner": "0.0.7", + "@wasp.sh/wasp-app-runner": "0.0.10", "prisma": "5.19.1" }, "devDependencies": { diff --git a/template/e2e-tests/tests/demoAppTests.spec.ts b/template/e2e-tests/tests/demoAppTests.spec.ts index 2d3669082..c07a0a51d 100644 --- a/template/e2e-tests/tests/demoAppTests.spec.ts +++ b/template/e2e-tests/tests/demoAppTests.spec.ts @@ -21,8 +21,8 @@ test.describe.configure({ mode: "serial" }); test.beforeAll(async ({ browser }) => { page = await browser.newPage(); testUser = createRandomUser(); - await signUserUp({ page: page, user: testUser }); - await logUserIn({ page: page, user: testUser }); + await signUserUp({ page, user: testUser }); + await logUserIn({ page, user: testUser }); }); test.afterAll(async () => { diff --git a/template/e2e-tests/tests/utils.ts b/template/e2e-tests/tests/utils.ts index d0d62806e..0a9fba79c 100644 --- a/template/e2e-tests/tests/utils.ts +++ b/template/e2e-tests/tests/utils.ts @@ -90,7 +90,7 @@ export const makeStripePayment = async ({ }) => { test.slow(); // Stripe payments take a long time to confirm and can cause tests to fail so we use a longer timeout - await page.click('text="Pricing"'); + await page.goto("/pricing"); await page.waitForURL("**/pricing"); const buyBtn = page.locator(`button[aria-describedby="${planId}"]`); @@ -123,7 +123,7 @@ export const makeStripePayment = async ({ await page.waitForURL("**/checkout?status=success"); await page.waitForURL("**/account"); if (planId === "credits10") { - await expect(page.getByText("Credits remaining: 13")).toBeVisible(); + await expect(page.getByText("13 credits")).toBeVisible(); } else { await expect(page.getByText(planId)).toBeVisible(); } diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 000000000..f5f0375b2 --- /dev/null +++ b/tools/README.md @@ -0,0 +1,70 @@ +# Open SaaS Tools + +## dope.sh - Diff Or Patch Executor + +The `dope.sh` script allows you to easily create a diff between two projects (base and derived), +or to patch those diffs onto the base project to get the derived one. This is useful when a derived +project has only small changes on top of the base project and you want to keep it in a directory +in the same repo as the main project. + +Since derived apps (like `opensaas-sh` and `template-test`) are just the Open SaaS template with some small tweaks, +and we want to keep them up to date as the template changes, we don't version the actual app code in git. +Instead, we version the diffs between it and the template in an `opensaas-sh/app_diff/` directory. + +### Usage + +```sh +./dope.sh +``` + +- ``: The base project directory (e.g., `base/app/` or `template/app/`) +- ``: The derived project directory (e.g., `derived/app/` or `opensaas-sh/app/`) +- ``: Either `diff` or `patch` + - `diff`: Creates a diff between the base and derived directories + - `patch`: Applies existing diffs onto the base directory to recreate the derived directory + +The diffs are stored in a directory named `_diff/` (e.g., `derived/app_diff/` or `opensaas-sh/app_diff/`). + +### Diff structure + +The diff directory can contain `.diff` files to patch files from the base directory, +and `.copy` files to copy files directly from the diff directory to the derived directory +(useful for binary files). + +### Workflow + +The typical workflow is: + +1. Run `dope.sh` with the `patch` action to generate `derived/app/` from `base/app/` and `derived/app_diff/`: + + ```sh + ./dope.sh base/app derived/app patch + ``` + +2. If there are any conflicts (usually due to updates to the `base` app), modify `derived/app/` until you resolve them. Make any additional changes as needed. + +3. Run `dope.sh` with the `diff` action to generate a new `derived/app_diff/` based on the updated `derived/app/`: + + ```sh + ./dope.sh base/app derived/app diff + ``` + +### Requirements + +#### Running on macOS + +If you're running the `dope.sh` script on macOS, install: + +- `grealpath` (packaged within `coreutils`) +- `gpatch` +- `diffutils` + +You can easily install them through `brew`: + +```sh +brew install coreutils # contains grealpath +brew install gpatch +brew install diffutils +``` + +The script automatically detects macOS and uses `gpatch` instead of the default `patch` command. diff --git a/opensaas-sh/tools/dope.sh b/tools/dope.sh similarity index 67% rename from opensaas-sh/tools/dope.sh rename to tools/dope.sh index 07a35e6fa..88249477c 100755 --- a/opensaas-sh/tools/dope.sh +++ b/tools/dope.sh @@ -1,21 +1,33 @@ #!/usr/bin/env bash -# -# Diff Or Patch Executor -# -# Allows you to easily create a diff between the two projects (base and derived), or to patch those diffs onto the base project to get the derived one. -# Useful when derived project has only small changes on top of base project and you want to keep it in a dir in the same repo as main project. + +# Set the locale to C for consistent command behavior. +# For example, `sort` can sort differently depending on the locale, +# and `diff` might change if it's doing text or binary comparison. +# See: https://unix.stackexchange.com/questions/87745/what-does-lc-all-c-do +export LC_ALL=C # Determine the patch command to use based on OS PATCH_CMD="patch" if [[ "$(uname)" == "Darwin" ]]; then - # On macOS, require gpatch to be installed - if command -v gpatch &> /dev/null; then - PATCH_CMD="gpatch" - else - echo "Error: GNU patch (gpatch) not found. On MacOS, this script requires GNU patch." - echo "Install it with: brew install gpatch" - exit 1 + PATCH_CMD="gpatch" +fi + +# Assert that we are using GNU patch +if ! $PATCH_CMD --version 2> /dev/null | grep -q "GNU patch"; then + echo "Error: GNU patch not found." + if [[ "$(uname)" == "Darwin" ]]; then + echo "On macOS, install it with: brew install gpatch" + fi + exit 1 +fi + +# Assert that we are using GNU diff +if ! diff --version 2> /dev/null | grep -q "GNU diffutils"; then + echo "Error: GNU diff not found." + if [[ "$(uname)" == "Darwin" ]]; then + echo "On macOS, install it with: brew install diffutils" fi + exit 1 fi # List all the source files in the specified dir. @@ -39,6 +51,14 @@ ACTION=$3 DIFF_DIR="${DERIVED_DIR}_diff" DIFF_DIR_DELETIONS="${DIFF_DIR}/deletions" +file_is_binary() { + file --mime "$1" | grep -q "charset=binary" +} + +files_are_equal() { + cmp -s "$1" "$2" +} + # Based on base dir and derived dir, creates a diff dir that contains the diff between the two dirs. recreate_diff_dir() { mkdir -p "${DIFF_DIR}" @@ -52,8 +72,8 @@ recreate_diff_dir() { # For each source file in the derived dir, generate a .diff file between it and the # corresponding source file in the base dir. while IFS= read -r filepath; do - local derivedFilepath="${DERIVED_DIR}/${filepath}" local baseFilepath="${BASE_DIR}/${filepath}" + local derivedFilepath="${DERIVED_DIR}/${filepath}" local filepathToBeUsedAsBase="${baseFilepath}" # If the file is not one of the source files in base dir (e.g. is gitignored or doesn't exist), @@ -62,11 +82,19 @@ recreate_diff_dir() { filepathToBeUsedAsBase="/dev/null" fi - local DIFF_OUTPUT - DIFF_OUTPUT=$(diff -Nu --label "${baseFilepath}" --label "${derivedFilepath}" "${filepathToBeUsedAsBase}" "${derivedFilepath}") - if [ $? -eq 1 ]; then - mkdir -p "${DIFF_DIR}/$(dirname "${filepath}")" - echo "${DIFF_OUTPUT}" > "${DIFF_DIR}/${filepath}.diff" + if files_are_equal "${filepathToBeUsedAsBase}" "${derivedFilepath}"; then + continue + fi + + mkdir -p "${DIFF_DIR}/$(dirname "${filepath}")" + + if file_is_binary "${derivedFilepath}"; then + cp "${derivedFilepath}" "${DIFF_DIR}/${filepath}.copy" + echo "Generated ${DIFF_DIR}/${filepath}.copy" + else + diff -Nu --label "${baseFilepath}" --label "${derivedFilepath}" \ + "${filepathToBeUsedAsBase}" "${derivedFilepath}" \ + > "${DIFF_DIR}/${filepath}.diff" echo "Generated ${DIFF_DIR}/${filepath}.diff" fi done <<< "${DERIVED_FILES}" @@ -91,7 +119,7 @@ recreate_derived_dir() { # Copy all the source files from the base dir over to the derived dir. while IFS= read -r filepath; do - mkdir -p "${DERIVED_DIR}/$(dirname ${filepath})" + mkdir -p "${DERIVED_DIR}/$(dirname "${filepath}")" cp "${BASE_DIR}/${filepath}" "${DERIVED_DIR}/${filepath}" done <<< "${BASE_FILES}" @@ -100,7 +128,7 @@ recreate_derived_dir() { local num_patches_failed=0 while IFS= read -r diff_filepath; do local derived_filepath - derived_filepath="${diff_filepath#${DIFF_DIR}/}" + derived_filepath="${diff_filepath#"${DIFF_DIR}"/}" derived_filepath="${derived_filepath%.diff}" local patch_output @@ -118,13 +146,35 @@ recreate_derived_dir() { echo "" done < <(find "${DIFF_DIR}" -name "*.diff") + # For each .copy file in diff dir, copy it to the corresponding location in the derived dir. + while IFS= read -r copy_filepath; do + local derived_filepath + derived_filepath="${copy_filepath#"${DIFF_DIR}"/}" + derived_filepath="${derived_filepath%.copy}" + + mkdir -p "${DERIVED_DIR}/$(dirname "${derived_filepath}")" + cp "${copy_filepath}" "${DERIVED_DIR}/${derived_filepath}" + echo "Copied ${copy_filepath} to ${DERIVED_DIR}/${derived_filepath}" + echo -e "${GREEN_COLOR}[OK]${RESET_COLOR}" + echo "" + done < <(find "${DIFF_DIR}" -name "*.copy") + # Delete any files that exist in the base dir but shouldn't exist in the derived dir. # TODO: also allow deletion of dirs. if [ -f "${DIFF_DIR_DELETIONS}" ]; then while IFS= read -r filepath; do + # Skip empty lines + [[ -z "$filepath" ]] && continue + local derived_dir_filepath local rm_exit_code derived_dir_filepath="${DERIVED_DIR}/${filepath}" + + # Only delete if it exists and is a file + if [[ ! -f "$derived_dir_filepath" ]]; then + continue + fi + rm "${derived_dir_filepath}" rm_exit_code=$? if [ ${rm_exit_code} -eq 0 ]; then diff --git a/tools/wasp b/tools/wasp new file mode 100755 index 000000000..12fd61186 --- /dev/null +++ b/tools/wasp @@ -0,0 +1,5 @@ +#!/bin/sh + +# Just a wrapper to always use the latest Wasp CLI from main branch. + +npx -y https://pkg.pr.new/@wasp.sh/wasp-cli@main "$@"